From 7a1f7bc4e077bae6f79b453f904869090d8a3779 Mon Sep 17 00:00:00 2001 From: D4rK7355608 Date: Sat, 29 Jun 2024 00:33:01 +0300 Subject: [PATCH] Improved the code structure --- app/build.gradle.kts | 1 + .../ui/memory/MemoryManagerComposable.kt | 181 +++++++++--------- .../ui/memory/MemoryManagerViewModel.kt | 131 ++++++++++--- gradle/libs.versions.toml | 8 +- 4 files changed, 199 insertions(+), 122 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f3c21cf..ba7549a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -121,6 +121,7 @@ dependencies { implementation(libs.androidx.lifecycle.process) implementation(libs.androidx.lifecycle.viewmodel.ktx) implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.lifecycle.runtime.compose) // Google implementation(libs.play.services.ads) diff --git a/app/src/main/kotlin/com/d4rk/cleaner/ui/memory/MemoryManagerComposable.kt b/app/src/main/kotlin/com/d4rk/cleaner/ui/memory/MemoryManagerComposable.kt index 83a5002..776837d 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/ui/memory/MemoryManagerComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/ui/memory/MemoryManagerComposable.kt @@ -64,12 +64,12 @@ import com.d4rk.cleaner.data.model.StorageInfo import kotlin.math.absoluteValue val StorageIcons = mapOf( - "Installed Apps" to Icons.Outlined.Apps , - "System" to Icons.Outlined.Android , - "Music" to Icons.Outlined.MusicNote , - "Images" to Icons.Outlined.Image , - "Documents" to Icons.Outlined.FolderOpen , - "Downloads" to Icons.Outlined.Download , + "Installed Apps" to Icons.Outlined.Apps, + "System" to Icons.Outlined.Android, + "Music" to Icons.Outlined.MusicNote, + "Images" to Icons.Outlined.Image, + "Documents" to Icons.Outlined.FolderOpen, + "Downloads" to Icons.Outlined.Download, "Other Files" to Icons.Outlined.FolderOpen ) @@ -96,31 +96,32 @@ fun MemoryManagerComposable() { modifier = Modifier.fillMaxSize() ) { HorizontalPager( - state = pagerState , modifier = Modifier - .fillMaxWidth() + state = pagerState, modifier = Modifier + .fillMaxWidth() ) { page -> val pageOffset = (page - pagerState.currentPage).absoluteValue val scale by animateFloatAsState( targetValue = lerp( - start = 0.9f , stop = 1f , fraction = 1f - pageOffset.coerceIn(0 , 1) - ) , label = "" + start = 0.9f, stop = 1f, fraction = 1f - pageOffset.coerceIn(0, 1) + ), label = "" ) - Card(modifier = Modifier.padding(16.dp) - .graphicsLayer { - scaleX = scale - scaleY = scale - alpha = lerp( - start = 0.5f , stop = 1f , fraction = 1f - pageOffset.coerceIn(0 , 1) - ) - } - .offset { - val pageOffsetState = (page - pagerState.currentPage) - IntOffset( - x = (10.dp * pageOffsetState.toFloat()).roundToPx() , y = 0 - ) - } - .fillMaxWidth()) { + Card(modifier = Modifier + .padding(16.dp) + .graphicsLayer { + scaleX = scale + scaleY = scale + alpha = lerp( + start = 0.5f, stop = 1f, fraction = 1f - pageOffset.coerceIn(0, 1) + ) + } + .offset { + val pageOffsetState = (page - pagerState.currentPage) + IntOffset( + x = (10.dp * pageOffsetState.toFloat()).roundToPx(), y = 0 + ) + } + .fillMaxWidth()) { when (page) { 0 -> StorageInfoCard(storageInfo) 1 -> RamInfoCard(ramInfo) @@ -128,9 +129,9 @@ fun MemoryManagerComposable() { } } DotsIndicator( - modifier = Modifier.align(Alignment.CenterHorizontally) , - totalDots = 2 , - selectedIndex = pagerState.currentPage , + modifier = Modifier.align(Alignment.CenterHorizontally), + totalDots = 2, + selectedIndex = pagerState.currentPage, dotSize = 6.dp ) @@ -138,15 +139,15 @@ fun MemoryManagerComposable() { Row( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) + .fillMaxWidth() + .padding(horizontal = 16.dp) ) { Box(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.animateContentSize()) { if (listExpanded) { LazyColumn { items(storageInfo.storageBreakdown.entries.toList()) { entry -> - StorageBreakdownItem(icon = entry.key , size = entry.value) + StorageBreakdownItem(icon = entry.key, size = entry.value) } } } @@ -154,9 +155,9 @@ fun MemoryManagerComposable() { } Spacer(modifier = Modifier.width(8.dp)) - IconButton(onClick = { listExpanded = ! listExpanded }) { + IconButton(onClick = { listExpanded = !listExpanded }) { Icon( - imageVector = if (listExpanded) Icons.Outlined.ArrowDropDown else Icons.AutoMirrored.Filled.ArrowLeft , + imageVector = if (listExpanded) Icons.Outlined.ArrowDropDown else Icons.AutoMirrored.Filled.ArrowLeft, contentDescription = if (listExpanded) "Collapse" else "Expand" ) } @@ -165,49 +166,49 @@ fun MemoryManagerComposable() { } @Composable -fun StorageInfoCard(storageInfo : StorageInfo) { +fun StorageInfoCard(storageInfo: StorageInfo) { Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Storage Information" , - style = MaterialTheme.typography.headlineSmall , + text = "Storage Information", + style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(8.dp)) LinearProgressIndicator( - progress = { storageInfo.usedStorage.toFloat() / storageInfo.totalStorage.toFloat() } , + progress = { storageInfo.usedStorage.toFloat() / storageInfo.totalStorage.toFloat() }, modifier = Modifier - .fillMaxWidth() - .height(8.dp) , - color = MaterialTheme.colorScheme.primary , + .fillMaxWidth() + .height(8.dp), + color = MaterialTheme.colorScheme.primary, ) Spacer(modifier = Modifier.height(8.dp)) - StorageInfoText(label = "Used:" , size = storageInfo.usedStorage) - StorageInfoText(label = "Free:" , size = storageInfo.freeStorage) - StorageInfoText(label = "Total:" , size = storageInfo.totalStorage) + StorageInfoText(label = "Used:", size = storageInfo.usedStorage) + StorageInfoText(label = "Free:", size = storageInfo.freeStorage) + StorageInfoText(label = "Total:", size = storageInfo.totalStorage) } } @Composable -fun StorageBreakdownItem(icon : String , size : Long) { +fun StorageBreakdownItem(icon: String, size: Long) { Card( modifier = Modifier - .fillMaxWidth() - .padding(vertical = 4.dp) , + .fillMaxWidth() + .padding(vertical = 4.dp), ) { Row( modifier = Modifier - .fillMaxWidth() - .padding(16.dp) , + .fillMaxWidth() + .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Card( - modifier = Modifier.size(48.dp) , - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer) , + modifier = Modifier.size(48.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer), ) { - Box(modifier = Modifier.fillMaxSize() , contentAlignment = Alignment.Center) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Icon( - imageVector = StorageIcons[icon] ?: Icons.Filled.Info , - contentDescription = icon , + imageVector = StorageIcons[icon] ?: Icons.Filled.Info, + contentDescription = icon, tint = MaterialTheme.colorScheme.onPrimaryContainer ) } @@ -217,78 +218,78 @@ fun StorageBreakdownItem(icon : String , size : Long) { Column { Text( - text = icon , - style = MaterialTheme.typography.bodyMedium , + text = icon, + style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold ) - Text(text = formatSize(size) , style = MaterialTheme.typography.bodySmall) + Text(text = formatSize(size), style = MaterialTheme.typography.bodySmall) } } } } @Composable -fun StorageInfoText(label : String , size : Long) { - Text(text = "$label ${formatSize(size)}" , style = MaterialTheme.typography.bodyMedium) +fun StorageInfoText(label: String, size: Long) { + Text(text = "$label ${formatSize(size)}", style = MaterialTheme.typography.bodyMedium) } @Composable -fun RamInfoCard(ramInfo : RamInfo) { +fun RamInfoCard(ramInfo: RamInfo) { Column(modifier = Modifier.padding(16.dp)) { Text( - "RAM Information" , - style = MaterialTheme.typography.headlineSmall , + "RAM Information", + style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(8.dp)) StorageProgressBar( StorageInfo( - totalStorage = ramInfo.totalRam , - usedStorage = ramInfo.usedRam , + totalStorage = ramInfo.totalRam, + usedStorage = ramInfo.usedRam, freeStorage = ramInfo.availableRam ) ) Spacer(modifier = Modifier.height(8.dp)) - StorageInfoText(label = "Used RAM:" , size = ramInfo.usedRam) - StorageInfoText(label = "Free RAM:" , size = ramInfo.availableRam) - StorageInfoText(label = "Total RAM:" , size = ramInfo.totalRam) + StorageInfoText(label = "Used RAM:", size = ramInfo.usedRam) + StorageInfoText(label = "Free RAM:", size = ramInfo.availableRam) + StorageInfoText(label = "Total RAM:", size = ramInfo.totalRam) } } @Composable -fun StorageProgressBar(storageInfo : StorageInfo) { +fun StorageProgressBar(storageInfo: StorageInfo) { val progress = - (storageInfo.usedStorage.toFloat() / storageInfo.totalStorage.toFloat()).coerceIn( - 0f , - 1f - ) + (storageInfo.usedStorage.toFloat() / storageInfo.totalStorage.toFloat()).coerceIn( + 0f, + 1f + ) LinearProgressIndicator( - progress = { progress } , + progress = { progress }, modifier = Modifier - .fillMaxWidth() - .height(8.dp) , - color = MaterialTheme.colorScheme.primary , + .fillMaxWidth() + .height(8.dp), + color = MaterialTheme.colorScheme.primary, ) } @Composable fun DotsIndicator( - modifier : Modifier = Modifier , - totalDots : Int , - selectedIndex : Int , - selectedColor : Color = MaterialTheme.colorScheme.primary , - unSelectedColor : Color = Color.Gray , - dotSize : Dp , + modifier: Modifier = Modifier, + totalDots: Int, + selectedIndex: Int, + selectedColor: Color = MaterialTheme.colorScheme.primary, + unSelectedColor: Color = Color.Gray, + dotSize: Dp, ) { LazyRow( modifier = modifier - .wrapContentWidth() - .wrapContentHeight() + .wrapContentWidth() + .wrapContentHeight() ) { items(totalDots) { index -> IndicatorDot( - color = if (index == selectedIndex) selectedColor else unSelectedColor , + color = if (index == selectedIndex) selectedColor else unSelectedColor, size = dotSize ) @@ -301,14 +302,14 @@ fun DotsIndicator( @Composable fun IndicatorDot( - modifier : Modifier = Modifier , - size : Dp , - color : Color , + modifier: Modifier = Modifier, + size: Dp, + color: Color, ) { Box( modifier = modifier - .size(size) - .clip(CircleShape) - .background(color) + .size(size) + .clip(CircleShape) + .background(color) ) } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cleaner/ui/memory/MemoryManagerViewModel.kt b/app/src/main/kotlin/com/d4rk/cleaner/ui/memory/MemoryManagerViewModel.kt index cba3223..586f575 100644 --- a/app/src/main/kotlin/com/d4rk/cleaner/ui/memory/MemoryManagerViewModel.kt +++ b/app/src/main/kotlin/com/d4rk/cleaner/ui/memory/MemoryManagerViewModel.kt @@ -18,55 +18,79 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File +import java.util.Locale import java.util.UUID import kotlin.math.log10 import kotlin.math.pow +/** + * ViewModel for managing and providing information about device memory (RAM and storage). + */ class MemoryManagerViewModel : ViewModel() { private val _storageInfo = MutableStateFlow(StorageInfo()) - val storageInfo : StateFlow = _storageInfo.asStateFlow() + val storageInfo: StateFlow = _storageInfo.asStateFlow() private val _ramInfo = MutableStateFlow(RamInfo()) val ramInfo: StateFlow = _ramInfo.asStateFlow() - fun updateStorageInfo(context : Context) { + /** + * Updates the storage information by fetching the latest data from the device. + * + * @param context The application context. + */ + fun updateStorageInfo(context: Context) { viewModelScope.launch { _storageInfo.value = getStorageInfo(context) } } + /** + * Updates the RAM information by fetching the latest data from the device. + * + * @param context The application context. + */ fun updateRamInfo(context: Context) { viewModelScope.launch { _ramInfo.value = getRamInfo(context) } } - private suspend fun getStorageInfo(context : Context) : StorageInfo = - withContext(Dispatchers.IO) { - val storageManager = - context.getSystemService(Context.STORAGE_SERVICE) as StorageManager - val storageStatsManager = - context.getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager - val storageVolume = storageManager.primaryStorageVolume - val totalSize : Long - val usedSize : Long - val freeSize : Long - val uuidStr = storageVolume.uuid - val uuid : UUID = if (uuidStr == null) StorageManager.UUID_DEFAULT - else UUID.fromString(uuidStr) - totalSize = storageStatsManager.getTotalBytes(uuid) - freeSize = storageStatsManager.getFreeBytes(uuid) - usedSize = totalSize - freeSize - val storageBreakdown = getStorageBreakdown(context) - StorageInfo( - totalStorage = totalSize , - freeStorage = freeSize , - usedStorage = usedSize , - storageBreakdown = storageBreakdown - ) - } - + /** + * Fetches the current storage information from the device. + * + * @param context The application context. + * @return A [StorageInfo] object containing details about total, free, and used storage. + */ + private suspend fun getStorageInfo(context: Context): StorageInfo = + withContext(Dispatchers.IO) { + val storageManager = + context.getSystemService(Context.STORAGE_SERVICE) as StorageManager + val storageStatsManager = + context.getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager + val storageVolume = storageManager.primaryStorageVolume + val totalSize: Long + val usedSize: Long + val freeSize: Long + val uuidStr = storageVolume.uuid + val uuid: UUID = if (uuidStr == null) StorageManager.UUID_DEFAULT + else UUID.fromString(uuidStr) + totalSize = storageStatsManager.getTotalBytes(uuid) + freeSize = storageStatsManager.getFreeBytes(uuid) + usedSize = totalSize - freeSize + val storageBreakdown = getStorageBreakdown(context) + StorageInfo( + totalStorage = totalSize, + freeStorage = freeSize, + usedStorage = usedSize, + storageBreakdown = storageBreakdown + ) + } + /** + * Retrieves information about the internal storage. + * + * @return An [InternalStorageInfo] object containing details about total, free, and used internal storage. + */ private fun getInternalStorageInfo(): InternalStorageInfo { val statFs = StatFs(Environment.getDataDirectory().path) val blockSizeBytes = statFs.blockSizeLong @@ -80,6 +104,12 @@ class MemoryManagerViewModel : ViewModel() { return InternalStorageInfo(totalStorage, freeStorage, usedStorage) } + /** + * Calculates a breakdown of storage usage by different categories. + * + * @param context The application context. + * @return A map containing storage usage by category (e.g., "Installed Apps", "Music", etc.). + */ private fun getStorageBreakdown(context: Context): Map { val breakdown = mutableMapOf() val externalStoragePath = Environment.getExternalStorageDirectory().absolutePath @@ -96,6 +126,12 @@ class MemoryManagerViewModel : ViewModel() { return breakdown } + /** + * Calculates the total size of installed apps. + * + * @param context The application context. + * @return The total size of installed apps in bytes. + */ private fun getInstalledAppsSize(context: Context): Long { val packageManager = context.packageManager val installedApps = packageManager.getInstalledApplications(0) @@ -106,6 +142,13 @@ class MemoryManagerViewModel : ViewModel() { return installedAppsSize } + /** + * Retrieves the size of an APK file for a given package name. + * + * @param context The application context. + * @param packageName The package name of the app. + * @return The size of the APK file in bytes. + */ private fun getApkSize(context: Context, packageName: String): Long { return try { context.packageManager.getApplicationInfo( @@ -117,6 +160,12 @@ class MemoryManagerViewModel : ViewModel() { } } + /** + * Calculates the size of a directory and its contents. + * + * @param directory The directory to calculate the size for. + * @return The size of the directory in bytes. + */ private fun getDirectorySize(directory: File?): Long { if (directory == null || !directory.exists() || !directory.isDirectory) return 0 var size = 0L @@ -133,12 +182,25 @@ class MemoryManagerViewModel : ViewModel() { return size } - private fun getOtherFilesSize(breakdown : MutableMap): Long { + /** + * Calculates the size of "other files" by subtracting the calculated sizes of known categories + * from the total used storage. + * + * @param breakdown The current storage breakdown map. + * @return The size of "other files" in bytes. + */ + private fun getOtherFilesSize(breakdown: MutableMap): Long { val totalUsedStorage = getInternalStorageInfo().usedStorage val calculatedSize = breakdown.values.sum() return totalUsedStorage - calculatedSize } + /** + * Fetches the current RAM information from the device. + * + * @param context The application context. + * @return A [RamInfo] object containing details about total, available, and used RAM. + */ private fun getRamInfo(context: Context): RamInfo { val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val memoryInfo = ActivityManager.MemoryInfo() @@ -151,9 +213,20 @@ class MemoryManagerViewModel : ViewModel() { } } +/** + * Formats a file size in bytes to a human-readable string (e.g., "128 MB"). + * + * @param size The file size in bytes. + * @return A formatted string representing the file size. + */ fun formatSize(size: Long): String { if (size <= 0) return "0 B" val units = arrayOf("B", "KB", "MB", "GB", "TB") val digitGroups = (log10(size.toDouble()) / log10(1024.0)).toInt() - return String.format("%.2f %s", size / 1024.0.pow(digitGroups.toDouble()), units[digitGroups]) // FIXME: Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead + return String.format( + Locale.US, + "%.2f %s", + size / 1024.0.pow(digitGroups.toDouble()), + units[digitGroups] + ) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 17f65d4..29f9018 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,12 +10,13 @@ coreSplashscreen = "1.0.1" datastoreCore = "1.1.1" firebaseBom = "32.8.1" glide = "4.16.0" +lifecycleRuntimeCompose = "2.8.2" volley = "1.2.1" kotlin = "1.9.23" coreKtx = "1.13.1" junit = "4.13.2" -junitVersion = "1.2.0" -espressoCore = "3.6.0" +junitVersion = "1.2.1" +espressoCore = "3.6.1" kotlinxCoroutinesAndroid = "1.8.1" lifecycleRuntimeKtx = "2.8.2" activityCompose = "1.9.0" @@ -27,7 +28,7 @@ google-firebase-crashlytics = "3.0.2" material = "1.12.0" multidex = "2.0.1" navigationCompose = "2.7.7" -playServicesAds = "23.1.0" +playServicesAds = "23.2.0" playServicesOssLicenses = "17.1.0" reviewKtx = "2.0.1" workRuntimeKtx = "2.9.0" @@ -45,6 +46,7 @@ androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" } androidx-lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "lifecycleRuntimeKtx" } androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleRuntimeKtx" } androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycleRuntimeKtx" } +androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" } androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleRuntimeKtx" } androidx-multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" }