Skip to content

Commit 4e1600d

Browse files
committed
feat: Refactor whitelist management and enhance MainScreen UI
This commit introduces a dedicated `WhitelistViewModel` to manage application whitelisting logic, decoupling it from the UI layer. It also updates the `MainScreen` to display the count of whitelisted apps and improves navigation state handling. Key changes include: * **Whitelist Refactoring**: * Introduced `WhitelistViewModel` to handle app loading and toggling logic using `viewModelScope`. * Updated `WhitelistScreenWrapper` to utilize the new ViewModel. * Added helper methods in `AppLimits.kt` (`getWhitelistedCount` and `getWhitelistedLaunchableCount`) to accurately track whitelisted applications. * **UI Enhancements**: * Renamed "Time Limits" card to "Whitelist" on the `MainScreen`. * Updated the Whitelist card icon to `Icons.Rounded.CheckCircle` and added a dynamic count of allowed apps. * **Navigation Improvements**: Updated `MainActivity` navigation logic to include `saveState` and `restoreState`, ensuring a smoother user experience when switching between bottom bar destinations. * **Utility Updates**: Added `getLimitedAppsCount` to `AppLimits` for better state visibility. Signed-off-by: invokevirtual <purwarpranav80@gmail.com>
1 parent 4a605ab commit 4e1600d

5 files changed

Lines changed: 132 additions & 62 deletions

File tree

Reef/src/main/java/dev/pranav/reef/MainActivity.kt

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ class MainActivity: ComponentActivity() {
126126

127127
var slideProgress by remember { mutableFloatStateOf(0f) }
128128

129+
val whitelistedCount = remember {
130+
Whitelist.getWhitelistedLaunchableCount(launcherApps)
131+
}
132+
129133
val selectedNavIndex = remember(currentDestination) {
130134
when {
131135
currentDestination?.hasRoute<Screen.Home>() == true -> 0
@@ -197,23 +201,36 @@ class MainActivity: ComponentActivity() {
197201
onItemSelected = { index ->
198202
when (index) {
199203
0 -> navController.navigate(Screen.Home) {
200-
popUpTo(Screen.Home) { inclusive = true }
204+
popUpTo(Screen.Home) {
205+
inclusive = true
206+
saveState = true
207+
}
201208
launchSingleTop = true
209+
restoreState = true
202210
}
203211

204212
1 -> navController.navigate(Screen.Usage) {
205-
popUpTo(Screen.Home)
213+
popUpTo(Screen.Home) {
214+
saveState = true
215+
}
206216
launchSingleTop = true
217+
restoreState = true
207218
}
208219

209220
2 -> navController.navigate(Screen.Timer) {
210-
popUpTo(Screen.Home)
221+
popUpTo(Screen.Home) {
222+
saveState = true
223+
}
211224
launchSingleTop = true
225+
restoreState = true
212226
}
213227

214228
3 -> navController.navigate(Screen.Settings) {
215-
popUpTo(Screen.Home)
229+
popUpTo(Screen.Home) {
230+
saveState = true
231+
}
216232
launchSingleTop = true
233+
restoreState = true
217234
}
218235
}
219236
}
@@ -257,7 +274,8 @@ class MainActivity: ComponentActivity() {
257274
slideProgress = progress
258275
},
259276
currentTimeLeft = currentTimeLeft,
260-
currentTimerState = currentTimerState
277+
currentTimerState = currentTimerState,
278+
whitelistedAppsCount = whitelistedCount
261279
)
262280
}
263281

Reef/src/main/java/dev/pranav/reef/MainScreen.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ fun HomeContent(
4848
@Suppress("UNUSED_PARAMETER") slideProgress: Float = 0f,
4949
onSlideProgressChange: (Float) -> Unit = {},
5050
currentTimeLeft: String = "00:00",
51-
currentTimerState: String = "FOCUS"
51+
currentTimerState: String = "FOCUS",
52+
whitelistedAppsCount: Int = 0
5253
) {
5354
val context = LocalContext.current
5455
val timerState by TimerStateManager.state.collectAsState()
@@ -101,7 +102,8 @@ fun HomeContent(
101102
)
102103
TimeLimitsCard(
103104
modifier = Modifier.weight(1f),
104-
onClick = onNavigateToWhitelist
105+
onClick = onNavigateToWhitelist,
106+
whitelistedCount = whitelistedAppsCount
105107
)
106108
}
107109

@@ -383,7 +385,8 @@ private fun AppUsageCard(
383385
@Composable
384386
private fun TimeLimitsCard(
385387
modifier: Modifier = Modifier,
386-
onClick: () -> Unit
388+
onClick: () -> Unit,
389+
whitelistedCount: Int = 0
387390
) {
388391
Card(
389392
onClick = onClick,
@@ -407,7 +410,7 @@ private fun TimeLimitsCard(
407410
modifier = Modifier.fillMaxSize()
408411
) {
409412
Icon(
410-
Icons.Rounded.HourglassEmpty,
413+
Icons.Rounded.CheckCircle,
411414
contentDescription = null,
412415
modifier = Modifier.size(20.dp),
413416
tint = MaterialTheme.colorScheme.onTertiaryContainer
@@ -417,15 +420,15 @@ private fun TimeLimitsCard(
417420

418421
Column {
419422
Text(
420-
text = "Time Limits",
423+
text = "Whitelist",
421424
style = MaterialTheme.typography.titleLarge.copy(
422425
fontWeight = FontWeight.Bold
423426
),
424427
fontFamily = Typography.DMSerif,
425428
color = MaterialTheme.colorScheme.onTertiaryContainer
426429
)
427430
Text(
428-
text = "3 apps capped",
431+
text = "$whitelistedCount apps allowed",
429432
style = MaterialTheme.typography.bodyMedium,
430433
color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.7f)
431434
)

Reef/src/main/java/dev/pranav/reef/screens/ScreenWrappers.kt

Lines changed: 12 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,15 @@ import android.app.usage.UsageStatsManager
44
import android.content.Context
55
import android.content.pm.LauncherApps
66
import android.content.pm.PackageManager
7-
import android.os.Process
8-
import androidx.compose.runtime.*
9-
import androidx.compose.ui.graphics.asImageBitmap
7+
import androidx.compose.runtime.Composable
108
import androidx.lifecycle.ViewModel
119
import androidx.lifecycle.ViewModelProvider
1210
import androidx.lifecycle.viewmodel.compose.viewModel
1311
import dev.pranav.reef.ui.appusage.AppUsageScreen
1412
import dev.pranav.reef.ui.appusage.AppUsageStats
1513
import dev.pranav.reef.ui.appusage.AppUsageViewModel
16-
import dev.pranav.reef.ui.whitelist.AllowedAppsState
1714
import dev.pranav.reef.ui.whitelist.WhitelistScreen
18-
import dev.pranav.reef.ui.whitelist.WhitelistedApp
19-
import dev.pranav.reef.ui.whitelist.toBitmap
20-
import dev.pranav.reef.util.Whitelist
21-
import kotlinx.coroutines.Dispatchers
22-
import kotlinx.coroutines.withContext
15+
import dev.pranav.reef.ui.whitelist.WhitelistViewModel
2316

2417
@Composable
2518
fun UsageScreenWrapper(
@@ -55,51 +48,19 @@ fun WhitelistScreenWrapper(
5548
packageManager: PackageManager,
5649
currentPackageName: String
5750
) {
58-
var uiState by remember { mutableStateOf<AllowedAppsState>(AllowedAppsState.Loading) }
59-
60-
LaunchedEffect(Unit) {
61-
withContext(Dispatchers.IO) {
62-
val apps = launcherApps.getActivityList(null, Process.myUserHandle())
63-
.asSequence()
64-
.distinctBy { it.applicationInfo.packageName }
65-
.map { it.applicationInfo }
66-
.filter { it.packageName != currentPackageName }
67-
.map { appInfo ->
68-
WhitelistedApp(
69-
packageName = appInfo.packageName,
70-
label = appInfo.loadLabel(packageManager).toString(),
71-
icon = appInfo.loadIcon(packageManager).toBitmap().asImageBitmap(),
72-
isWhitelisted = Whitelist.isWhitelisted(appInfo.packageName)
73-
)
74-
}
75-
.sortedBy { it.label }
76-
.toList()
77-
uiState = AllowedAppsState.Success(apps)
78-
}
79-
}
80-
81-
fun toggleWhitelist(app: WhitelistedApp) {
82-
if (app.isWhitelisted) {
83-
Whitelist.unwhitelist(app.packageName)
84-
} else {
85-
Whitelist.whitelist(app.packageName)
86-
}
87-
88-
val currentState = uiState
89-
if (currentState is AllowedAppsState.Success) {
90-
val updatedList = currentState.apps.map {
91-
if (it.packageName == app.packageName) {
92-
it.copy(isWhitelisted = !it.isWhitelisted)
93-
} else {
94-
it
95-
}
51+
val viewModel: WhitelistViewModel = viewModel(
52+
factory = object: ViewModelProvider.Factory {
53+
@Suppress("UNCHECKED_CAST")
54+
override fun <T: ViewModel> create(modelClass: Class<T>): T {
55+
return WhitelistViewModel(
56+
launcherApps, packageManager, currentPackageName
57+
) as T
9658
}
97-
uiState = AllowedAppsState.Success(updatedList)
9859
}
99-
}
60+
)
10061

10162
WhitelistScreen(
102-
uiState = uiState,
103-
onToggle = ::toggleWhitelist
63+
uiState = viewModel.uiState.value,
64+
onToggle = viewModel::toggleWhitelist
10465
)
10566
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package dev.pranav.reef.ui.whitelist
2+
3+
import android.content.pm.LauncherApps
4+
import android.content.pm.PackageManager
5+
import android.os.Process
6+
import androidx.compose.runtime.State
7+
import androidx.compose.runtime.mutableStateOf
8+
import androidx.compose.ui.graphics.asImageBitmap
9+
import androidx.lifecycle.ViewModel
10+
import androidx.lifecycle.viewModelScope
11+
import dev.pranav.reef.util.Whitelist
12+
import kotlinx.coroutines.Dispatchers
13+
import kotlinx.coroutines.launch
14+
import kotlinx.coroutines.withContext
15+
16+
class WhitelistViewModel(
17+
private val launcherApps: LauncherApps,
18+
private val packageManager: PackageManager,
19+
private val currentPackageName: String
20+
): ViewModel() {
21+
22+
private val _uiState = mutableStateOf<AllowedAppsState>(AllowedAppsState.Loading)
23+
val uiState: State<AllowedAppsState> = _uiState
24+
25+
init {
26+
loadApps()
27+
}
28+
29+
private fun loadApps() {
30+
viewModelScope.launch {
31+
val apps = withContext(Dispatchers.IO) {
32+
launcherApps.getActivityList(null, Process.myUserHandle())
33+
.asSequence()
34+
.distinctBy { it.applicationInfo.packageName }
35+
.map { it.applicationInfo }
36+
.filter { it.packageName != currentPackageName }
37+
.map { appInfo ->
38+
WhitelistedApp(
39+
packageName = appInfo.packageName,
40+
label = appInfo.loadLabel(packageManager).toString(),
41+
icon = appInfo.loadIcon(packageManager).toBitmap().asImageBitmap(),
42+
isWhitelisted = Whitelist.isWhitelisted(appInfo.packageName)
43+
)
44+
}
45+
.sortedBy { it.label }
46+
.toList()
47+
}
48+
_uiState.value = AllowedAppsState.Success(apps)
49+
}
50+
}
51+
52+
fun toggleWhitelist(app: WhitelistedApp) {
53+
if (app.isWhitelisted) {
54+
Whitelist.unwhitelist(app.packageName)
55+
} else {
56+
Whitelist.whitelist(app.packageName)
57+
}
58+
59+
val currentState = _uiState.value
60+
if (currentState is AllowedAppsState.Success) {
61+
val updatedList = currentState.apps.map {
62+
if (it.packageName == app.packageName) {
63+
it.copy(isWhitelisted = !it.isWhitelisted)
64+
} else {
65+
it
66+
}
67+
}
68+
_uiState.value = AllowedAppsState.Success(updatedList)
69+
}
70+
}
71+
}
72+

Reef/src/main/java/dev/pranav/reef/util/AppLimits.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ object AppLimits {
3131

3232
fun hasLimit(pkg: String): Boolean = limits.containsKey(pkg)
3333

34+
fun getLimitedAppsCount(): Int = limits.size
35+
3436
fun removeLimit(pkg: String) {
3537
limits.remove(pkg)
3638
}
@@ -136,6 +138,20 @@ object Whitelist {
136138
sharedPreferences.edit { putBoolean(packageName, false) }
137139
}
138140

141+
fun getWhitelistedCount(): Int {
142+
return sharedPreferences.all.count { it.value == true }
143+
}
144+
145+
fun getWhitelistedLaunchableCount(launcherApps: android.content.pm.LauncherApps): Int {
146+
val launchablePackages =
147+
launcherApps.getActivityList(null, android.os.Process.myUserHandle())
148+
.map { it.applicationInfo.packageName }
149+
.toSet()
150+
return sharedPreferences.all.count { (pkg, isWhitelisted) ->
151+
isWhitelisted == true && launchablePackages.contains(pkg)
152+
}
153+
}
154+
139155
val allowedApps = hashSetOf(
140156
"dev.pranav.reef",
141157
"dev.pranav.applock",

0 commit comments

Comments
 (0)