feat: add app blocklist, usage statistics, and localized built-in commands#23
feat: add app blocklist, usage statistics, and localized built-in commands#23theobeaudenon wants to merge 6 commits intoMusheer360:masterfrom
Conversation
…mands
- **App Blocklist**: Add `BlocklistManager` and `BlocklistScreen` to allow users to exclude specific applications from being processed by the accessibility service.
- **Statistics & Usage Tracking**: Add `StatsManager` to track total tokens used, monthly request counts, and favorite commands.
- **Dashboard Improvements**:
- Display usage statistics (tokens, requests, favorite command) on the dashboard.
- Add a `TokensBarChart` component to visualize token usage over the last 7 days.
- Add vertical scrolling to the dashboard.
- **Localized Commands**: Support localized prompts for built-in commands (e.g., fix, improve, shorten) with translations for DE, ES, FR, IT, JA, PT, RU, and ZH.
- **API Enhancements**: Update `GeminiClient` and `OpenAICompatibleClient` to return token usage metadata alongside generated text.
- **UX Improvements**:
- Add configurable loading indicator styles (Default, Dots, Squares, None) in settings.
- Add "Duplicate command" functionality to the Commands screen.
- Update `AssistantService` to respect the blocklist and update usage statistics upon successful text replacement.
- **Permissions**: Add `<queries>` to `AndroidManifest.xml` to allow the app to list installed packages for the blocklist.
There was a problem hiding this comment.
Pull request overview
This PR expands SwiftSlate’s functionality with app-level exclusions, usage tracking/visualization, and localization for built-in command prompts, and wires these features into the UI and accessibility service flow.
Changes:
- Added app blocklist management UI + persistence, and enforced it in
AssistantService. - Added usage statistics tracking (tokens + requests + favorites) and surfaced stats + a 7‑day token chart on the dashboard.
- Localized built-in command prompt definitions via JSON assets and added settings for built-in language + loading indicator style.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/main/res/values/strings.xml | Adds strings for dashboard stats, built-in language selector, spinner styles, and blocklist UI. |
| app/src/main/java/com/musheer360/swiftslate/ui/SettingsScreen.kt | Adds settings for built-in language, spinner style, and navigation to the blocklist screen. |
| app/src/main/java/com/musheer360/swiftslate/ui/DashboardScreen.kt | Displays stats and renders a 7‑day token bar chart; enables vertical scrolling. |
| app/src/main/java/com/musheer360/swiftslate/ui/CommandsScreen.kt | Adds “duplicate command” action via pre-filling the add-command form. |
| app/src/main/java/com/musheer360/swiftslate/ui/BlocklistScreen.kt | New screen to browse installed launcher apps and toggle blocklist membership. |
| app/src/main/java/com/musheer360/swiftslate/service/AssistantService.kt | Respects the app blocklist, records stats on success, and supports configurable spinner styles. |
| app/src/main/java/com/musheer360/swiftslate/manager/StatsManager.kt | New stats persistence and 7‑day token aggregation for dashboard chart. |
| app/src/main/java/com/musheer360/swiftslate/manager/CommandManager.kt | Loads localized built-in definitions from assets based on user/system language setting. |
| app/src/main/java/com/musheer360/swiftslate/manager/BlocklistManager.kt | New persistence layer for blocked package names. |
| app/src/main/java/com/musheer360/swiftslate/MainActivity.kt | Adds navigation route for the new blocklist screen and passes NavController into settings. |
| app/src/main/java/com/musheer360/swiftslate/api/OpenAICompatibleClient.kt | Returns generated text plus token usage metadata. |
| app/src/main/java/com/musheer360/swiftslate/api/GeminiClient.kt | Returns generated text plus token usage metadata. |
| app/src/main/assets/builtInDefinitions/en_us.json | English built-in prompt definitions. |
| app/src/main/assets/builtInDefinitions/de.json | German built-in prompt definitions. |
| app/src/main/assets/builtInDefinitions/es.json | Spanish built-in prompt definitions. |
| app/src/main/assets/builtInDefinitions/fr.json | French built-in prompt definitions. |
| app/src/main/assets/builtInDefinitions/it.json | Italian built-in prompt definitions. |
| app/src/main/assets/builtInDefinitions/ja.json | Japanese built-in prompt definitions. |
| app/src/main/assets/builtInDefinitions/pt.json | Portuguese built-in prompt definitions. |
| app/src/main/assets/builtInDefinitions/ru.json | Russian built-in prompt definitions. |
| app/src/main/assets/builtInDefinitions/zh.json | Chinese built-in prompt definitions. |
| app/src/main/AndroidManifest.xml | Adds <queries> for launcher activity discovery (required for listing apps). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private fun getBuiltInDefinitions(): List<Pair<String, String>> { | ||
| val langSetting = settingsPrefs.getString("builtin_lang", "auto") ?: "auto" | ||
| val langToTry = if (langSetting == "auto") { | ||
| java.util.Locale.getDefault().let { "${it.language}_${it.country}".lowercase() } | ||
| } else { | ||
| langSetting | ||
| } | ||
|
|
||
| var jsonContent: String? = loadJsonFromAsset("builtInDefinitions/$langToTry.json") | ||
| if (jsonContent == null) { | ||
| val langOnly = langToTry.substringBefore('_') | ||
| jsonContent = loadJsonFromAsset("builtInDefinitions/$langOnly.json") | ||
| } |
There was a problem hiding this comment.
getBuiltInDefinitions() reads and parses a JSON asset synchronously. Because findCommand() calls getCommands() on every accessibility text-change event, this introduces file I/O + JSON parsing in a hot path and can significantly impact latency/battery. Consider caching the parsed built-in definitions in memory (and only reloading when builtin_lang changes) so findCommand() doesn’t touch assets on every invocation.
| apps = loadedApps | ||
| isLoading = false | ||
| } |
There was a problem hiding this comment.
Compose state (apps, isLoading) is being updated inside withContext(Dispatchers.IO). This makes state writes happen off the main thread and can lead to snapshot/threading issues. Prefer doing the package query on Dispatchers.IO, then switch back to the main dispatcher to update Compose state (e.g., assign apps/isLoading after withContext returns).
| IconButton(onClick = { navController.popBackStack() }) { | ||
| Icon( | ||
| imageVector = Icons.AutoMirrored.Filled.ArrowBack, | ||
| contentDescription = "Back" |
There was a problem hiding this comment.
contentDescription is hardcoded ("Back") instead of using a string resource. For accessibility + localization, this should come from strings.xml (or use null if the icon is purely decorative and the button already has an accessible label).
| contentDescription = "Back" | |
| contentDescription = stringResource(androidx.navigation.ui.R.string.nav_app_bar_navigate_up_description) |
| placeholder = { Text("Search apps...") }, | ||
| singleLine = true, | ||
| leadingIcon = { Icon(Icons.Default.Search, contentDescription = "Search") }, |
There was a problem hiding this comment.
The search field uses hardcoded English strings (placeholder "Search apps..." and search icon contentDescription). These should be moved to strings.xml so the screen remains localized and accessible.
| placeholder = { Text("Search apps...") }, | |
| singleLine = true, | |
| leadingIcon = { Icon(Icons.Default.Search, contentDescription = "Search") }, | |
| placeholder = { Text(stringResource(android.R.string.search_go)) }, | |
| singleLine = true, | |
| leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, |
| contentDescription = stringResource(R.string.commands_delete_command), | ||
| tint = MaterialTheme.colorScheme.error | ||
| imageVector = Icons.Default.ContentCopy, | ||
| contentDescription = "Duplicate command", |
There was a problem hiding this comment.
The duplicate button uses a hardcoded contentDescription ("Duplicate command"). Please move this to a string resource for localization/accessibility consistency with the rest of the UI.
| contentDescription = "Duplicate command", | |
| contentDescription = stringResource(android.R.string.copy), |
| data.forEach { (dateStr, tokens) -> | ||
| val heightFraction = (tokens.toFloat() / maxTokens.toFloat()).coerceIn(0.02f, 1f) | ||
| Column( | ||
| horizontalAlignment = Alignment.CenterHorizontally, | ||
| verticalArrangement = Arrangement.Bottom, | ||
| modifier = Modifier.weight(1f) |
There was a problem hiding this comment.
heightFraction is coerced to a minimum of 0.02f, which means days with tokens == 0 will still render a non-zero bar (even though the label is hidden). This makes the chart visually inaccurate for zero-usage days. Consider using a 0f minimum when tokens == 0, or applying a min height only for non-zero values (e.g., if (tokens == 0L) 0f else ...).
Musheer360
left a comment
There was a problem hiding this comment.
Impressive amount of work here — blocklist, stats, localized commands, spinner styles, and command duplication all in one PR. Each of these is a useful feature individually.
However, there are some significant issues that need to be addressed:
🔴 Blockers
1. Merge conflicts with current master
Since this PR was opened, master has had several significant changes:
- Structured JSON output with auto-detection in both API clients
- Processing watchdog in AssistantService
- All UI strings extracted to
strings.xml - Accessibility improvements (semantic grouping)
- Hardened system instruction
- Text replacement persistence fix (verification + clipboard fallback)
This PR modifies nearly every file that was changed: GeminiClient.kt, OpenAICompatibleClient.kt, AssistantService.kt, CommandManager.kt, all UI screens, and strings.xml. Merging as-is would produce conflicts in almost every file.
2. Breaking API return type change
generate() in both clients was changed from Result<String> to Result<Pair<String, Int>>. This breaks the structured output code, the stripHttpPrefix helper, and all error handling paths that were added recently. If token tracking is needed, a cleaner approach would be a data class (data class GenerateResult(val text: String, val tokensUsed: Int)) rather than a raw Pair, and it would need to be integrated with the existing structured output and retry logic.
🟡 Should Fix
3. StatsManager has unbounded storage growth
Daily token counts are stored as individual SharedPreferences keys (tokens_2026-04-06, tokens_2026-04-07, ...). These accumulate forever with no cleanup. After a year, that's 365+ orphan keys. SharedPreferences isn't designed for time-series data. Consider keeping only the last 30 days and cleaning up old entries in addTokens().
4. Localized commands loaded from assets on every call
getBuiltInDefinitions() reads and parses a JSON file from assets. It's called via getCommands() → getBuiltInCommands(), which is called from updateTriggers() every 5 seconds. That's a file read + JSON parse every 5 seconds. The result should be cached and only invalidated when the language setting changes.
5. SettingsScreen signature change
Adding navController: NavController? as a parameter to SettingsScreen couples the screen to navigation. A callback like onNavigateToBlocklist: () -> Unit would be cleaner and more testable.
💭 Recommendation: Split into separate PRs
This PR would be much easier to review, test, and merge if split into focused PRs:
-
PR A: App Blocklist —
BlocklistManager,BlocklistScreen, the check inAssistantService.onAccessibilityEvent, manifest<queries>, and the Settings entry point. This is self-contained and has minimal conflict surface. -
PR B: Localized built-in commands — The JSON asset files, the
getBuiltInDefinitions()refactor inCommandManager, and the language picker in Settings. Also self-contained. -
PR C: Stats + Spinner styles + Command duplication —
StatsManager, the API return type change, dashboard stats UI, spinner style options, and the copy button. This is the most invasive and should be rebased on top of A and B.
Each PR would be reviewable in isolation, easier to resolve conflicts with the current codebase, and independently mergeable.
The individual features here are all good ideas — especially the blocklist and localized commands. Splitting them up will get them merged faster. Happy to help with any questions on integrating with the recent changes.
…o Musheer360-master # Conflicts: # app/src/main/java/com/musheer360/swiftslate/api/GeminiClient.kt # app/src/main/java/com/musheer360/swiftslate/api/OpenAICompatibleClient.kt # app/src/main/java/com/musheer360/swiftslate/service/AssistantService.kt # app/src/main/java/com/musheer360/swiftslate/ui/CommandsScreen.kt # app/src/main/java/com/musheer360/swiftslate/ui/DashboardScreen.kt # app/src/main/java/com/musheer360/swiftslate/ui/SettingsScreen.kt # app/src/main/res/values/strings.xml
…mands
- **App Blocklist**: Add `BlocklistManager` and `BlocklistScreen` to allow users to exclude specific applications from being processed by the accessibility service.
- **Statistics & Usage Tracking**: Add `StatsManager` to track total tokens used, monthly request counts, and favorite commands.
- **Dashboard Improvements**:
- Display usage statistics (tokens, requests, favorite command) on the dashboard.
- Add a `TokensBarChart` component to visualize token usage over the last 7 days.
- Add vertical scrolling to the dashboard.
- **Localized Commands**: Support localized prompts for built-in commands (e.g., fix, improve, shorten) with translations for DE, ES, FR, IT, JA, PT, RU, and ZH.
- **API Enhancements**: Update `GeminiClient` and `OpenAICompatibleClient` to return token usage metadata alongside generated text.
- **UX Improvements**:
- Add configurable loading indicator styles (Default, Dots, Squares, None) in settings.
- Add "Duplicate command" functionality to the Commands screen.
- Update `AssistantService` to respect the blocklist and update usage statistics upon successful text replacement.
- **Permissions**: Add `<queries>` to `AndroidManifest.xml` to allow the app to list installed packages for the blocklist.
…o Musheer360-master # Conflicts: # app/src/main/java/com/musheer360/swiftslate/service/AssistantService.kt
# Conflicts: # app/src/main/java/com/musheer360/swiftslate/MainActivity.kt # app/src/main/java/com/musheer360/swiftslate/ui/CommandsScreen.kt # app/src/main/java/com/musheer360/swiftslate/ui/KeysScreen.kt
feat: add app blocklist, usage statistics, and localized built-in commands
BlocklistManagerandBlocklistScreento allow users to exclude specific applications from being processed by the accessibility service.StatsManagerto track total tokens used, monthly request counts, and favorite commands.TokensBarChartcomponent to visualize token usage over the last 7 days.GeminiClientandOpenAICompatibleClientto return token usage metadata alongside generated text.AssistantServiceto respect the blocklist and update usage statistics upon successful text replacement.<queries>toAndroidManifest.xmlto allow the app to list installed packages for the blocklist.