diff --git a/app/build.gradle.kts b/app/build.gradle.kts index edebb81..584b006 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -114,6 +114,7 @@ dependencies { // Lifecycle implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.serialization.json) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.common.java8) implementation(libs.androidx.lifecycle.livedata.ktx) diff --git a/app/src/main/kotlin/com/d4rk/cleaner/data/datastore/DataStore.kt b/app/src/main/kotlin/com/d4rk/cleaner/data/datastore/DataStore.kt index a00476d..5e4098f 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/data/datastore/DataStore.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/data/datastore/DataStore.kt @@ -16,14 +16,14 @@ import kotlinx.coroutines.flow.map val Context.dataStore by preferencesDataStore(name = DataStoreNamesConstants.DATA_STORE_SETTINGS) -class DataStore(context: Context) { +class DataStore(context : Context) { private val dataStore = context.dataStore companion object { @Volatile - private var instance: DataStore? = null + private var instance : DataStore? = null - fun getInstance(context: Context): DataStore { + fun getInstance(context : Context) : DataStore { return instance ?: synchronized(lock = this) { instance ?: DataStore(context).also { instance = it } } @@ -32,12 +32,12 @@ class DataStore(context: Context) { // Last used app notifications private val lastUsedKey = - longPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_LAST_USED) - val lastUsed: Flow = dataStore.data.map { preferences -> + longPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_LAST_USED) + val lastUsed : Flow = dataStore.data.map { preferences -> preferences[lastUsedKey] ?: 0 } - suspend fun saveLastUsed(timestamp: Long) { + suspend fun saveLastUsed(timestamp : Long) { dataStore.edit { preferences -> preferences[lastUsedKey] = timestamp } @@ -45,12 +45,12 @@ class DataStore(context: Context) { // Startup private val startupKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_STARTUP) - val startup: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_STARTUP) + val startup : Flow = dataStore.data.map { preferences -> preferences[startupKey] != false } - suspend fun saveStartup(isFirstTime: Boolean) { + suspend fun saveStartup(isFirstTime : Boolean) { dataStore.edit { preferences -> preferences[startupKey] = isFirstTime } @@ -59,81 +59,81 @@ class DataStore(context: Context) { // Display val themeModeState = mutableStateOf(value = "follow_system") private val themeModeKey = - stringPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_THEME_MODE) - val themeMode: Flow = dataStore.data.map { preferences -> + stringPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_THEME_MODE) + val themeMode : Flow = dataStore.data.map { preferences -> preferences[themeModeKey] ?: "follow_system" } - suspend fun saveThemeMode(mode: String) { + suspend fun saveThemeMode(mode : String) { dataStore.edit { preferences -> preferences[themeModeKey] = mode } } private val amoledModeKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_AMOLED_MODE) - val amoledMode: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_AMOLED_MODE) + val amoledMode : Flow = dataStore.data.map { preferences -> preferences[amoledModeKey] == true } - suspend fun saveAmoledMode(isChecked: Boolean) { + suspend fun saveAmoledMode(isChecked : Boolean) { dataStore.edit { preferences -> preferences[amoledModeKey] = isChecked } } private val dynamicColorsKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DYNAMIC_COLORS) - val dynamicColors: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DYNAMIC_COLORS) + val dynamicColors : Flow = dataStore.data.map { preferences -> preferences[dynamicColorsKey] != false } - suspend fun saveDynamicColors(isChecked: Boolean) { + suspend fun saveDynamicColors(isChecked : Boolean) { dataStore.edit { preferences -> preferences[dynamicColorsKey] = isChecked } } private val bouncyButtonsKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_BOUNCY_BUTTONS) - val bouncyButtons: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_BOUNCY_BUTTONS) + val bouncyButtons : Flow = dataStore.data.map { preferences -> preferences[bouncyButtonsKey] != false } - suspend fun saveBouncyButtons(isChecked: Boolean) { + suspend fun saveBouncyButtons(isChecked : Boolean) { dataStore.edit { preferences -> preferences[bouncyButtonsKey] = isChecked } } - fun getStartupPage(): Flow { + fun getStartupPage() : Flow { return dataStore.data.map { preferences -> preferences[stringPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_STARTUP_PAGE)] ?: BottomBarRoutes.HOME } } - suspend fun saveStartupPage(startupPage: String) { + suspend fun saveStartupPage(startupPage : String) { dataStore.edit { preferences -> preferences[stringPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_STARTUP_PAGE)] = - startupPage + startupPage } } - fun getShowBottomBarLabels(): Flow { + fun getShowBottomBarLabels() : Flow { return dataStore.data.map { preferences -> preferences[booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_SHOW_BOTTOM_BAR_LABELS)] != false } } private val languageKey = - stringPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_LANGUAGE) + stringPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_LANGUAGE) - fun getLanguage(): Flow = dataStore.data.map { preferences -> + fun getLanguage() : Flow = dataStore.data.map { preferences -> preferences[languageKey] ?: "en" } - suspend fun saveLanguage(language: String) { + suspend fun saveLanguage(language : String) { dataStore.edit { preferences -> preferences[languageKey] = language } @@ -141,185 +141,205 @@ class DataStore(context: Context) { // Cleaning private val cleanedSpaceKey = longPreferencesKey(name = "cleaned_space") - val cleanedSpace: Flow = dataStore.data.map { preferences -> + val cleanedSpace : Flow = dataStore.data.map { preferences -> preferences[cleanedSpaceKey] ?: 0L } - suspend fun addCleanedSpace(space: Long) { + suspend fun addCleanedSpace(space : Long) { dataStore.edit { preferences -> preferences[cleanedSpaceKey] = (preferences[cleanedSpaceKey] ?: 0L) + space } } private val lastScanTimestampKey = longPreferencesKey(name = "last_scan_timestamp") - val lastScanTimestamp: Flow = dataStore.data.map { preferences -> + val lastScanTimestamp : Flow = dataStore.data.map { preferences -> preferences[lastScanTimestampKey] ?: 0L } - suspend fun saveLastScanTimestamp(timestamp: Long) { + suspend fun saveLastScanTimestamp(timestamp : Long) { dataStore.edit { preferences -> preferences[lastScanTimestampKey] = timestamp } } - private val trashFilePathsKey = stringSetPreferencesKey("trash_file_paths") + private val trashFileOriginalPathsKey = stringSetPreferencesKey("trash_file_original_paths") - val trashFilePaths: Flow> = dataStore.data.map { preferences -> - preferences[trashFilePathsKey] ?: emptySet() + val trashFileOriginalPaths: Flow> = dataStore.data.map { preferences -> + preferences[trashFileOriginalPathsKey] ?: emptySet() } - suspend fun addTrashFilePath(filePath: String) { + suspend fun addTrashFileOriginalPath(originalPath: String) { dataStore.edit { settings -> - val currentPaths = settings[trashFilePathsKey] ?: emptySet() - settings[trashFilePathsKey] = currentPaths + filePath + val currentPaths = settings[trashFileOriginalPathsKey] ?: emptySet() + settings[trashFileOriginalPathsKey] = currentPaths + originalPath } } - suspend fun removeTrashFilePath(filePath: String) { + suspend fun removeTrashFileOriginalPath(originalPath: String) { dataStore.edit { settings -> - val currentPaths = settings[trashFilePathsKey] ?: emptySet() - settings[trashFilePathsKey] = currentPaths - filePath + val currentPaths = settings[trashFileOriginalPathsKey] ?: emptySet() + settings[trashFileOriginalPathsKey] = currentPaths - originalPath } } - private val trashSizeKey = longPreferencesKey(name = "trash_size") - val trashSize: Flow = dataStore.data.map { preferences -> - preferences[trashSizeKey] ?: 0L + private val trashFilePathsKey = stringSetPreferencesKey("trash_file_paths") + + val trashFilePaths: Flow>> = dataStore.data.map { preferences -> + preferences[trashFilePathsKey]?.mapNotNull { entry -> + val parts = entry.split("||") + if (parts.size == 2) { + Pair(parts[0] , parts[1]) + } + else { + println("Cleaner for Android -> Invalid entry in trashFilePaths: $entry. It should contain the '||' delimiter.") + null + } + }?.toSet() ?: emptySet() + } - suspend fun addTrashSize(size: Long) { - dataStore.edit { preferences -> - preferences[trashSizeKey] = (preferences[trashSizeKey] ?: 0L) + size + suspend fun addTrashFilePath(pathPair: Pair) { + dataStore.edit { settings -> + val currentPaths = settings[trashFilePathsKey] ?: emptySet() + settings[trashFilePathsKey] = currentPaths + "${pathPair.first}||${pathPair.second}" } } - suspend fun subtractTrashSize(size: Long) { - dataStore.edit { preferences -> - preferences[trashSizeKey] = (preferences[trashSizeKey] ?: 0L) - size + suspend fun removeTrashFilePath(originalPath: String) { + dataStore.edit { settings -> + val currentPaths = settings[trashFilePathsKey] ?: emptySet() + val updatedPaths = currentPaths.filterNot { it.startsWith("$originalPath||") }.toSet() + settings[trashFilePathsKey] = updatedPaths } } - suspend fun clearTrashSize() { - dataStore.edit { preferences -> - preferences[trashSizeKey] = 0L + private val trashSizeKey = longPreferencesKey(name = "trash_size") + val trashSize: Flow = dataStore.data.map { preferences -> + preferences[trashSizeKey] ?: 0L + } + + suspend fun addTrashSize(size : Long) { + dataStore.edit { settings -> + val currentSize = settings[trashSizeKey] ?: 0L + settings[trashSizeKey] = currentSize + size } } private val genericFilterKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_GENERIC_FILTER) - val genericFilter: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_GENERIC_FILTER) + val genericFilter : Flow = dataStore.data.map { preferences -> preferences[genericFilterKey] == true } - suspend fun saveGenericFilter(isChecked: Boolean) { + suspend fun saveGenericFilter(isChecked : Boolean) { dataStore.edit { preferences -> preferences[genericFilterKey] = isChecked } } private val deleteEmptyFoldersKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_EMPTY_FOLDERS) - val deleteEmptyFolders: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_EMPTY_FOLDERS) + val deleteEmptyFolders : Flow = dataStore.data.map { preferences -> preferences[deleteEmptyFoldersKey] != false } - suspend fun saveDeleteEmptyFolders(isChecked: Boolean) { + suspend fun saveDeleteEmptyFolders(isChecked : Boolean) { dataStore.edit { preferences -> preferences[deleteEmptyFoldersKey] = isChecked } } private val deleteArchivesKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_ARCHIVES) - val deleteArchives: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_ARCHIVES) + val deleteArchives : Flow = dataStore.data.map { preferences -> preferences[deleteArchivesKey] == true } - suspend fun saveDeleteArchives(isChecked: Boolean) { + suspend fun saveDeleteArchives(isChecked : Boolean) { dataStore.edit { preferences -> preferences[deleteArchivesKey] = isChecked } } private val deleteInvalidMediaKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_INVALID_MEDIA) - val deleteInvalidMedia: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_INVALID_MEDIA) + val deleteInvalidMedia : Flow = dataStore.data.map { preferences -> preferences[deleteInvalidMediaKey] == true } - suspend fun saveDeleteInvalidMedia(isChecked: Boolean) { + suspend fun saveDeleteInvalidMedia(isChecked : Boolean) { dataStore.edit { preferences -> preferences[deleteInvalidMediaKey] = isChecked } } private val deleteCorpseFilesKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_CORPSE_FILES) - val deleteCorpseFiles: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_CORPSE_FILES) + val deleteCorpseFiles : Flow = dataStore.data.map { preferences -> preferences[deleteCorpseFilesKey] == true } - suspend fun saveDeleteCorpseFiles(isChecked: Boolean) { + suspend fun saveDeleteCorpseFiles(isChecked : Boolean) { dataStore.edit { preferences -> preferences[deleteCorpseFilesKey] = isChecked } } private val deleteApkFilesKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_APK_FILES) - val deleteApkFiles: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_APK_FILES) + val deleteApkFiles : Flow = dataStore.data.map { preferences -> preferences[deleteApkFilesKey] != false } - suspend fun saveDeleteApkFiles(isChecked: Boolean) { + suspend fun saveDeleteApkFiles(isChecked : Boolean) { dataStore.edit { preferences -> preferences[deleteApkFilesKey] = isChecked } } private val deleteAudioFilesKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_AUDIO_FILES) - val deleteAudioFiles: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_AUDIO_FILES) + val deleteAudioFiles : Flow = dataStore.data.map { preferences -> preferences[deleteAudioFilesKey] != false } - suspend fun saveDeleteAudioFiles(isChecked: Boolean) { + suspend fun saveDeleteAudioFiles(isChecked : Boolean) { dataStore.edit { preferences -> preferences[deleteAudioFilesKey] = isChecked } } private val deleteVideoFilesKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_VIDEO_FILES) - val deleteVideoFiles: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_VIDEO_FILES) + val deleteVideoFiles : Flow = dataStore.data.map { preferences -> preferences[deleteVideoFilesKey] != false } - suspend fun saveDeleteVideoFiles(isChecked: Boolean) { + suspend fun saveDeleteVideoFiles(isChecked : Boolean) { dataStore.edit { preferences -> preferences[deleteVideoFilesKey] = isChecked } } private val deleteImageFilesKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_IMAGE_FILES) - val deleteImageFiles: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_DELETE_IMAGE_FILES) + val deleteImageFiles : Flow = dataStore.data.map { preferences -> preferences[deleteImageFilesKey] != false } - suspend fun saveDeleteImageFiles(isChecked: Boolean) { + suspend fun saveDeleteImageFiles(isChecked : Boolean) { dataStore.edit { preferences -> preferences[deleteImageFilesKey] = isChecked } } private val clipboardCleanKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_CLIPBOARD_CLEAN) - val clipboardClean: Flow = dataStore.data.map { preferences -> + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_CLIPBOARD_CLEAN) + val clipboardClean : Flow = dataStore.data.map { preferences -> preferences[clipboardCleanKey] == true } - suspend fun saveClipboardClean(isChecked: Boolean) { + suspend fun saveClipboardClean(isChecked : Boolean) { dataStore.edit { preferences -> preferences[clipboardCleanKey] = isChecked } @@ -327,12 +347,12 @@ class DataStore(context: Context) { // Usage and Diagnostics private val usageAndDiagnosticsKey = - booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_USAGE_AND_DIAGNOSTICS) - val usageAndDiagnostics: Flow = dataStore.data.map { preferences -> - preferences[usageAndDiagnosticsKey] ?: !BuildConfig.DEBUG + booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_USAGE_AND_DIAGNOSTICS) + val usageAndDiagnostics : Flow = dataStore.data.map { preferences -> + preferences[usageAndDiagnosticsKey] ?: ! BuildConfig.DEBUG } - suspend fun saveUsageAndDiagnostics(isChecked: Boolean) { + suspend fun saveUsageAndDiagnostics(isChecked : Boolean) { dataStore.edit { preferences -> preferences[usageAndDiagnosticsKey] = isChecked } @@ -341,10 +361,10 @@ class DataStore(context: Context) { // Ads private val adsKey = booleanPreferencesKey(name = DataStoreNamesConstants.DATA_STORE_ADS) val ads : Flow = dataStore.data.map { preferences -> - preferences[adsKey] ?: !BuildConfig.DEBUG + preferences[adsKey] ?: ! BuildConfig.DEBUG } - suspend fun saveAds(isChecked: Boolean) { + suspend fun saveAds(isChecked : Boolean) { dataStore.edit { preferences -> preferences[adsKey] = isChecked } 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 30e2c80..29723f1 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 @@ -150,7 +150,9 @@ class HomeRepository( * @param onSuccess Callback function to be invoked after successful restore. */ suspend fun restoreFromTrash(filesToRestore : Set , onSuccess : () -> Unit) { + println("Cleaner for Android -> restoreFromTrash - normal repo") withContext(Dispatchers.IO) { + println("Cleaner for Android -> we are on IO") restoreFromTrash(filesToRestore) withContext(Dispatchers.Main) { onSuccess() 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 e3c4949..076e355 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 @@ -8,6 +8,7 @@ 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 import com.d4rk.cleaner.utils.cleaning.StorageUtils +import kotlinx.coroutines.flow.first import java.io.File import java.util.concurrent.TimeUnit import kotlin.coroutines.resume @@ -35,7 +36,7 @@ abstract class HomeRepositoryImplementation( } } - fun calculateDaysSince(timestamp: Long): Int { + fun calculateDaysSince(timestamp : Long) : Int { if (timestamp == 0L) return 0 val currentTime = System.currentTimeMillis() @@ -79,11 +80,6 @@ abstract class HomeRepositoryImplementation( } } - /** - * Moves the given files to the trash directory. - * - * @param filesToMove The list of files to move to the trash. - */ suspend fun moveToTrash(filesToMove : List) { if (! trashDir.exists()) { trashDir.mkdirs() @@ -92,10 +88,15 @@ abstract class HomeRepositoryImplementation( filesToMove.forEach { file -> if (file.exists()) { val originalPath = file.absolutePath - val destination = File(trashDir , "${file.name}_${file.lastModified()}") + val destination = File(trashDir , file.name) + + println("Cleaner for Android -> Moving file: ${file.absolutePath} to ${destination.absolutePath}") if (file.renameTo(destination)) { - dataStore.addTrashFilePath(originalPath) + println("Cleaner for Android -> File moved successfully") + dataStore.addTrashFileOriginalPath(originalPath) + dataStore.addTrashFilePath(originalPath to destination.absolutePath) + MediaScannerConnection.scanFile( application , arrayOf( @@ -103,34 +104,78 @@ abstract class HomeRepositoryImplementation( ) , null , null ) } + else { + println("Cleaner for Android -> File move failed") + } + } + else { + println("Cleaner for Android -> File does not exist: ${file.absolutePath}") } } } suspend fun restoreFromTrash(filesToRestore : Set) { + println("Cleaner for Android -> impl logic called") + val originalPaths = dataStore.trashFileOriginalPaths.first() + println("Cleaner for Android -> Original paths from DataStore: $originalPaths") + val trashToOriginalMap = + dataStore.trashFilePaths.first().associate { it.second to it.first } + println("Cleaner for Android -> trashToOriginalMap: $trashToOriginalMap") + filesToRestore.forEach { file -> + println("Cleaner for Android -> Attempting to restore: ${file.absolutePath}") + if (file.exists()) { - 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 (file.renameTo(destinationFile)) { - MediaScannerConnection.scanFile( - application , arrayOf( - destinationFile.absolutePath , file.absolutePath - ) , null , null - ) - dataStore.removeTrashFilePath(originalPath) - } + val originalPath = originalPaths.firstOrNull { File(it).name == file.name } + println("Cleaner for Android -> Original path found: $originalPath") + + if (originalPath != null) { + val destinationFile = File(originalPath) + val destinationParent = destinationFile.parentFile + + if (destinationParent?.exists() == false) { + destinationParent.mkdirs() + } + + println("Cleaner for Android -> Restoring to: ${destinationFile.absolutePath}") + + if (file.renameTo(destinationFile)) { + println("Cleaner for Android -> File restored successfully") + dataStore.removeTrashFileOriginalPath(originalPath) + dataStore.removeTrashFilePath(originalPath) + MediaScannerConnection.scanFile( + application , arrayOf( + destinationFile.absolutePath , file.absolutePath + ) , null , null + ) + } + else { + println("Cleaner for Android -> File restore failed. Check if the file already exists or there is a permission issue.") // More informative message } } + else { + println("Cleaner for Android -> No original path found for ${file.name}. Restoring to Downloads.") + val downloadsDir = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + val destinationFile = File(downloadsDir , file.name) + + if (file.renameTo(destinationFile)) { + println("Cleaner for Android -> File restored to Downloads successfully") + + MediaScannerConnection.scanFile( + application , + arrayOf(destinationFile.absolutePath , file.absolutePath) , + null , + null + ) + } + else { + println("Cleaner for Android -> File restore to Downloads failed") + } + } + } + else { + println("Cleaner for Android -> File does not exist in trash: ${file.absolutePath}") } } } diff --git a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/MainViewModel.kt b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/MainViewModel.kt index f1f1c79..d87d47d 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/MainViewModel.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/MainViewModel.kt @@ -12,6 +12,7 @@ import com.d4rk.cleaner.ui.screens.main.repository.MainRepository import com.d4rk.cleaner.ui.screens.startup.StartupActivity import com.d4rk.cleaner.ui.viewmodel.BaseViewModel import com.d4rk.cleaner.utils.IntentUtils +import com.d4rk.cleaner.utils.cleaning.StorageUtils import com.google.android.play.core.appupdate.AppUpdateManager import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -25,8 +26,8 @@ class MainViewModel(application : Application) : BaseViewModel(application) { fun loadTrashSize() { viewModelScope.launch(coroutineExceptionHandler) { - repository.getTrashSize { trashSize -> - _uiState.update { it.copy(trashSize = trashSize) } + repository.dataStore.trashSize.collect { trashSize -> + _uiState.update { it.copy(trashSize = StorageUtils.formatSize(trashSize)) } } } } diff --git a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/repository/MainRepository.kt b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/repository/MainRepository.kt index adfcde9..ae5afe7 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/repository/MainRepository.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/main/repository/MainRepository.kt @@ -74,17 +74,4 @@ class MainRepository(val dataStore : DataStore , application : Application) : } } } - - /** - * Retrieves the current trash size from DataStore and formats it. - */ - suspend fun getTrashSize(onSuccess : (String) -> Unit) { - withContext(Dispatchers.IO) { - val size = dataStore.trashSize.first() - val formattedSize = StorageUtils.formatSize(size) - withContext(Dispatchers.Main) { - onSuccess(formattedSize) - } - } - } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/trash/TrashViewModel.kt b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/trash/TrashViewModel.kt index ce58af0..3e3a9a3 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/trash/TrashViewModel.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/ui/screens/trash/TrashViewModel.kt @@ -42,7 +42,12 @@ class TrashViewModel(application : Application) : BaseViewModel(application) { fun restoreFromTrash() { viewModelScope.launch(coroutineExceptionHandler) { val filesToRestore = _uiState.value.fileSelectionStates.filter { it.value }.keys + println("Cleaner for Android -> restoreFromTrash() called") // Log the function call + + println("Cleaner for Android -> Files to restore: $filesToRestore") // Log the files to restore + showLoading() + repository.restoreFromTrash(filesToRestore) { loadTrashItems() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 97f77a9..f2871eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,7 @@ constraintlayoutCompose = "1.0.1" coreSplashscreen = "1.0.1" datastoreCore = "1.1.1" firebaseBom = "33.4.0" +kotlinxSerializationJson = "1.7.3" lifecycle = "2.8.6" volley = "1.2.1" kotlin = "2.0.10" @@ -73,6 +74,7 @@ androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "composeUi" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "composeUi" } androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "composeMaterial3" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }