diff --git a/changelog/unreleased/4570 b/changelog/unreleased/4570 new file mode 100644 index 00000000000..53f7acfe5e3 --- /dev/null +++ b/changelog/unreleased/4570 @@ -0,0 +1,6 @@ +Feature: Maintain scroll position when user scrolls and navigates in file/folder list + +Saves scroll position when user scrolls and navigates in file/folder list + +https://github.com/owncloud/android/issues/4528 +https://github.com/owncloud/android/pull/4570 diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index 8997f953ec2..562daf39019 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -798,10 +798,15 @@ class MainFileListFragment : Fragment(), collectLatestLifecycleFlow(mainFileListViewModel.fileListUiState) { fileListUiState -> if (fileListUiState !is MainFileListViewModel.FileListUiState.Success) return@collectLatestLifecycleFlow + saveScrollPosition() + fileListAdapter.updateFileList( filesToAdd = fileListUiState.folderContent, fileListOption = fileListUiState.fileListOption, ) + + restoreScrollPosition() + showOrHideEmptyView(fileListUiState) binding.spaceHeader.root.apply { @@ -994,6 +999,38 @@ class MainFileListFragment : Fragment(), _binding = null } + /** + * Saves the current scroll position of the RecyclerView by capturing the indices of + * the last visible items from the StaggeredGridLayoutManager. + * + * This position is stored in the ViewModel so it can be restored later, + * typically after the adapter data is updated. This ensures continuity + * in the user’s scroll experience when navigating between fragments or refreshing data. + */ + private fun saveScrollPosition() { + val layoutManager = binding.recyclerViewMainFileList.layoutManager as? StaggeredGridLayoutManager + mainFileListViewModel.lastVisibleItemPositions = layoutManager?.findLastVisibleItemPositions(null) + } + + /** + * Restores the scroll position of the RecyclerView based on the previously + * saved positions in the ViewModel. It scrolls to the maximum of the last + * visible item positions captured earlier using [saveScrollPosition]. + * + * This method is posted to the queue to ensure it runs after the + * RecyclerView has completed its layout pass and the adapter update has been applied. + */ + private fun restoreScrollPosition() { + binding.recyclerViewMainFileList.post { + mainFileListViewModel.lastVisibleItemPositions?.let { positions -> + val maxPosition = positions.maxOrNull() ?: 0 + binding.recyclerViewMainFileList.scrollToPosition(maxPosition) + mainFileListViewModel.lastVisibleItemPositions = null + } + } + } + + fun updateFileListOption(newFileListOption: FileListOption, file: OCFile) { mainFileListViewModel.updateFileListOption(newFileListOption) binding.swipeRefreshMainFileList.isEnabled = newFileListOption != FileListOption.AV_OFFLINE diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt index 6d629b8352c..d584cd04e84 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListViewModel.kt @@ -94,6 +94,7 @@ class MainFileListViewModel( fileListOptionParam: FileListOption, ) : ViewModel() { + var lastVisibleItemPositions: IntArray? = null private val showHiddenFiles: Boolean = sharedPreferencesProvider.getBoolean(PREF_SHOW_HIDDEN_FILES, false) val currentFolderDisplayed: MutableStateFlow = MutableStateFlow(initialFolderToDisplay)