Skip to content

Commit 7fdfed9

Browse files
committed
Fix main design
- **UI Refactoring**: - Replaced the main toggle-based app list with a dashboard displaying currently protected apps. - Introduced a `ModalBottomSheet` for adding new apps to the protection list. - Replaced permission dialogs with a persistent `PermissionWarningBanner` on the main screen. - Added a status indicator for global protection (ON/OFF) in the top bar. - **Performance & Caching**: - Implemented `AppIconCache` with `LruCache` to improve app icon and label loading performance. - Updated `MainViewModel` to use `StateFlow` and `combine` for more efficient reactive filtering of locked and unlocked apps. - **Data Layer**: - Simplified `LockedAppsRepository` by using Kotlin collection operators (`+`, `-`) for cleaner preference updates. - Added support for bulk adding/removing locked apps in `LockedAppsRepository` and `AppLockRepository`. - **Accessibility & Logic**: - Improved anti-uninstall detection in `AppLockAccessibilityService` by refining package-specific alert dialog checks. - Updated `.gitignore` to include `.idea`. - Added several new string resources for permission warnings.
1 parent 4a3c5ff commit 7fdfed9

File tree

10 files changed

+652
-429
lines changed

10 files changed

+652
-429
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
*.iml
22
.gradle
33
/local.properties
4-
/idea
4+
/.idea
55
.kotlin
66
.DS_Store
77
/build

.idea/modules.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class AppLockRepository(private val context: Context) {
1515

1616
fun getLockedApps(): Set<String> = lockedAppsRepository.getLockedApps()
1717
fun addLockedApp(packageName: String) = lockedAppsRepository.addLockedApp(packageName)
18+
fun addMultipleLockedApps(packageNames: Set<String>) =
19+
lockedAppsRepository.addMultipleLockedApps(packageNames)
1820
fun removeLockedApp(packageName: String) = lockedAppsRepository.removeLockedApp(packageName)
1921
fun isAppLocked(packageName: String): Boolean = lockedAppsRepository.isAppLocked(packageName)
2022

app/src/main/java/dev/pranav/applock/data/repository/LockedAppsRepository.kt

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,18 @@ class LockedAppsRepository(context: Context) {
1515

1616
// Locked Apps Management
1717
fun getLockedApps(): Set<String> {
18-
return preferences.getStringSet(KEY_LOCKED_APPS, emptySet()) ?: emptySet()
18+
return preferences.getStringSet(KEY_LOCKED_APPS, emptySet())?.toSet() ?: emptySet()
1919
}
2020

2121
fun addLockedApp(packageName: String) {
2222
if (packageName.isBlank()) return
23-
24-
val currentApps = getLockedApps().toMutableSet()
25-
if (currentApps.add(packageName)) {
26-
preferences.edit { putStringSet(KEY_LOCKED_APPS, currentApps) }
27-
}
23+
val updated = getLockedApps() + packageName
24+
preferences.edit { putStringSet(KEY_LOCKED_APPS, updated) }
2825
}
2926

3027
fun removeLockedApp(packageName: String) {
31-
val currentApps = getLockedApps().toMutableSet()
32-
if (currentApps.remove(packageName)) {
33-
preferences.edit { putStringSet(KEY_LOCKED_APPS, currentApps) }
34-
}
28+
val updated = getLockedApps() - packageName
29+
preferences.edit { putStringSet(KEY_LOCKED_APPS, updated) }
3530
}
3631

3732
fun isAppLocked(packageName: String): Boolean {
@@ -44,23 +39,19 @@ class LockedAppsRepository(context: Context) {
4439

4540
// Trigger Exclusions Management
4641
fun getTriggerExcludedApps(): Set<String> {
47-
return preferences.getStringSet(KEY_TRIGGER_EXCLUDED_APPS, emptySet()) ?: emptySet()
42+
return preferences.getStringSet(KEY_TRIGGER_EXCLUDED_APPS, emptySet())?.toSet()
43+
?: emptySet()
4844
}
4945

5046
fun addTriggerExcludedApp(packageName: String) {
5147
if (packageName.isBlank()) return
52-
53-
val currentApps = getTriggerExcludedApps().toMutableSet()
54-
if (currentApps.add(packageName)) {
55-
preferences.edit { putStringSet(KEY_TRIGGER_EXCLUDED_APPS, currentApps) }
56-
}
48+
val updated = getTriggerExcludedApps() + packageName
49+
preferences.edit { putStringSet(KEY_TRIGGER_EXCLUDED_APPS, updated) }
5750
}
5851

5952
fun removeTriggerExcludedApp(packageName: String) {
60-
val currentApps = getTriggerExcludedApps().toMutableSet()
61-
if (currentApps.remove(packageName)) {
62-
preferences.edit { putStringSet(KEY_TRIGGER_EXCLUDED_APPS, currentApps) }
63-
}
53+
val updated = getTriggerExcludedApps() - packageName
54+
preferences.edit { putStringSet(KEY_TRIGGER_EXCLUDED_APPS, updated) }
6455
}
6556

6657
fun isAppTriggerExcluded(packageName: String): Boolean {
@@ -75,18 +66,13 @@ class LockedAppsRepository(context: Context) {
7566
fun addMultipleLockedApps(packageNames: Set<String>) {
7667
val validPackageNames = packageNames.filter { it.isNotBlank() }.toSet()
7768
if (validPackageNames.isEmpty()) return
78-
79-
val currentApps = getLockedApps().toMutableSet()
80-
if (currentApps.addAll(validPackageNames)) {
81-
preferences.edit { putStringSet(KEY_LOCKED_APPS, currentApps) }
82-
}
69+
val updated = getLockedApps() + validPackageNames
70+
preferences.edit { putStringSet(KEY_LOCKED_APPS, updated) }
8371
}
8472

8573
fun removeMultipleLockedApps(packageNames: Set<String>) {
86-
val currentApps = getLockedApps().toMutableSet()
87-
if (currentApps.removeAll(packageNames)) {
88-
preferences.edit { putStringSet(KEY_LOCKED_APPS, currentApps) }
89-
}
74+
val updated = getLockedApps() - packageNames
75+
preferences.edit { putStringSet(KEY_LOCKED_APPS, updated) }
9076
}
9177

9278
companion object {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package dev.pranav.applock.features.applist.ui
2+
3+
import android.content.Context
4+
import android.content.pm.ApplicationInfo
5+
import android.util.LruCache
6+
import androidx.compose.ui.graphics.ImageBitmap
7+
import androidx.compose.ui.graphics.asImageBitmap
8+
import androidx.core.graphics.drawable.toBitmap
9+
10+
object AppIconCache {
11+
private const val MAX_CACHE_SIZE = 200
12+
13+
private val iconCache = LruCache<String, ImageBitmap>(MAX_CACHE_SIZE)
14+
private val labelCache = LruCache<String, String>(MAX_CACHE_SIZE)
15+
16+
fun getIcon(context: Context, appInfo: ApplicationInfo): ImageBitmap? {
17+
val cached = iconCache.get(appInfo.packageName)
18+
if (cached != null) return cached
19+
20+
val icon = appInfo.loadIcon(context.packageManager)?.toBitmap()?.asImageBitmap()
21+
icon?.let { iconCache.put(appInfo.packageName, it) }
22+
return icon
23+
}
24+
25+
fun getLabel(context: Context, appInfo: ApplicationInfo): String {
26+
val cached = labelCache.get(appInfo.packageName)
27+
if (cached != null) return cached
28+
29+
val label = appInfo.loadLabel(context.packageManager).toString()
30+
labelCache.put(appInfo.packageName, label)
31+
return label
32+
}
33+
34+
fun clear() {
35+
iconCache.evictAll()
36+
labelCache.evictAll()
37+
}
38+
}
39+

0 commit comments

Comments
 (0)