Skip to content

Commit 4e035ce

Browse files
Merge pull request #16848 from nextcloud/fix/file-list-on-browse-up
fix(file-list): browse up
2 parents 329e4ff + 17271d8 commit 4e035ce

File tree

4 files changed

+361
-54
lines changed

4 files changed

+361
-54
lines changed

app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,7 +1575,13 @@ class FileDisplayActivity :
15751575

15761576
private fun handleRemovedFolder(syncFolderRemotePath: String?) {
15771577
DisplayUtils.showSnackMessage(this, R.string.sync_current_folder_was_removed, syncFolderRemotePath)
1578-
browseToRoot()
1578+
fileListFragment?.let {
1579+
it.parentFolderFinder.getParentOnFirstParentRemoved(syncFolderRemotePath, storageManager)?.let { target ->
1580+
it.listDirectory(target, MainApp.isOnlyOnDevice())
1581+
updateActionBarTitleAndHomeButton(target)
1582+
file = target
1583+
}
1584+
}
15791585
}
15801586

15811587
private fun updateFileList(
@@ -1899,13 +1905,13 @@ class FileDisplayActivity :
18991905
// endregion
19001906

19011907
fun browseToRoot() {
1902-
val listOfFiles = this.listOfFilesFragment
1903-
if (listOfFiles != null) { // should never be null, indeed
1908+
listOfFilesFragment?.let {
19041909
val root = storageManager.getFileByPath(OCFile.ROOT_PATH)
1905-
listOfFiles.resetSearchAttributes()
1906-
file = listOfFiles.currentFile
1910+
it.resetSearchAttributes()
1911+
file = it.currentFile
19071912
startSyncFolderOperation(root, false)
19081913
}
1914+
19091915
binding.fabMain.setImageResource(R.drawable.ic_plus)
19101916
resetScrollingAndUpdateActionBar()
19111917
}

app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@
8080
import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
8181
import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
8282
import com.owncloud.android.lib.resources.files.ToggleFavoriteRemoteOperation;
83-
import com.owncloud.android.lib.resources.files.model.RemoteFile;
8483
import com.owncloud.android.lib.resources.status.E2EVersion;
8584
import com.owncloud.android.lib.resources.status.OCCapability;
8685
import com.owncloud.android.lib.resources.status.Type;
@@ -106,6 +105,7 @@
106105
import com.owncloud.android.ui.events.FavoriteEvent;
107106
import com.owncloud.android.ui.events.FileLockEvent;
108107
import com.owncloud.android.ui.events.SearchEvent;
108+
import com.owncloud.android.ui.fragment.helper.ParentFolderFinder;
109109
import com.owncloud.android.ui.helpers.FileOperationsHelper;
110110
import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface;
111111
import com.owncloud.android.ui.preview.PreviewImageFragment;
@@ -124,7 +124,6 @@
124124
import org.greenrobot.eventbus.Subscribe;
125125
import org.greenrobot.eventbus.ThreadMode;
126126

127-
import java.io.File;
128127
import java.util.ArrayList;
129128
import java.util.Collection;
130129
import java.util.HashSet;
@@ -228,6 +227,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
228227
protected String mLimitToMimeType;
229228
private FloatingActionButton mFabMain;
230229
public static boolean isMultipleFileSelectedForCopyOrMove = false;
230+
public final ParentFolderFinder parentFolderFinder = new ParentFolderFinder();
231231

232232
private static final Intent scanIntentExternalApp = new Intent("org.fairscan.app.action.SCAN_TO_PDF");
233233

@@ -1019,7 +1019,6 @@ private void updateSortAndGridMenuItems() {
10191019
}
10201020
}
10211021

1022-
10231022
/**
10241023
* Call this, when the user presses the up button.
10251024
* <p>
@@ -1029,62 +1028,27 @@ private void updateSortAndGridMenuItems() {
10291028
* return Count of folder levels browsed up.
10301029
*/
10311030
public int onBrowseUp() {
1032-
if (mFile == null) {
1031+
if (mFile == null || mFile.isRootDirectory()) {
1032+
return 0;
1033+
}
1034+
1035+
final var result = parentFolderFinder.getParent(mFile, mContainerActivity.getStorageManager());
1036+
OCFile target = result.getSecond();
1037+
1038+
if (target == null) {
1039+
Log_OC.e(TAG, "onBrowseUp: could not resolve parent, staying put");
10331040
return 0;
10341041
}
10351042

1036-
Pair<Integer, OCFile> result = getPreviousFile();
1037-
mFile = result.second;
1043+
mFile = target;
10381044
setFileDepth(mFile);
10391045

1040-
// since on browse down sets it to the false, browse up should set back to true if current search type is not NO_SEARCH
10411046
if (mFile.isRootDirectory() && currentSearchType != NO_SEARCH) {
10421047
searchFragment = true;
10431048
}
10441049

10451050
updateFileList();
1046-
return result.first;
1047-
}
1048-
1049-
private Pair<Integer, OCFile> getPreviousFile() {
1050-
if (mFile == null) {
1051-
return new Pair<>(0, null);
1052-
}
1053-
1054-
FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
1055-
int moveCount = 0;
1056-
String parentPath;
1057-
OCFile parentDir;
1058-
1059-
if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
1060-
parentPath = new File(mFile.getRemotePath()).getParent();
1061-
parentPath = ensureTrailingSeparator(parentPath);
1062-
parentDir = storageManager.getFileByPath(parentPath);
1063-
moveCount++;
1064-
} else {
1065-
parentDir = storageManager.getFileByPath(ROOT_PATH);
1066-
parentPath = ROOT_PATH;
1067-
}
1068-
1069-
// Keep going up until we find a valid folder
1070-
while (parentDir == null && !ROOT_PATH.equals(parentPath)) {
1071-
parentPath = new File(parentPath).getParent();
1072-
if (parentPath == null) {
1073-
parentPath = ROOT_PATH; // fallback to root
1074-
}
1075-
parentPath = ensureTrailingSeparator(parentPath);
1076-
parentDir = storageManager.getFileByPath(parentPath);
1077-
moveCount++;
1078-
}
1079-
1080-
return new Pair<>(moveCount, parentDir);
1081-
}
1082-
1083-
private String ensureTrailingSeparator(String path) {
1084-
if (path == null) {
1085-
return ROOT_PATH;
1086-
}
1087-
return path.endsWith(OCFile.PATH_SEPARATOR) ? path : path + OCFile.PATH_SEPARATOR;
1051+
return result.getFirst();
10881052
}
10891053

10901054
private void updateFileList() {
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.owncloud.android.ui.fragment.helper
9+
10+
import com.owncloud.android.datamodel.FileDataStorageManager
11+
import com.owncloud.android.datamodel.OCFile
12+
import com.owncloud.android.lib.common.utils.Log_OC
13+
14+
@Suppress("ReturnCount")
15+
class ParentFolderFinder {
16+
companion object {
17+
private const val TAG = "ParentFolderFinder"
18+
}
19+
20+
/**
21+
* User tries to move up but parent folder was deleted thus parent of parent
22+
* will be used as destination else ROOT directory
23+
*/
24+
fun getParentOnFirstParentRemoved(path: String?, storageManager: FileDataStorageManager?): OCFile? {
25+
if (storageManager == null) {
26+
Log_OC.e(TAG, "StorageManager is null")
27+
return null
28+
}
29+
30+
if (path.isNullOrEmpty() || path == OCFile.ROOT_PATH) {
31+
Log_OC.w(TAG, "Path is null, empty, or already at root. Falling back to ROOT.")
32+
return storageManager.getFileByEncryptedRemotePath(OCFile.ROOT_PATH)
33+
}
34+
35+
return walkUpByPath(path, storageManager).second
36+
}
37+
38+
fun getParent(file: OCFile?, storageManager: FileDataStorageManager?): Pair<Int, OCFile?> {
39+
if (file == null || file.isRootDirectory) {
40+
Log_OC.e(TAG, "File is null or already at root")
41+
return 0 to file
42+
}
43+
44+
if (storageManager == null) {
45+
Log_OC.e(TAG, "StorageManager is null")
46+
return 0 to file
47+
}
48+
49+
// id based lookup
50+
val parentId = file.parentId
51+
if (parentId > FileDataStorageManager.ROOT_PARENT_ID && parentId != file.fileId) {
52+
storageManager.getFileById(parentId)?.let { parentById ->
53+
if (parentById.isFolder) {
54+
return 1 to parentById
55+
}
56+
}
57+
58+
Log_OC.w(TAG, "ID lookup missed for parentId=$parentId, falling back to path walk")
59+
}
60+
61+
// path-based walk up
62+
return walkUpByPath(file.remotePath, storageManager)
63+
}
64+
65+
/**
66+
* Walks the remote path upward one segment at a time until a valid folder
67+
* is found in the DB, or we reach root.
68+
*/
69+
private fun walkUpByPath(initialPath: String?, storageManager: FileDataStorageManager): Pair<Int, OCFile?> {
70+
var path = initialPath
71+
var moveCount = 0
72+
73+
while (!path.isNullOrEmpty() && path != OCFile.ROOT_PATH) {
74+
val parentPath = resolveParentPath(path)
75+
moveCount++
76+
77+
if (parentPath == null) {
78+
Log_OC.w(TAG, "Failed to resolve parent path for $path, fallback to root")
79+
break
80+
}
81+
82+
// Check if this resolved parent exists in DB
83+
storageManager.getFileByEncryptedRemotePath(parentPath)?.let { candidate ->
84+
if (candidate.isFolder) {
85+
return moveCount to candidate
86+
}
87+
}
88+
89+
if (parentPath == OCFile.ROOT_PATH) {
90+
Log_OC.w(TAG, "Root path reached but not found in DB loop, forcing fallback")
91+
break
92+
}
93+
94+
// Move up one more level for the next iteration
95+
path = parentPath
96+
}
97+
98+
// fallback to root
99+
val root = storageManager.getFileByEncryptedRemotePath(OCFile.ROOT_PATH)
100+
return moveCount to root
101+
}
102+
103+
/**
104+
* Returns the parent path of `path` with a guaranteed trailing "/",
105+
* or `null` if `path` is already root or invalid.
106+
*
107+
*
108+
* Correctly handles paths with or without a trailing separator, so both
109+
* "/Foo/Bar" and "/Foo/Bar/" return "/Foo/".
110+
*/
111+
private fun resolveParentPath(path: String?): String? {
112+
if (path.isNullOrEmpty() || path == OCFile.ROOT_PATH) {
113+
return null
114+
}
115+
116+
// Strip trailing "/" before computing parent
117+
val normalized = if (path.endsWith(OCFile.PATH_SEPARATOR)) {
118+
path.substring(0, path.length - 1)
119+
} else {
120+
path
121+
}
122+
123+
val lastSep = normalized.lastIndexOf(OCFile.PATH_SEPARATOR)
124+
if (lastSep <= 0) {
125+
Log_OC.w(TAG, "No separator found, or only one at the start")
126+
return OCFile.ROOT_PATH
127+
}
128+
129+
// e.g. "/Foo/Bar" → "/Foo/"
130+
return normalized.substring(0, lastSep + 1)
131+
}
132+
}

0 commit comments

Comments
 (0)