package de.softwareapp_hb.filemanager import android.app.Application import android.content.Context import coil3.ImageLoader import coil3.SingletonImageLoader import coil3.video.VideoFrameDecoder import de.softwareapp_hb.filemanager.data.ApkInspector import de.softwareapp_hb.filemanager.data.BookmarkStore 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.PackageManagerApkInspector import de.softwareapp_hb.filemanager.data.SearchIndex import de.softwareapp_hb.filemanager.data.SqliteBookmarkStore import de.softwareapp_hb.filemanager.data.SqliteSearchIndex 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 /** * Application entry point and the app's tiny object graph. * * There is no dependency-injection framework here on purpose: the long-lived collaborators below are * built once and handed to the one ViewModel that needs them. Nothing holds a reference to an * Activity, so none of this leaks. */ class FileManagerApplication : Application(), SingletonImageLoader.Factory { /** Where files live and which backend is in effect. */ lateinit var storage: StorageProvider private set /** Keeps the foreground service in step with a running operation. */ lateinit var notifier: OperationNotifier private set /** Builds the platform's own delete confirmation, when it can. */ lateinit var deleteRequests: DeleteRequestFactory private set /** Reads whatever another app shares in. */ lateinit var incoming: IncomingSource private set /** Remembers what is in the trash, and where each item came from. */ lateinit var trashIndex: TrashIndex private set /** Reads an APK's manifest, so a package can be previewed before it is installed. */ lateinit var apkInspector: ApkInspector private set /** Remembers what is on the volumes by name, so a search does not have to walk them. */ lateinit var searchIndex: SearchIndex private set /** Remembers the folders the user starred, so the home screen can offer them back. */ lateinit var bookmarkStore: BookmarkStore private set override fun onCreate() { super.onCreate() storage = StorageProvider(this) notifier = ServiceOperationNotifier(this) deleteRequests = MediaStoreDeleter(this).factory() incoming = ContentResolverIncomingSource(contentResolver) trashIndex = SqliteTrashIndex(this) apkInspector = PackageManagerApkInspector(this) searchIndex = SqliteSearchIndex(this) bookmarkStore = SqliteBookmarkStore(this) } /** * The image loader every thumbnail and viewer goes through. * * Built here for one reason: **video frames**. Coil has no idea what to do with an `.mp4` until a * decoder is registered for it, and `coil-video` supplies one that understands the platform's own * `MediaMetadataRetriever`. * * The decoder is registered **explicitly** rather than left to `coil-video`'s `META-INF/services` * entry, because that entry does not survive a release build: R8 removes both the service file and * the class it names, since a class mentioned only from a resource looks unused. Checked against * the release APK, where neither was present — which would have left video thumbnails working in * debug and silently showing an icon in the build everyone installs. * * Only the decoder is configured. Coil's own cache sizes are left alone deliberately: they are * sized from the device's heap, which a number written here would only be guessing at. */ override fun newImageLoader(context: Context): ImageLoader = ImageLoader.Builder(context) .components { add(VideoFrameDecoder.Factory()) } .build() }