From 777863b15d6731a8072aceb19b3925d0e382a91f Mon Sep 17 00:00:00 2001 From: MitsuhaMone Date: Mon, 19 Oct 2026 17:11:52 +0000 Subject: [PATCH] Search backups by their note - applySearch also matches the per-backup note - filter the backups list in the app page and in the restore page by the active note search, so the right backup can be found quickly among dozens The original indices of backupsNewestFirst are preserved when filtering, as they address the backup to restore. If no backup note matches the query (e.g. the app was found by its name), the full backups list is shown as before. --- .../ui/compose/component/BatchPackageItem.kt | 15 +++++++++++++-- .../backup/ui/compose/component/Recyclers.kt | 2 ++ .../com/machiav3lli/backup/ui/pages/AppPage.kt | 4 +++- .../com/machiav3lli/backup/ui/pages/BatchPage.kt | 1 + .../com/machiav3lli/backup/ui/pages/HomePage.kt | 1 + .../com/machiav3lli/backup/utils/FilterUtils.kt | 12 +++++++++++- 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/machiav3lli/backup/ui/compose/component/BatchPackageItem.kt b/src/main/java/com/machiav3lli/backup/ui/compose/component/BatchPackageItem.kt index b9bf2ca14..69764c06f 100644 --- a/src/main/java/com/machiav3lli/backup/ui/compose/component/BatchPackageItem.kt +++ b/src/main/java/com/machiav3lli/backup/ui/compose/component/BatchPackageItem.kt @@ -149,6 +149,7 @@ fun RestorePackageItem( onClick: (Package, Boolean, Boolean) -> Unit = { _: Package, _: Boolean, _: Boolean -> }, onBackupApkClick: (String, Boolean, Int) -> Unit = { _: String, _: Boolean, _: Int -> }, onBackupDataClick: (String, Boolean, Int) -> Unit = { _: String, _: Boolean, _: Int -> }, + searchQuery: String = "", ) { val packageItem by remember(item) { mutableStateOf(item) } val apkBC by apkBackupChecked @@ -239,9 +240,19 @@ fun RestorePackageItem( ) } } - item.backupsNewestFirst.forEachIndexed { index, item -> + // filter by note search, but keep the original indices of + // backupsNewestFirst, since they are used to address the + // backup to restore + val indexedBackups = + item.backupsNewestFirst.mapIndexed { index, backup -> index to backup } + val displayedBackups = + if (searchQuery.isEmpty()) indexedBackups + else indexedBackups + .filter { it.second.note.contains(searchQuery, ignoreCase = true) } + .ifEmpty { indexedBackups } + displayedBackups.forEach { (index, backup) -> RestoreBackupItem( - item = item, + item = backup, index = index, isApkChecked = index == apkBC, isDataChecked = index == dataBC, diff --git a/src/main/java/com/machiav3lli/backup/ui/compose/component/Recyclers.kt b/src/main/java/com/machiav3lli/backup/ui/compose/component/Recyclers.kt index dc7a8a44c..505407fff 100644 --- a/src/main/java/com/machiav3lli/backup/ui/compose/component/Recyclers.kt +++ b/src/main/java/com/machiav3lli/backup/ui/compose/component/Recyclers.kt @@ -104,6 +104,7 @@ fun BatchPackageRecycler( modifier: Modifier = Modifier, productsList: PersistentList?, restore: Boolean = false, + searchQuery: String = "", apkBackupCheckedList: SnapshotStateMap, dataBackupCheckedList: SnapshotStateMap, onBackupApkClick: (String, Boolean, Int) -> Unit = { _: String, _: Boolean, _: Int -> }, @@ -129,6 +130,7 @@ fun BatchPackageRecycler( onClick, onBackupApkClick, onBackupDataClick, + searchQuery, ) else BatchPackageItem( it, diff --git a/src/main/java/com/machiav3lli/backup/ui/pages/AppPage.kt b/src/main/java/com/machiav3lli/backup/ui/pages/AppPage.kt index 7ca7273ae..e5860ce0d 100644 --- a/src/main/java/com/machiav3lli/backup/ui/pages/AppPage.kt +++ b/src/main/java/com/machiav3lli/backup/ui/pages/AppPage.kt @@ -113,6 +113,7 @@ import com.machiav3lli.backup.ui.dialogs.RestoreDialogUI import com.machiav3lli.backup.ui.dialogs.StringInputDialogUI import com.machiav3lli.backup.utils.BackupShareUtils import com.machiav3lli.backup.utils.TraceUtils +import com.machiav3lli.backup.utils.applyNoteSearch import com.machiav3lli.backup.utils.extensions.koinNeoViewModel import com.machiav3lli.backup.utils.infoChips import com.machiav3lli.backup.viewmodels.AppVM @@ -121,6 +122,7 @@ import timber.log.Timber @Composable fun AppPage( packageName: String, + searchQuery: String = "", viewModel: AppVM = koinNeoViewModel(), onDismiss: () -> Unit, ) { @@ -502,7 +504,7 @@ fun AppPage( } } this.items( - items = pkg.backupsNewestFirst, + items = pkg.backupsNewestFirst.applyNoteSearch(searchQuery), span = { GridItemSpan(columns) }) { BackupItem( it, diff --git a/src/main/java/com/machiav3lli/backup/ui/pages/BatchPage.kt b/src/main/java/com/machiav3lli/backup/ui/pages/BatchPage.kt index 8e45dd5ba..0c265b8b9 100644 --- a/src/main/java/com/machiav3lli/backup/ui/pages/BatchPage.kt +++ b/src/main/java/com/machiav3lli/backup/ui/pages/BatchPage.kt @@ -228,6 +228,7 @@ fun BatchPage( .fillMaxSize(), productsList = state.filteredPackages, restore = !backupBoolean, + searchQuery = state.searchQuery, apkBackupCheckedList = viewModel.apkBackupCheckedList, dataBackupCheckedList = viewModel.dataBackupCheckedList, onBackupApkClick = { packageName: String, b: Boolean, i: Int -> diff --git a/src/main/java/com/machiav3lli/backup/ui/pages/HomePage.kt b/src/main/java/com/machiav3lli/backup/ui/pages/HomePage.kt index d02da9fca..a3354db4b 100644 --- a/src/main/java/com/machiav3lli/backup/ui/pages/HomePage.kt +++ b/src/main/java/com/machiav3lli/backup/ui/pages/HomePage.kt @@ -337,6 +337,7 @@ fun HomePage( AnimatedPane { AppPage( packageName = packageName, + searchQuery = mainState.searchQuery, onDismiss = onDismiss, ) } diff --git a/src/main/java/com/machiav3lli/backup/utils/FilterUtils.kt b/src/main/java/com/machiav3lli/backup/utils/FilterUtils.kt index b9f6c466a..a18ad2b3b 100644 --- a/src/main/java/com/machiav3lli/backup/utils/FilterUtils.kt +++ b/src/main/java/com/machiav3lli/backup/utils/FilterUtils.kt @@ -38,6 +38,7 @@ import com.machiav3lli.backup.R import com.machiav3lli.backup.Sort import com.machiav3lli.backup.UpdatedFilter import com.machiav3lli.backup.data.dbs.entity.AppExtras +import com.machiav3lli.backup.data.dbs.entity.Backup import com.machiav3lli.backup.data.entity.Package import com.machiav3lli.backup.data.entity.SortFilterModel import com.machiav3lli.backup.data.entity.SpecialFilter @@ -106,9 +107,18 @@ fun Collection.applySearch( ) ) .any { it.contains(query, ignoreCase = true) } - ) + ) || item.backupList.any { it.note.contains(query, ignoreCase = true) } } +/** + * Filters a list of backups by matching their note against the search query. + * Falls back to the full list when no backup's note matches + * (e.g. when the app was found by its name instead of a note). + */ +fun List.applyNoteSearch(query: String): List = + if (query.isEmpty()) this + else filter { it.note.contains(query, ignoreCase = true) }.ifEmpty { this } + fun Collection.applyFilter( filter: SortFilterModel, tagsMap: Map>,