Skip to content

Commit c4d1b98

Browse files
Added view model for the app manager
1 parent 10c7945 commit c4d1b98

File tree

5 files changed

+127
-38
lines changed

5 files changed

+127
-38
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ android {
1414
applicationId = "com.d4rk.cleaner"
1515
minSdk = 26
1616
targetSdk = 34
17-
versionCode = 81
17+
versionCode = 84
1818
versionName = "2.0.0"
1919
archivesName = "${applicationId}-v${versionName}"
2020
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.d4rk.cleaner.data.model.ui
2+
3+
data class ApkInfo(
4+
val id: Long,
5+
val path: String,
6+
val size: Long
7+
)

app/src/main/kotlin/com/d4rk/cleaner/ui/appmanager/AppManagerComposable.kt

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package com.d4rk.cleaner.ui.appmanager
22

3+
import android.app.Application
34
import android.content.Intent
45
import android.content.pm.ApplicationInfo
5-
import android.content.pm.PackageManager
66
import android.graphics.Bitmap
77
import android.graphics.Canvas
88
import android.graphics.drawable.BitmapDrawable
99
import android.net.Uri
10-
import android.provider.MediaStore
1110
import android.provider.Settings
1211
import androidx.compose.foundation.Image
1312
import androidx.compose.foundation.layout.Box
@@ -33,7 +32,7 @@ import androidx.compose.material3.TabRowDefaults
3332
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
3433
import androidx.compose.material3.Text
3534
import androidx.compose.runtime.Composable
36-
import androidx.compose.runtime.LaunchedEffect
35+
import androidx.compose.runtime.collectAsState
3736
import androidx.compose.runtime.getValue
3837
import androidx.compose.runtime.mutableIntStateOf
3938
import androidx.compose.runtime.mutableStateOf
@@ -49,24 +48,24 @@ import androidx.compose.ui.res.imageResource
4948
import androidx.compose.ui.res.stringResource
5049
import androidx.compose.ui.text.style.TextOverflow
5150
import androidx.compose.ui.unit.dp
51+
import androidx.lifecycle.viewmodel.compose.viewModel
5252
import com.d4rk.cleaner.R
53+
import com.d4rk.cleaner.data.model.ui.ApkInfo
5354
import java.io.File
5455

5556
/**
5657
* Composable function for managing and displaying different app categories.
57-
*
58-
* This composable function displays tabs for "Installed Apps", "System Apps", and "App Install Files".
59-
* Each tab shows corresponding app information based on the selected category.
6058
*/
6159
@Composable
6260
fun AppManagerComposable() {
61+
62+
val viewModel: AppManagerViewModel = viewModel(
63+
factory = AppManagerViewModelFactory(LocalContext.current.applicationContext as Application)
64+
)
6365
val tabs = listOf("Installed Apps", "System Apps", "App Install Files")
6466
var selectedIndex by remember { mutableIntStateOf(0) }
65-
var apps by remember { mutableStateOf(listOf<ApplicationInfo>()) }
66-
val context = LocalContext.current
67-
LaunchedEffect(Unit) {
68-
apps = context.packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
69-
}
67+
val installedApps by viewModel.installedApps.collectAsState()
68+
val apkFiles by viewModel.apkFiles.collectAsState()
7069

7170
Column {
7271
TabRow(
@@ -94,9 +93,11 @@ fun AppManagerComposable() {
9493
}
9594
}
9695
when (selectedIndex) {
97-
0 -> AppsComposable(apps.filter { it.flags and ApplicationInfo.FLAG_SYSTEM == 0 })
98-
1 -> AppsComposable(apps.filter { it.flags and ApplicationInfo.FLAG_SYSTEM != 0 })
99-
2 -> ApksComposable()
96+
0 -> AppsComposable(
97+
apps = installedApps.filter { it.flags and ApplicationInfo.FLAG_SYSTEM == 0 })
98+
1 -> AppsComposable(
99+
apps = installedApps.filter { it.flags and ApplicationInfo.FLAG_SYSTEM != 0 })
100+
2 -> ApksComposable(apkFiles = apkFiles)
100101
}
101102
}
102103
}
@@ -181,7 +182,9 @@ fun AppItemComposable(
181182
}
182183

183184
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
184-
DropdownMenuItem(text = { Text(stringResource(R.string.uninstall)) },
185+
DropdownMenuItem(text = {
186+
Text(stringResource(R.string.uninstall))
187+
},
185188
onClick = {
186189
val uri = Uri.fromParts("package", app.packageName, null)
187190
val intent = Intent(Intent.ACTION_DELETE, uri)
@@ -220,33 +223,16 @@ fun AppItemComposable(
220223
* Composable function for displaying a list of APK files on the device.
221224
*/
222225
@Composable
223-
fun ApksComposable() {
224-
val context = LocalContext.current
225-
val uri = MediaStore.Files.getContentUri("external")
226-
val cursor = context.contentResolver.query(
227-
uri,
228-
arrayOf(MediaStore.Files.FileColumns.DATA),
229-
MediaStore.Files.FileColumns.MIME_TYPE + "=?",
230-
arrayOf("application/vnd.android.package-archive"),
231-
null
232-
)
233-
234-
var apkPaths = listOf<String>()
235-
cursor?.use {
236-
while (it.moveToNext()) {
237-
val dataColumnIndex = it.getColumnIndex(MediaStore.Files.FileColumns.DATA)
238-
val filePath = it.getString(dataColumnIndex)
239-
apkPaths = apkPaths + filePath
240-
}
241-
}
226+
fun ApksComposable(apkFiles: List<ApkInfo>) {
242227

243228
LazyColumn {
244-
items(apkPaths) { apkPath ->
245-
ApkItemComposable(apkPath)
229+
items(apkFiles) { apkInfo ->
230+
ApkItemComposable(apkPath = apkInfo.path)
246231
}
247232
}
248233
}
249234

235+
250236
/**
251237
* Composable function for displaying detailed information about an APK file.
252238
*
@@ -321,7 +307,8 @@ fun ApkItemComposable(apkPath: String) {
321307
DropdownMenuItem(text = { Text("Install") }, onClick = {
322308
val installIntent = Intent(Intent.ACTION_VIEW)
323309
installIntent.setDataAndType(
324-
Uri.fromFile(apkFile), "application/vnd.android.package-archive"
310+
Uri.fromFile(apkFile),
311+
"application/vnd.android.package-archive"
325312
)
326313
installIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
327314
context.startActivity(installIntent)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.d4rk.cleaner.ui.appmanager
2+
3+
import android.app.Application
4+
import android.content.pm.ApplicationInfo
5+
import android.content.pm.PackageManager
6+
import android.database.Cursor
7+
import android.net.Uri
8+
import android.provider.MediaStore
9+
import androidx.lifecycle.ViewModel
10+
import androidx.lifecycle.viewModelScope
11+
import com.d4rk.cleaner.data.model.ui.ApkInfo
12+
import kotlinx.coroutines.Dispatchers
13+
import kotlinx.coroutines.flow.MutableStateFlow
14+
import kotlinx.coroutines.flow.StateFlow
15+
import kotlinx.coroutines.flow.asStateFlow
16+
import kotlinx.coroutines.launch
17+
import kotlinx.coroutines.withContext
18+
19+
class AppManagerViewModel(private val application: Application) : ViewModel() {
20+
private val _installedApps = MutableStateFlow<List<ApplicationInfo>>(emptyList())
21+
val installedApps: StateFlow<List<ApplicationInfo>> = _installedApps.asStateFlow()
22+
23+
private val _apkFiles = MutableStateFlow<List<ApkInfo>>(emptyList())
24+
val apkFiles: StateFlow<List<ApkInfo>> = _apkFiles.asStateFlow()
25+
26+
27+
init {
28+
loadInstalledApps()
29+
loadApkFiles()
30+
}
31+
32+
private fun loadInstalledApps() {
33+
viewModelScope.launch {
34+
_installedApps.value = getInstalledApps()
35+
}
36+
}
37+
38+
private suspend fun getInstalledApps(): List<ApplicationInfo> {
39+
return withContext(Dispatchers.IO) {
40+
application.packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
41+
}
42+
}
43+
44+
private fun loadApkFiles() {
45+
viewModelScope.launch {
46+
_apkFiles.value = getApkFilesFromStorage()
47+
}
48+
}
49+
50+
private suspend fun getApkFilesFromStorage(): List<ApkInfo> {
51+
return withContext(Dispatchers.IO) {
52+
val apkFiles = mutableListOf<ApkInfo>()
53+
val uri: Uri = MediaStore.Files.getContentUri("external")
54+
val projection = arrayOf(
55+
MediaStore.Files.FileColumns._ID,
56+
MediaStore.Files.FileColumns.DATA,
57+
MediaStore.Files.FileColumns.SIZE
58+
)
59+
val selection = "${MediaStore.Files.FileColumns.MIME_TYPE} = ?"
60+
val selectionArgs = arrayOf("application/vnd.android.package-archive")
61+
val cursor: Cursor? = application.contentResolver.query(
62+
uri, projection, selection, selectionArgs, null
63+
)
64+
65+
cursor?.use {
66+
val idColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
67+
val dataColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)
68+
val sizeColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE)
69+
70+
while (it.moveToNext()) {
71+
val id = it.getLong(idColumn)
72+
val path = it.getString(dataColumn)
73+
val size = it.getLong(sizeColumn)
74+
apkFiles.add(ApkInfo(id, path, size))
75+
}
76+
}
77+
apkFiles
78+
}
79+
}
80+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.d4rk.cleaner.ui.appmanager
2+
3+
import android.app.Application
4+
import androidx.lifecycle.ViewModel
5+
import androidx.lifecycle.ViewModelProvider
6+
7+
class AppManagerViewModelFactory(private val application: Application) : ViewModelProvider.Factory {
8+
override fun <T : ViewModel> create(modelClass: Class<T>): T {
9+
if (modelClass.isAssignableFrom(AppManagerViewModel::class.java)) {
10+
@Suppress("UNCHECKED_CAST")
11+
return AppManagerViewModel(application) as T
12+
}
13+
throw IllegalArgumentException("Unknown ViewModel class")
14+
}
15+
}

0 commit comments

Comments
 (0)