Remember what is in the trash, and add a screen to restore from it

The staged-delete work left a hole. The undo handle lived in BrowserUiState, so a delete from an earlier session was unreachable even though the files were sitting on the volume; and because nothing on disk recorded which folder an item came from, not even the user could put one back by hand with confidence. Adding a Trash screen alone would not have fixed it: the recovery data did not survive the process. So the origin is now written at delete time.

data/ gains its first persistence: a SQLite index (staged path, original path, name, type, size, timestamp). SQLite rather than Room because an annotation processor is a large addition to this build for one table and three queries, and behind a TrashIndex port so domain/Trash keeps no Context while the tests drive it with a map and the real one is still tested against real SQLite. New Trash entry in the overflow menu lists batches and restores a whole one.

The index is an index, not the truth: the staged files are the truth, and the two can disagree if a user empties the folder by hand or pulls a card. Reconciled both ways - restorableFrom drops entries whose file has gone, purgeExpired forgets entries whose folder has gone and sweeps folders no entry mentions - and rows are written after the move, so a crash leaves an unindexed folder (harmless, swept) rather than an entry that could never restore. Verified on hardware: delete, force-stop, relaunch, restore.

Also corrects the docs where they claimed input swipe x y x y 900 is delivered as a plain tap on MIUI. It does open the selection bar - it was driven that way - but it has also landed as a tap, so it is flaky rather than impossible.
This commit is contained in:
2026-10-15 10:25:37 +02:00
parent d1bc61e717
commit 667a85f80c
16 changed files with 758 additions and 22 deletions
@@ -5,7 +5,9 @@ import de.softwareapp_hb.filemanager.data.ContentResolverIncomingSource
import de.softwareapp_hb.filemanager.data.DeleteRequestFactory
import de.softwareapp_hb.filemanager.data.IncomingSource
import de.softwareapp_hb.filemanager.data.MediaStoreDeleter
import de.softwareapp_hb.filemanager.data.SqliteTrashIndex
import de.softwareapp_hb.filemanager.data.StorageProvider
import de.softwareapp_hb.filemanager.data.TrashIndex
import de.softwareapp_hb.filemanager.domain.OperationNotifier
import de.softwareapp_hb.filemanager.service.ServiceOperationNotifier
@@ -34,11 +36,16 @@ class FileManagerApplication : Application() {
lateinit var incoming: IncomingSource
private set
/** Remembers what is in the trash, and where each item came from. */
lateinit var trashIndex: TrashIndex
private set
override fun onCreate() {
super.onCreate()
storage = StorageProvider(this)
notifier = ServiceOperationNotifier(this)
deleteRequests = MediaStoreDeleter(this).factory()
incoming = ContentResolverIncomingSource(contentResolver)
trashIndex = SqliteTrashIndex(this)
}
}