diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3acbb6a..96a7a5c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -58,6 +58,7 @@ @@ -86,10 +87,11 @@ android:label="@string/image_optimizer" android:parentActivityName=".ui.screens.imageoptimizer.imageoptimizer.ImageOptimizerActivity" /> - + android:parentActivityName=".ui.screens.main.MainActivity" /> 0 + val isLoading: Boolean by viewModel.isLoading.collectAsState() val filesTypesTitles = data.analyzeState.fileTypesData.fileTypesTitles val apkExtensions = data.analyzeState.fileTypesData.apkExtensions @@ -143,11 +146,7 @@ fun AnalyzeScreen( return@remember finalMap } - if (groupedFiles.isEmpty()) { - Box(modifier = Modifier.fillMaxSize()) { - CircularProgressIndicator() - } - } + println("$TAG - Recomposing AnalyzeScreen") Column( modifier = Modifier @@ -162,17 +161,20 @@ fun AnalyzeScreen( .fillMaxWidth() , ) { when { - data.analyzeState.scannedFileList.isEmpty() -> { + isLoading -> { + println("$TAG - ScannedFileList is empty, showing CircularProgressIndicator") // Log empty state Box(modifier = Modifier.fillMaxSize() , contentAlignment = Alignment.Center) { CircularProgressIndicator() } } groupedFiles.isEmpty() || data.analyzeState.isFileScanEmpty -> { + println("$TAG - GroupedFiles is empty or file scan is empty, showing NoFilesFoundScreen") // Log empty state NoFilesFoundScreen(viewModel = viewModel) } else -> { + println("$TAG - Files found, displaying them in tabs") // Log files found state val tabs = groupedFiles.keys.toList() val pagerState : PagerState = rememberPagerState(pageCount = { tabs.size }) @@ -242,6 +244,7 @@ fun AnalyzeScreen( } } if (data.analyzeState.scannedFileList.isNotEmpty()) { + println("$TAG - ScannedFileList is not empty, displaying selection controls") // Log selection controls state Row( modifier = Modifier.fillMaxWidth() , verticalAlignment = Alignment.CenterVertically , @@ -291,7 +294,7 @@ fun AnalyzeScreen( onEndButtonText = R.string.delete_forever , view = view) - if (data.analyzeState.isMoveToTrashConfirmationDialogVisible) { + if (data.analyzeState.isDeleteForeverConfirmationDialogVisible) { ConfirmationAlertDialog(confirmationTitle = stringResource(R.string.delete_forever_title) , confirmationMessage = stringResource(R.string.delete_forever_message) , confirmationConfirmButtonText = stringResource(android.R.string.ok) , diff --git a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/HomeViewModel.kt b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/HomeViewModel.kt index 4c4a14d..27a8bdd 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/HomeViewModel.kt @@ -13,6 +13,13 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.io.File +/** + * ViewModel for the home screen. + * Manages the UI state and interacts with the repository to perform file operations. + * + * @param application The application instance. + * @author Mihai-Cristian Condrea + */ class HomeViewModel(application : Application) : BaseViewModel(application) { private val repository = HomeRepository(DataStore(application) , application) private val _uiState = MutableStateFlow(UiHomeModel()) @@ -22,11 +29,17 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { prepareScreenData() } + /** + * Loads initial screen data, including storage info and file types data. + */ private fun prepareScreenData() { updateStorageInfo() populateFileTypesData() } + /** + * Updates the storage information in the UI state. + */ private fun updateStorageInfo() { viewModelScope.launch(coroutineExceptionHandler) { repository.getStorageInfo { uiHomeModel -> @@ -41,12 +54,14 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { } } + /** + * Initiates file analysis and updates the UI state with the results. + */ fun analyze() { viewModelScope.launch(context = Dispatchers.Default + coroutineExceptionHandler) { showLoading() repository.analyzeFiles { result -> - val filteredFiles = result.first - val emptyFolders = result.second + val (filteredFiles , emptyFolders) = result _uiState.update { currentUiState -> currentUiState.copy( analyzeState = currentUiState.analyzeState.copy( @@ -62,9 +77,13 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { } } + /** + * Rescans files and updates the UI state with the new results. + */ fun rescanFiles() { viewModelScope.launch(context = Dispatchers.Default + coroutineExceptionHandler) { showLoading() + // Clear previous scan results before starting a new scan _uiState.update { currentUiState -> currentUiState.copy( analyzeState = currentUiState.analyzeState.copy( @@ -85,6 +104,9 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { } } + /** + * Hides the analyze screen. + */ fun onCloseAnalyzeComposable() { viewModelScope.launch(coroutineExceptionHandler) { _uiState.update { currentUiState -> @@ -97,6 +119,12 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { } } + /** + * Updates the file selection state and selected file count. + * + * @param file The file whose selection state has changed. + * @param isChecked True if the file is now selected, false otherwise. + */ fun onFileSelectionChange(file : File , isChecked : Boolean) { viewModelScope.launch(coroutineExceptionHandler) { val updatedFileSelectionStates = @@ -111,7 +139,7 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { areAllFilesSelected = when { newSelectedCount == currentUiState.analyzeState.scannedFileList.size && newSelectedCount > 0 -> true newSelectedCount == 0 -> false - isChecked -> currentUiState.analyzeState.areAllFilesSelected + isChecked -> currentUiState.analyzeState.areAllFilesSelected // Maintain 'select all' state if an item was checked and all were already checked else -> false } ) @@ -120,6 +148,9 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { } } + /** + * Toggles the "select all" state for files. + */ fun toggleSelectAllFiles() { viewModelScope.launch(context = Dispatchers.Default + coroutineExceptionHandler) { val newState = ! _uiState.value.analyzeState.areAllFilesSelected @@ -141,6 +172,9 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { } } + /** + * Deletes selected files and updates the UI state. + */ fun clean() { viewModelScope.launch(context = Dispatchers.Default + coroutineExceptionHandler) { val filesToDelete = @@ -165,6 +199,9 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { } } + /** + * Moves selected files to the trash and updates the UI state. + */ fun moveToTrash() { viewModelScope.launch(context = Dispatchers.Default + coroutineExceptionHandler) { val filesToMove = @@ -189,6 +226,9 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { } } + /** + * Populates file types data in the UI state. + */ private fun populateFileTypesData() { viewModelScope.launch(coroutineExceptionHandler) { repository.getFileTypesData { fileTypesData -> @@ -203,13 +243,21 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { } } - fun setDeleteForeverConfirmationDialogVisibility(isVisible: Boolean) { + /** + * Sets the visibility of the "delete forever" confirmation dialog. + * @param isVisible True to show the dialog, false to hide it. + */ + fun setDeleteForeverConfirmationDialogVisibility(isVisible : Boolean) { _uiState.update { it.copy(analyzeState = it.analyzeState.copy(isDeleteForeverConfirmationDialogVisible = isVisible)) } } - fun setMoveToTrashConfirmationDialogVisibility(isVisible: Boolean) { + /** + * Sets the visibility of the "move to trash" confirmation dialog. + * @param isVisible True to show the dialog, false to hide it. + */ + fun setMoveToTrashConfirmationDialogVisibility(isVisible : Boolean) { _uiState.update { it.copy(analyzeState = it.analyzeState.copy(isMoveToTrashConfirmationDialogVisible = isVisible)) } diff --git a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/repository/HomeRepository.kt b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/repository/HomeRepository.kt index 35a27e5..77c48fb 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/repository/HomeRepository.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/repository/HomeRepository.kt @@ -2,6 +2,7 @@ package com.d4rk.cleaner.ui.screens.home.repository import android.app.Application import android.os.Environment +import android.util.Log import com.d4rk.cleaner.data.datastore.DataStore import com.d4rk.cleaner.data.model.ui.screens.FileTypesData import com.d4rk.cleaner.data.model.ui.screens.UiHomeModel @@ -10,11 +11,22 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File +/** + * Repository class for handling home screen data and operations. + * + * @param dataStore The DataStore instance for accessing user preferences. + * @param application The application instance for accessing resources and external files directory. + * @author Mihai-Cristian Condrea + */ class HomeRepository( dataStore : DataStore , application : Application , ) : HomeRepositoryImplementation(application , dataStore) { private val fileScanner = FileScanner(dataStore , application) + /** + * Retrieves storage information. + * @param onSuccess Callback function to be invoked with the storage information. + */ suspend fun getStorageInfo(onSuccess : (UiHomeModel) -> Unit) { withContext(Dispatchers.IO) { val storageInfo : UiHomeModel = getStorageInfo() @@ -24,7 +36,11 @@ class HomeRepository( } } - suspend fun getFileTypesData(onSuccess: (FileTypesData) -> Unit) { + /** + * Retrieves file types data from resources. + * @param onSuccess Callback function to be invoked with the file types data. + */ + suspend fun getFileTypesData(onSuccess : (FileTypesData) -> Unit) { withContext(Dispatchers.IO) { val fileTypesData = getFileTypesDataFromResources() withContext(Dispatchers.Main) { @@ -33,16 +49,28 @@ class HomeRepository( } } + /** + * Analyzes files and retrieves filtered files and empty folders. + * @param onSuccess Callback function to be invoked with the filtered files and empty folders. + */ suspend fun analyzeFiles(onSuccess : (Pair , List>) -> Unit) { withContext(Dispatchers.IO) { - val (filteredFiles , emptyFolders) = fileScanner.getAllFiles() - + fileScanner.startScanning() + val filteredFiles = fileScanner.getFilteredFiles() + val emptyFolders = fileScanner.getAllFiles().second.ifEmpty { + emptyList() + } + println("Cleaner for Android -> analyzeFiles() received filteredFiles size: ${filteredFiles.size}, emptyFolders size: ${emptyFolders.size}") withContext(Dispatchers.Main) { onSuccess(Pair(filteredFiles , emptyFolders)) } } } + /** + * Rescans files and retrieves filtered files. + * @param onSuccess Callback to receive the result of filtered files. + */ suspend fun rescanFiles(onSuccess : (List) -> Unit) { withContext(Dispatchers.IO) { fileScanner.reset() @@ -54,6 +82,10 @@ class HomeRepository( } } + /** + * Retrieves files from the trash directory. + * @return A list of files in the trash directory. + */ suspend fun getTrashFiles() : List { return withContext(Dispatchers.IO) { val trashDir = File( @@ -69,6 +101,11 @@ class HomeRepository( } } + /** + * Deletes the specified files. + * @param filesToDelete The set of files to delete. + * @param onSuccess Callback function to be invoked after successful deletion. + */ suspend fun deleteFiles(filesToDelete : Set , onSuccess : () -> Unit) { withContext(Dispatchers.IO) { deleteFiles(filesToDelete) @@ -78,6 +115,11 @@ class HomeRepository( } } + /** + * Moves the specified files to the trash directory. + * @param filesToMove The list of files to move to trash. + * @param onSuccess Callback function to be invoked after successful move. + */ suspend fun moveToTrash(filesToMove : List , onSuccess : () -> Unit) { withContext(Dispatchers.IO) { moveToTrash(filesToMove) @@ -87,6 +129,11 @@ class HomeRepository( } } + /** + * Restores the specified files from the trash directory. + * @param filesToRestore The set of files to restore from trash. + * @param onSuccess Callback function to be invoked after successful restore. + */ suspend fun restoreFromTrash(filesToRestore : Set , onSuccess : () -> Unit) { withContext(Dispatchers.IO) { restoreFromTrash(filesToRestore) diff --git a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/repository/HomeRepositoryImplementation.kt b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/repository/HomeRepositoryImplementation.kt index 358051a..f8550de 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/repository/HomeRepositoryImplementation.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/home/repository/HomeRepositoryImplementation.kt @@ -1,7 +1,6 @@ package com.d4rk.cleaner.ui.screens.home.repository import android.app.Application -import android.content.Context import android.media.MediaScannerConnection import android.os.Environment import com.d4rk.cleaner.R @@ -21,34 +20,39 @@ abstract class HomeRepositoryImplementation( private val trashDir = File(application.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) , "Trash") - private val sharedPrefs = - application.getSharedPreferences("TrashMetadata" , Context.MODE_PRIVATE) - suspend fun getStorageInfo() : UiHomeModel { return suspendCoroutine { continuation -> StorageUtils.getStorageInfo(application) { used , total , usageProgress -> continuation.resume( UiHomeModel( - storageUsageProgress = usageProgress , usedStorageFormatted = used , totalStorageFormatted = total + storageUsageProgress = usageProgress , + usedStorageFormatted = used , + totalStorageFormatted = total , ) ) } } } - suspend fun getFileTypesDataFromResources(): FileTypesData { + suspend fun getFileTypesDataFromResources() : FileTypesData { return suspendCoroutine { continuation -> - val apkExtensions = application.resources.getStringArray(R.array.apk_extensions).toList() - val imageExtensions = application.resources.getStringArray(R.array.image_extensions).toList() - val videoExtensions = application.resources.getStringArray(R.array.video_extensions).toList() - val audioExtensions = application.resources.getStringArray(R.array.audio_extensions).toList() - val archiveExtensions = application.resources.getStringArray(R.array.archive_extensions).toList() - val fileTypesTitles = application.resources.getStringArray(R.array.file_types_titles).toList() + val apkExtensions = + application.resources.getStringArray(R.array.apk_extensions).toList() + val imageExtensions = + application.resources.getStringArray(R.array.image_extensions).toList() + val videoExtensions = + application.resources.getStringArray(R.array.video_extensions).toList() + val audioExtensions = + application.resources.getStringArray(R.array.audio_extensions).toList() + val archiveExtensions = + application.resources.getStringArray(R.array.archive_extensions).toList() + val fileTypesTitles = + application.resources.getStringArray(R.array.file_types_titles).toList() val fileTypesData = FileTypesData( apkExtensions = apkExtensions , imageExtensions = imageExtensions , videoExtensions = videoExtensions , - audioExtensions = audioExtensions , // FIXME: Type mismatch: inferred type is Array<(out) String!> but IntArray was expected + audioExtensions = audioExtensions , archiveExtensions = archiveExtensions , fileTypesTitles = fileTypesTitles ) @@ -82,44 +86,40 @@ abstract class HomeRepositoryImplementation( if (file.renameTo(destination)) { dataStore.addTrashFilePath(originalPath) + MediaScannerConnection.scanFile( - application , - arrayOf( - destination.absolutePath , - file.absolutePath - ) , - null , - null + application , arrayOf( + destination.absolutePath , file.absolutePath + ) , null , null ) } } } } - fun restoreFromTrash(filesToRestore : Set) { + suspend fun restoreFromTrash(filesToRestore : Set) { filesToRestore.forEach { file -> if (file.exists()) { - val originalPath = sharedPrefs.getString(file.absolutePath , null) - ?: file.absolutePath.replace(Regex("_\\d+$") , "") - - val destinationFile = File(originalPath) - val destinationParent = destinationFile.parentFile + dataStore.trashFilePaths.collect { paths -> + val originalPath = + paths.find { it == file.absolutePath.replace(Regex("_\\d+$") , "") } + if (originalPath != null) { + val destinationFile = File(originalPath) + val destinationParent = destinationFile.parentFile - if (destinationParent?.exists() == false) { - destinationParent.mkdirs() - } + if (destinationParent?.exists() == false) { + destinationParent.mkdirs() + } - if (file.renameTo(destinationFile)) { - MediaScannerConnection.scanFile( - application , - arrayOf( - destinationFile.absolutePath , - file.absolutePath - ) , - null , - null - ) - sharedPrefs.edit().remove(file.absolutePath).apply() + if (file.renameTo(destinationFile)) { + MediaScannerConnection.scanFile( + application , arrayOf( + destinationFile.absolutePath , file.absolutePath + ) , null , null + ) + dataStore.removeTrashFilePath(originalPath) + } + } } } } diff --git a/app/src/main/kotlin/com/d4rk/cleaner/utils/cleaning/FileScanner.kt b/app/src/main/kotlin/com/d4rk/cleaner/utils/cleaning/FileScanner.kt index 23a16ae..b7173c1 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/utils/cleaning/FileScanner.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/utils/cleaning/FileScanner.kt @@ -5,44 +5,43 @@ import android.os.Environment import com.d4rk.cleaner.R import com.d4rk.cleaner.constants.cleaning.ExtensionsConstants import com.d4rk.cleaner.data.datastore.DataStore -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.toList import java.io.File /** * A utility class for scanning and filtering files based on user-defined preferences. + * This class scans files in the external storage directory, excluding a designated "Trash" directory. + * It filters files based on user preferences for various file types (generic, archive, APK, audio, video, image, and empty folders). + * Preferences are retrieved from a DataStore instance, and file type extensions are defined in string arrays within the application's resources. * - * This class scans and filters files in the external storage directory based on user preferences such as file types (e.g., generic, archive, APK, audio, video, image files). The preferences are retrieved from a DataStore instance and the file types are defined in the provided Resources instance. - * - * @property dataStore A DataStore instance used for accessing user preferences. - * @property resources A Resources instance used for accessing string arrays that define file types. + * @param dataStore The DataStore instance for accessing user preferences. + * @param application The application instance for accessing resources and external files directory. + * @author Mihai-Cristian Condrea */ -class FileScanner(private val dataStore : DataStore , val application : Application) { +class FileScanner(private val dataStore : DataStore , private val application : Application) { private var preferences : Map = emptyMap() private var filteredFiles : List = emptyList() /** - * Initiates the file scanning process asynchronously. - * - * This function loads user preferences, retrieves all files in the external storage directory, applies filters based on the preferences, and stores the filtered files. The scanning process runs asynchronously to avoid blocking the main thread. - * - * @throws Exception If an error occurs during the scanning process. + * Starts the file scanning process. + * Loads user preferences, retrieves all files from external storage (excluding the "Trash" directory), + * and filters the files based on the loaded preferences. */ suspend fun startScanning() { + println("Cleaner for Android -> startScanning() called") loadPreferences() - val (files) = getAllFiles() - filteredFiles = filterFiles(files).toList() + val (files , emptyFolders) = getAllFiles() + filteredFiles = files + println("Cleaner for Android -> startScanning() completed, filteredFiles size: ${filteredFiles.size}, emptyFolders size: ${emptyFolders.size}") } /** - * Loads user preferences from the data datastore into the [preferences] map. - * - * The preferences include whether to filter generic files, archive files, APK files, audio files, video files, and image files. + * Loads user preferences from the DataStore. + * Retrieves user preferences for each file type filter and stores them in a map. */ private suspend fun loadPreferences() { + println("Cleaner for Android -> loadPreferences() called") preferences = with(ExtensionsConstants) { mapOf( GENERIC_EXTENSIONS to dataStore.genericFilter.first() , @@ -50,111 +49,168 @@ class FileScanner(private val dataStore : DataStore , val application : Applicat APK_EXTENSIONS to dataStore.deleteApkFiles.first() , IMAGE_EXTENSIONS to dataStore.deleteImageFiles.first() , AUDIO_EXTENSIONS to dataStore.deleteAudioFiles.first() , - VIDEO_EXTENSIONS to dataStore.deleteVideoFiles.first(), + VIDEO_EXTENSIONS to dataStore.deleteVideoFiles.first() , EMPTY_FOLDERS to dataStore.deleteEmptyFolders.first() ) } + println("Cleaner for Android -> loadPreferences() completed, preferences: $preferences") } /** - * Retrieves all files from the external storage directory recursively. - * - * @return A list of all files found in the external storage directory. + * Retrieves all files from the external storage directory, excluding the "Trash" directory. + * Filters files and empty folders based on user preferences. + * @return A Pair containing a list of filtered files and a list of empty folders. */ - fun getAllFiles(): Pair , MutableList> { + fun getAllFiles() : Pair , List> { val files = mutableListOf() val emptyFolders = mutableListOf() val stack = ArrayDeque() - val root: File = Environment.getExternalStorageDirectory() + val root : File = Environment.getExternalStorageDirectory() stack.addFirst(root) - val trashDir = File(application.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "Trash") + val trashDir = + File(application.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) , "Trash") + + val archiveExtensions = + application.resources.getStringArray(R.array.archive_extensions).toSet() + val apkExtensions = application.resources.getStringArray(R.array.apk_extensions).toSet() + val imageExtensions = application.resources.getStringArray(R.array.image_extensions).toSet() + val videoExtensions = application.resources.getStringArray(R.array.video_extensions).toSet() + val audioExtensions = application.resources.getStringArray(R.array.audio_extensions).toSet() + val genericExtensions = + application.resources.getStringArray(R.array.generic_extensions).toSet() while (stack.isNotEmpty()) { - val currentFile: File = stack.removeFirst() + val currentFile = stack.removeFirst() if (currentFile.isDirectory) { - if (!currentFile.absolutePath.startsWith(trashDir.absolutePath)) { + if (! currentFile.absolutePath.startsWith(trashDir.absolutePath)) { val children = currentFile.listFiles() - if (children == null || children.isEmpty()) { - emptyFolders.add(currentFile) - } else { - children.forEach { stack.addLast(it) } + if (children != null) { + if (children.isEmpty() && preferences[ExtensionsConstants.EMPTY_FOLDERS] == true) { + emptyFolders.add(currentFile) + } + else { + for (child in children) { + if (shouldKeepFile( + child , + archiveExtensions , + apkExtensions , + imageExtensions , + videoExtensions , + audioExtensions , + genericExtensions + ) + ) { + if (child.isDirectory) { + stack.addLast(child) + } + else { + files.add(child) + } + } + } + } + } + else { + println("Cleaner for Android -> listFiles() returned null for directory: ${currentFile.absolutePath}. Possible permission issue.") } + + } + } + else { + if (shouldKeepFile( + currentFile , + archiveExtensions , + apkExtensions , + imageExtensions , + videoExtensions , + audioExtensions , + genericExtensions + ) + ) { + files.add(currentFile) } - } else { - files.add(currentFile) } - } - return if (preferences[ExtensionsConstants.EMPTY_FOLDERS] == true) { - Pair(files, emptyFolders) - } else { - Pair(files, mutableListOf()) } - } - private fun filterFiles(allFiles : List) : Flow = flow { - allFiles.filter(::shouldFilterFile).forEach { emit(it) } + return Pair(files , emptyFolders) } - private fun shouldFilterFile(file : File) : Boolean { - return preferences.any { (key : String , value : Boolean) -> - with(ExtensionsConstants) { - return@with when (key) { - GENERIC_EXTENSIONS -> { - val extensions : Array = - application.resources.getStringArray(R.array.generic_extensions) - return@with value && extensions.map { it.removePrefix(prefix = ".") } - .contains(file.extension) - } + /** + * Checks if a file should be kept based on user preferences and file extension. + * + * @param file The file to check. + * @param archiveExtensions The set of archive file extensions. + * @param apkExtensions The set of APK file extensions. + * @param imageExtensions The set of image file extensions. + * @param videoExtensions The set of video file extensions. + * @param audioExtensions The set of audio file extensions. + * @param genericExtensions The set of generic file extensions. + * @return True if the file should be kept, false otherwise. + */ + private fun shouldKeepFile( + file : File , + archiveExtensions : Set , + apkExtensions : Set , + imageExtensions : Set , + videoExtensions : Set , + audioExtensions : Set , + genericExtensions : Set , + ) : Boolean { + if (preferences.isEmpty() || preferences.all { ! it.value }) return false + val extension = file.extension.lowercase() + + return when { + preferences[ExtensionsConstants.ARCHIVE_EXTENSIONS] == true && extension in archiveExtensions -> { + true + } - ARCHIVE_EXTENSIONS -> { - val extensions : Array = - application.resources.getStringArray(R.array.archive_extensions) - return@with value && extensions.contains(file.extension) - } + preferences[ExtensionsConstants.APK_EXTENSIONS] == true && extension in apkExtensions -> { + true + } - APK_EXTENSIONS -> { - val extensions : Array = - application.resources.getStringArray(R.array.apk_extensions) - return@with value && extensions.contains(file.extension) - } + preferences[ExtensionsConstants.IMAGE_EXTENSIONS] == true && extension in imageExtensions -> { + true + } - AUDIO_EXTENSIONS -> { - val extensions : Array = - application.resources.getStringArray(R.array.audio_extensions) - return@with value && extensions.contains(file.extension) - } + preferences[ExtensionsConstants.VIDEO_EXTENSIONS] == true && extension in videoExtensions -> { + true + } - VIDEO_EXTENSIONS -> { - val extensions : Array = - application.resources.getStringArray(R.array.video_extensions) - return@with value && extensions.contains(file.extension) - } + preferences[ExtensionsConstants.AUDIO_EXTENSIONS] == true && extension in audioExtensions -> { + true + } - IMAGE_EXTENSIONS -> { - val extensions : Array = - application.resources.getStringArray(R.array.image_extensions) - return@with value && extensions.contains(file.extension) - } + preferences[ExtensionsConstants.GENERIC_EXTENSIONS] == true && extension in genericExtensions -> { + true + } - else -> false - } + extension.isEmpty() && preferences.any { it.value } -> { + true + } + + else -> { + false } } } /** * Returns the list of filtered files. - * - * @return A list of files that match the user-defined preferences. + * @return The list of filtered files. */ fun getFilteredFiles() : List { + println("getFilteredFiles() called") return filteredFiles } + /** + * Resets the filtered files list to an empty list. + */ fun reset() { + println("reset() called") filteredFiles = emptyList() } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 965edc8..53d72e7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.7.0" +agp = "8.7.1" appcompat = "1.7.0" appUpdateKtx = "2.1.0" billing = "7.1.1"