-
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added the new app manager for the app
- Loading branch information
Mihai-Cristian Condrea
committed
Jun 23, 2024
1 parent
b49e46f
commit b9f1743
Showing
3 changed files
with
312 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
162 changes: 162 additions & 0 deletions
162
app/src/main/kotlin/com/d4rk/cleaner/ui/memory/MemoryManagerComposable.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
package com.d4rk.cleaner.ui.memory | ||
|
||
import androidx.compose.animation.AnimatedVisibility | ||
import androidx.compose.animation.expandVertically | ||
import androidx.compose.animation.fadeIn | ||
import androidx.compose.animation.fadeOut | ||
import androidx.compose.animation.shrinkVertically | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.lazy.LazyColumn | ||
import androidx.compose.foundation.lazy.items | ||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.automirrored.filled.ArrowRight | ||
import androidx.compose.material.icons.filled.ArrowDropDown | ||
import androidx.compose.material.icons.filled.Info | ||
import androidx.compose.material.icons.outlined.Android | ||
import androidx.compose.material.icons.outlined.Apps | ||
import androidx.compose.material.icons.outlined.Download | ||
import androidx.compose.material.icons.outlined.FolderOpen | ||
import androidx.compose.material.icons.outlined.Image | ||
import androidx.compose.material.icons.outlined.MusicNote | ||
import androidx.compose.material3.Card | ||
import androidx.compose.material3.CardDefaults | ||
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.IconButton | ||
import androidx.compose.material3.LinearProgressIndicator | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.collectAsState | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.text.font.FontWeight | ||
import androidx.compose.ui.unit.dp | ||
import androidx.lifecycle.viewmodel.compose.viewModel | ||
|
||
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 , | ||
"Other Files" to Icons.Outlined.FolderOpen | ||
) | ||
|
||
@Composable | ||
fun MemoryManagerComposable() { | ||
val viewModel = viewModel<MemoryManagerViewModel>() | ||
val storageInfo by viewModel.storageInfo.collectAsState() | ||
val context = LocalContext.current | ||
|
||
var listExpanded by remember { mutableStateOf(true) } | ||
|
||
LaunchedEffect(Unit) { | ||
viewModel.updateStorageInfo(context) | ||
} | ||
|
||
Column { | ||
StorageInfoCard(storageInfo = storageInfo) | ||
Spacer(modifier = Modifier.height(16.dp)) | ||
|
||
Row( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding(horizontal = 16.dp) , | ||
) { | ||
AnimatedVisibility( | ||
visible = listExpanded , | ||
enter = fadeIn() + expandVertically() , | ||
exit = fadeOut() + shrinkVertically() | ||
) { | ||
LazyColumn( | ||
modifier = Modifier.padding(end = 8.dp) | ||
) { | ||
items(storageInfo.storageBreakdown.entries.toList()) { entry -> | ||
StorageBreakdownItem(icon = entry.key , size = entry.value) | ||
} | ||
} | ||
} | ||
|
||
} | ||
IconButton(onClick = { listExpanded = ! listExpanded }) { | ||
Icon( | ||
imageVector = if (listExpanded) Icons.Default.ArrowDropDown else Icons.AutoMirrored.Filled.ArrowRight , | ||
contentDescription = if (listExpanded) "Collapse" else "Expand" | ||
) | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
fun StorageInfoCard(storageInfo : StorageInfo) { | ||
Card( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding(horizontal = 16.dp) , | ||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) | ||
) { | ||
Column(modifier = Modifier.padding(16.dp)) { | ||
Text( | ||
text = "Storage Information" , | ||
style = MaterialTheme.typography.headlineSmall , | ||
fontWeight = FontWeight.Bold | ||
) | ||
Spacer(modifier = Modifier.height(8.dp)) | ||
LinearProgressIndicator( | ||
progress = { storageInfo.usedStorage.toFloat() / storageInfo.totalStorage.toFloat() } , | ||
modifier = Modifier | ||
.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) | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
fun StorageBreakdownItem(icon : String , size : Long) { | ||
Row( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding(vertical = 8.dp) , | ||
verticalAlignment = Alignment.CenterVertically | ||
) { | ||
Icon( | ||
imageVector = StorageIcons[icon] ?: Icons.Filled.Info , | ||
contentDescription = icon , | ||
tint = MaterialTheme.colorScheme.primary | ||
) | ||
Spacer(modifier = Modifier.padding(horizontal = 8.dp)) | ||
Column { | ||
Text( | ||
text = icon , | ||
style = MaterialTheme.typography.bodyMedium , | ||
fontWeight = FontWeight.Bold | ||
) | ||
Text(text = formatSize(size) , style = MaterialTheme.typography.bodySmall) | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
fun StorageInfoText(label : String , size : Long) { | ||
Text(text = "$label ${formatSize(size)}" , style = MaterialTheme.typography.bodyMedium) | ||
} |
148 changes: 148 additions & 0 deletions
148
app/src/main/kotlin/com/d4rk/cleaner/ui/memory/MemoryManagerViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package com.d4rk.cleaner.ui.memory | ||
|
||
import android.app.usage.StorageStatsManager | ||
import android.content.Context | ||
import android.os.Environment | ||
import android.os.StatFs | ||
import android.os.storage.StorageManager | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.withContext | ||
import java.io.File | ||
import java.util.UUID | ||
import kotlin.math.log10 | ||
import kotlin.math.pow | ||
|
||
class MemoryManagerViewModel : ViewModel() { | ||
private val _storageInfo = MutableStateFlow(StorageInfo()) | ||
val storageInfo : StateFlow<StorageInfo> = _storageInfo.asStateFlow() | ||
|
||
fun updateStorageInfo(context : Context) { | ||
viewModelScope.launch { | ||
_storageInfo.value = getStorageInfo(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 | ||
) | ||
} | ||
|
||
|
||
private fun getInternalStorageInfo(): InternalStorageInfo { | ||
val statFs = StatFs(Environment.getDataDirectory().path) | ||
val blockSizeBytes = statFs.blockSizeLong | ||
val totalBlocks = statFs.blockCountLong | ||
val availableBlocks = statFs.availableBlocksLong | ||
|
||
val totalStorage = totalBlocks * blockSizeBytes | ||
val freeStorage = availableBlocks * blockSizeBytes | ||
val usedStorage = totalStorage - freeStorage | ||
|
||
return InternalStorageInfo(totalStorage, freeStorage, usedStorage) | ||
} | ||
|
||
private fun getStorageBreakdown(context: Context): Map<String, Long> { | ||
val breakdown = mutableMapOf<String, Long>() | ||
val externalStoragePath = Environment.getExternalStorageDirectory().absolutePath | ||
|
||
breakdown["Installed Apps"] = getInstalledAppsSize(context) | ||
breakdown["System"] = getDirectorySize(Environment.getRootDirectory()) | ||
breakdown["Music"] = getDirectorySize(File(externalStoragePath, "Music")) | ||
breakdown["Images"] = getDirectorySize(File(externalStoragePath, "DCIM")) + | ||
getDirectorySize(File(externalStoragePath, "Pictures")) | ||
breakdown["Documents"] = getDirectorySize(File(externalStoragePath, "Documents")) | ||
breakdown["Downloads"] = getDirectorySize(File(externalStoragePath, "Download")) // Use "Download" | ||
breakdown["Other Files"] = getOtherFilesSize(breakdown) | ||
|
||
return breakdown | ||
} | ||
|
||
private fun getInstalledAppsSize(context: Context): Long { | ||
val packageManager = context.packageManager | ||
val installedApps = packageManager.getInstalledApplications(0) | ||
var installedAppsSize = 0L | ||
for (app in installedApps) { | ||
installedAppsSize += getApkSize(context, app.packageName) | ||
} | ||
return installedAppsSize | ||
} | ||
|
||
private fun getApkSize(context: Context, packageName: String): Long { | ||
return try { | ||
context.packageManager.getApplicationInfo( | ||
packageName, | ||
0 | ||
).sourceDir.let { File(it).length() } | ||
} catch (e: Exception) { | ||
0L | ||
} | ||
} | ||
|
||
private fun getDirectorySize(directory: File?): Long { | ||
if (directory == null || !directory.exists() || !directory.isDirectory) return 0 | ||
var size = 0L | ||
val files = directory.listFiles() | ||
if (files != null) { | ||
for (file in files) { | ||
size += if (file.isDirectory) { | ||
getDirectorySize(file) | ||
} else { | ||
file.length() | ||
} | ||
} | ||
} | ||
return size | ||
} | ||
|
||
private fun getOtherFilesSize(breakdown : MutableMap<String , Long>): Long { | ||
val totalUsedStorage = getInternalStorageInfo().usedStorage | ||
val calculatedSize = breakdown.values.sum() | ||
return totalUsedStorage - calculatedSize | ||
} | ||
} | ||
|
||
data class InternalStorageInfo( | ||
val totalStorage: Long, | ||
val freeStorage: Long, | ||
val usedStorage: Long | ||
) | ||
|
||
data class StorageInfo( | ||
val totalStorage: Long = 0, | ||
val freeStorage: Long = 0, | ||
val usedStorage: Long = 0, | ||
val storageBreakdown: Map<String, Long> = emptyMap() | ||
) | ||
|
||
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]) | ||
} |