Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b494809
chore(gradle): Improve Gradle Configuration
jim-acn Oct 10, 2025
645481a
chore(gradle): Improve Gradle Configuration
jim-acn Oct 10, 2025
9807c02
chore(gradle): Resolve comment
jim-acn Oct 13, 2025
e77fdcd
chore(gradle): Resolve comment
jim-acn Oct 14, 2025
b9b51c8
paginate: local file list adapter
alperozturk96 Oct 1, 2025
aef2b4a
paginate: local file list adapter
alperozturk96 Oct 1, 2025
d3330a5
paginate: local file list adapter
alperozturk96 Oct 1, 2025
e766ef9
refactor: pagination
alperozturk96 Oct 6, 2025
af452ab
refactor: pagination
alperozturk96 Oct 6, 2025
a740cd6
fix: sorting order
alperozturk96 Oct 6, 2025
c3d5bed
add: file helper tests
alperozturk96 Oct 6, 2025
da8afa7
fix: code analytics
alperozturk96 Oct 6, 2025
a2698d2
fix: local file list duplicate item
alperozturk96 Oct 14, 2025
cfaf825
fix(l10n): Update translations from Transifex
nextcloud-bot Oct 15, 2025
71fcc18
fix: folder download
alperozturk96 Oct 14, 2025
d243293
rename to folder download worker
alperozturk96 Oct 14, 2025
da14449
rename to folder download worker
alperozturk96 Oct 15, 2025
5fdd7db
chore: upgrade android sdk 36
alperozturk96 Oct 6, 2025
a9b76a2
refactor: update back press gesture
alperozturk96 Oct 6, 2025
5628c7b
fix: custom back press navigation
alperozturk96 Oct 6, 2025
945fd35
fix: custom back press navigation
alperozturk96 Oct 6, 2025
0544556
fix: code analytics
alperozturk96 Oct 6, 2025
710bdaa
fix: code analytics
alperozturk96 Oct 6, 2025
d12d3a3
add: Unsupported platform APIs and unable to migrate due to outdated …
alperozturk96 Oct 7, 2025
7714335
fix: back press of fda
alperozturk96 Oct 15, 2025
4e277dd
fix: back press of fda
alperozturk96 Oct 15, 2025
1fa87b9
fix: make file upload worker long running task
alperozturk96 Oct 14, 2025
c3fde61
fix: save file for folder download
alperozturk96 Oct 15, 2025
60866b8
fix: save file for folder download
alperozturk96 Oct 15, 2025
6ad8a98
fix(l10n): Update translations from Transifex
nextcloud-bot Oct 16, 2025
f3b0b8a
fix: handle not modified and no content response
alperozturk96 Oct 15, 2025
e299a74
fix: handle not modified
alperozturk96 Oct 15, 2025
ed06c09
fix: git conflict
alperozturk96 Oct 15, 2025
0696850
fix: upload cancel
alperozturk96 Sep 29, 2025
00c8c7c
fix: upload cancel
alperozturk96 Sep 29, 2025
9b04f37
fix: upload cancel
alperozturk96 Sep 30, 2025
54056b9
fix: upload cancel
alperozturk96 Sep 30, 2025
523f822
fix: upload cancel
alperozturk96 Sep 30, 2025
ee69e98
fix: upload cancel
alperozturk96 Sep 30, 2025
506fbe9
fix: upload cancel
alperozturk96 Sep 30, 2025
0c7317b
fix: upload cancel
alperozturk96 Sep 30, 2025
b8c55bd
fix: upload cancel
alperozturk96 Sep 30, 2025
d0402ae
fix: git conflict
alperozturk96 Oct 15, 2025
60ffc50
chore: update lib
alperozturk96 Oct 15, 2025
fa4efa0
chore(gradle): Resolve conflict
jim-acn Oct 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor: pagination
Signed-off-by: alperozturk <[email protected]>
  • Loading branch information
alperozturk96 authored and jim-acn committed Oct 17, 2025
commit e766ef96977fd96a2a066b4fdcce8d92f5f35aaa
60 changes: 25 additions & 35 deletions app/src/main/java/com/nextcloud/utils/FileHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,57 +12,47 @@ import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.util.stream.Collectors
import kotlin.io.path.pathString

@Suppress("NestedBlockDepth")
object FileHelper {
private const val TAG = "FileHelper"

fun fetchFiles(folder: File?, offset: Int, limit: Int): List<File> {
val files = mutableListOf<File>()
if (folder == null || !folder.exists() || !folder.isDirectory) return files
fun listDirectoryEntries(
directory: File?,
startIndex: Int,
maxItems: Int,
fetchFolders: Boolean
): List<File> {
if (directory == null || !directory.exists() || !directory.isDirectory) return emptyList()

val dir = folder.toPath()
var skipped = 0
try {
Files.newDirectoryStream(dir).use { stream ->
for (entry in stream) {
if (skipped < offset) {
skipped++
continue
}
files.add(entry.toFile())
if (files.size >= limit) break
}
}
} catch (e: IOException) {
Log_OC.d(TAG, "fetchFiles: $e")
}
return files
}
return try {
val allEntries = Files.list(directory.toPath())
.map { it.toFile() }
.collect(Collectors.toList())

fun fetchFolders(folder: File?, offset: Int, limit: Int): List<File> {
val folders = mutableListOf<File>()
if (folder == null || !folder.exists() || !folder.isDirectory) return folders
val result = mutableListOf<File>()
var skipped = 0

val dir = folder.toPath()
var skipped = 0
try {
Files.newDirectoryStream(dir).use { stream ->
for (entry in stream) {
if (!entry.toFile().isDirectory) continue
if (skipped < offset) {
for (file in allEntries) {
val shouldInclude = if (fetchFolders) file.isDirectory else true

if (shouldInclude) {
if (skipped < startIndex) {
skipped++
continue
}
folders.add(entry.toFile())
if (folders.size >= limit) break
result.add(file)
if (result.size >= maxItems) break
}
}

result
} catch (e: IOException) {
Log_OC.d(TAG, "fetchFolders: $e")
Log_OC.d(TAG, "listDirectoryEntries: $e")
emptyList()
}
return folders
}

fun listFilesRecursive(files: Collection<File>): List<String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ public class LocalFileListAdapter extends RecyclerView.Adapter<RecyclerView.View

private static final int PAGE_SIZE = 50;
private int currentOffset = 0;
private File currentDirectory = null;

public LocalFileListAdapter(boolean localFolderPickerMode,
File directory,
Expand Down Expand Up @@ -305,100 +304,73 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int
*
* @param directory New file to adapt. Can be NULL, meaning "no content to adapt".
*/
@SuppressLint("NotifyDataSetChanged")
public void swapDirectory(final File directory) {
localFileListFragmentInterface.setLoading(true);
currentDirectory = directory;
currentOffset = 0; // reset offset

Executors.newSingleThreadExecutor().execute(() -> {
List<File> firstPage;
if (directory == null) {
firstPage = new ArrayList<>();
} else {
if (mLocalFolderPicker) {
firstPage = FileHelper.INSTANCE.fetchFolders(directory, currentOffset, PAGE_SIZE);
} else {
firstPage = FileHelper.INSTANCE.fetchFiles(directory, currentOffset, PAGE_SIZE);
}
}
List<File> firstPage = FileHelper.INSTANCE.listDirectoryEntries(directory, currentOffset, PAGE_SIZE, mLocalFolderPicker);

// sort and filter
if (!firstPage.isEmpty()) {
FileSortOrder sortOrder = preferences.getSortOrderByType(FileSortOrder.Type.localFileListView);
firstPage = sortOrder.sortLocalFiles(firstPage);

boolean showHiddenFiles = preferences.isShowHiddenFilesEnabled();
if (!showHiddenFiles) {
firstPage = filterHiddenFiles(firstPage);
}
firstPage = sortAndFilterHiddenEntries(firstPage);
}

final List<File> initialFiles = firstPage;
if (!mLocalFolderPicker) {
currentOffset += initialFiles.size();
}
// first set the currentOffset to PAGE_SIZE
currentOffset += PAGE_SIZE;
updateUIForFirstPage(firstPage);

// update UI immediately with first page
new Handler(Looper.getMainLooper()).post(() -> {
mFiles = new ArrayList<>(initialFiles);
mFilesAll = new ArrayList<>(initialFiles);
notifyDataSetChanged();
localFileListFragmentInterface.setLoading(false);
});
// load the rest silently in the background and updates the currentOffset by PAGE_SIZE for pagination
loadRemainingEntries(directory, mLocalFolderPicker);
});
}

// load the rest silently in the background
if(mLocalFolderPicker) {
loadRemainingFolders(directory);
} else {
loadRemainingFiles(directory);
}
@SuppressLint("NotifyDataSetChanged")
private void updateUIForFirstPage(List<File> firstPage) {
new Handler(Looper.getMainLooper()).post(() -> {
mFiles = new ArrayList<>(firstPage);
mFilesAll = new ArrayList<>(firstPage);
notifyDataSetChanged();
localFileListFragmentInterface.setLoading(false);
});
}

private void loadRemainingFolders(File directory) {
if (!mLocalFolderPicker) return;
private List<File> sortAndFilterHiddenEntries(List<File> nextPage) {
boolean showHiddenFiles = preferences.isShowHiddenFilesEnabled();
FileSortOrder sortOrder = preferences.getSortOrderByType(FileSortOrder.Type.localFileListView);

if (!showHiddenFiles) {
nextPage = filterHiddenFiles(nextPage);
}

return sortOrder.sortLocalFiles(nextPage);
}

private void loadRemainingEntries(File directory, boolean fetchFolders) {
while (true) {
List<File> nextPage = FileHelper.INSTANCE.fetchFolders(directory, currentOffset, PAGE_SIZE);
if (nextPage.isEmpty()) break;
List<File> nextPage = FileHelper.INSTANCE.listDirectoryEntries(directory, currentOffset, PAGE_SIZE, fetchFolders);
if (nextPage.isEmpty()) {
break;
}

currentOffset += nextPage.size();
nextPage = sortAndFilterHiddenEntries(nextPage);

new Handler(Looper.getMainLooper()).post(() -> {
int positionStart = mFiles.size();
mFiles.addAll(nextPage);
mFilesAll.addAll(nextPage);
Log_OC.d(TAG, "loadRemainingFolders, next page loaded. Item size: " + mFilesAll.size());
notifyItemRangeInserted(positionStart, nextPage.size());
});
currentOffset += PAGE_SIZE;
notifyItemRange(nextPage);
}
}

private void loadRemainingFiles(File directory) {
if (mLocalFolderPicker) return;
private void notifyItemRange(List<File> updatedList) {
new Handler(Looper.getMainLooper()).post(() -> {
int from = mFiles.size();
int to = updatedList.size();

boolean showHiddenFiles = preferences.isShowHiddenFilesEnabled();
FileSortOrder sortOrder = preferences.getSortOrderByType(FileSortOrder.Type.localFileListView);

while (true) {
List<File> nextPage = FileHelper.INSTANCE.fetchFiles(directory, currentOffset, PAGE_SIZE);
if (nextPage.isEmpty()) break;
mFiles.addAll(updatedList);
mFilesAll.addAll(updatedList);

if (!showHiddenFiles) nextPage = filterHiddenFiles(nextPage);
nextPage = sortOrder.sortLocalFiles(nextPage);
Log_OC.d(TAG, "notifyItemRange, item size: " + mFilesAll.size());

currentOffset += nextPage.size();

List<File> finalNextPage = nextPage;
new Handler(Looper.getMainLooper()).post(() -> {
int positionStart = mFiles.size();
mFiles.addAll(finalNextPage);
mFilesAll.addAll(finalNextPage);
Log_OC.d(TAG, "loadRemainingFiles, next page loaded. Item size: " + mFilesAll.size());
notifyItemRangeInserted(positionStart, finalNextPage.size());
});
}
notifyItemRangeInserted(from, to);
});
}

@SuppressLint("NotifyDataSetChanged")
Expand Down