Skip to content

Commit

Permalink
Added the new app manager for the app
Browse files Browse the repository at this point in the history
  • Loading branch information
Mihai-Cristian Condrea committed Jun 23, 2024
1 parent b49e46f commit b9f1743
Show file tree
Hide file tree
Showing 3 changed files with 312 additions and 1 deletion.
3 changes: 2 additions & 1 deletion app/src/main/kotlin/com/d4rk/cleaner/MainComposable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import com.d4rk.cleaner.ui.appmanager.AppManagerComposable
import com.d4rk.cleaner.ui.help.HelpActivity
import com.d4rk.cleaner.ui.home.HomeComposable
import com.d4rk.cleaner.ui.imageoptimizer.ImagePickerActivity
import com.d4rk.cleaner.ui.memory.MemoryManagerComposable
import com.d4rk.cleaner.ui.settings.SettingsActivity
import com.d4rk.cleaner.ui.support.SupportActivity
import com.d4rk.cleaner.ui.whitelist.WhitelistActivity
Expand Down Expand Up @@ -216,7 +217,7 @@ fun MainComposable() {
}
composable(Screen.MemoryManager.route) {
Box(modifier = Modifier.padding(innerPadding)) {
// MemoryManagerComposable()
MemoryManagerComposable()
}
}
}
Expand Down
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)
}
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])
}

0 comments on commit b9f1743

Please sign in to comment.