Conversation
Currently translated at 90.4% (123 of 136 strings) Translation: Grit/strings Translate-URL: https://hosted.weblate.org/projects/grit/strings/hu/
Signed-off-by: shub39 <[email protected]>
Signed-off-by: shub39 <[email protected]>
* ✨ habit widget improvements * ✨ new and improved streak widget * ✨ changed notif icon and replaced refresh/arrow_forward icon… * ✨ testing out widget previews * ✨ added widget previews * ✨ tweaked widget proportions * ✨ appropriate renaming * ✨ habit week chart widget * ✨ removed faulty average metric
Signed-off-by: shub39 <[email protected]>
Signed-off-by: shub39 <[email protected]>
Currently translated at 86.0% (117 of 136 strings) Translation: Grit/strings Translate-URL: https://hosted.weblate.org/projects/grit/strings/zh_Hans/
Signed-off-by: shub39 <[email protected]>
Currently translated at 91.9% (125 of 136 strings) Translation: Grit/strings Translate-URL: https://hosted.weblate.org/projects/grit/strings/ru/
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughAdds an in-app changelog system (asset, manager, UI, datastore tracking), restructures Glance widgets into dedicated packages (AllTasks, HabitOverview, HabitStreak, HabitWeekChart), updates widget metadata/receivers, renames habit APIs, removes server-port datastore keys, bumps app version, and adds resources/strings. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant App as App
participant MainVM as MainViewModel
participant ChangelogMgr as ChangelogManager
participant DataStore as DataStore
participant Dialog as ChangelogDialog
User->>App: Launch
App->>MainVM: onStart
MainVM->>ChangelogMgr: request changelogs
ChangelogMgr->>ChangelogMgr: read assets/changelog.json
ChangelogMgr-->>MainVM: changelog list
MainVM->>DataStore: getLastChangelogShown()
DataStore-->>MainVM: last shown version
MainVM->>MainVM: compare versions
alt new version available
MainVM->>DataStore: updateLastChangelogShown(newVersion)
MainVM-->>App: set currentChangelog
App->>Dialog: show(currentChangelog)
User->>Dialog: dismiss
Dialog->>App: onDismissChangelog()
App->>MainVM: dismissChangelog()
MainVM->>MainVM: clear currentChangelog
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 16
🧹 Nitpick comments (15)
app/src/main/res/drawable/check_list.xml (1)
8-8: Hardcoded fill color won't adapt to dark/dynamic themes.
#B7B7B7is baked in at the drawable level, so the icon will always render as static gray regardless of the active light/dark theme or Material You dynamic color. Prefer a theme attribute or@colorresource so the tint adapts automatically.🎨 Suggested fix
- android:fillColor="#B7B7B7"/> + android:fillColor="@color/widget_icon_tint"/>Define the token in
res/values/colors.xml(and a dark-mode override inres/values-night/colors.xml):<!-- res/values/colors.xml --> <color name="widget_icon_tint">#B7B7B7</color> <!-- res/values-night/colors.xml --> <color name="widget_icon_tint">#E0E0E0</color>Alternatively, if this drawable is always used inside an
ImageVieworAppCompatImageView, settingandroid:tint="?attr/colorControlNormal"at the usage site (or via aDrawableCompat.setTintcall) achieves the same result without touching the drawable itself.Given the summary notes that several other drawables (alarm.xml, analytics.xml, arrow_forward.xml, check_circle.xml, circle_border.xml, heat.xml, heat_outlined.xml, refresh.xml, view_week.xml) were also added in this PR, the same fix should be applied consistently across all of them.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/res/drawable/check_list.xml` at line 8, The drawable uses a hardcoded android:fillColor="#B7B7B7" which prevents theme/dark-mode adaptation; replace hardcoded literals in check_list.xml (and the other added drawables: alarm.xml, analytics.xml, arrow_forward.xml, check_circle.xml, circle_border.xml, heat.xml, heat_outlined.xml, refresh.xml, view_week.xml) with a theme-aware reference such as `@color/widget_icon_tint` or an attribute like ?attr/colorControlNormal, and add corresponding color resources in res/values/colors.xml and res/values-night/colors.xml (or ensure the attribute is provided by your theme) so the icon tint adapts to light/dark/dynamic themes.app/src/main/res/drawable/check_circle.xml (1)
8-8: Consider using a color resource or theme attribute instead of a hardcoded hex color.
#B7B7B7won't adapt to light/dark theming. If the app already has a designated icon tint color (or uses dynamic theming), referencing it here keeps all icons consistent and automatically responsive to theme changes.🎨 Proposed change using a color resource
Define the color once in
res/values/colors.xml:+ <color name="icon_tint_default">#B7B7B7</color>Then reference it in the drawable:
- android:fillColor="#B7B7B7"/> + android:fillColor="@color/icon_tint_default"/>Alternatively, if the app supports theming via
?attr/, you could bind to a theme attribute so the icon color shifts automatically between light and dark modes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/res/drawable/check_circle.xml` at line 8, Replace the hardcoded hex fillColor in check_circle.xml (android:fillColor="#B7B7B7") with a color resource or theme attribute so the icon adapts to theming; create or reuse a color in res/values/colors.xml (e.g., ic_tint) or reference a theme attribute (e.g., ?attr/iconTint) and update the android:fillColor in the drawable to use that resource reference instead.app/src/main/res/drawable/heat.xml (1)
8-8: Consider replacing the hardcoded fill color with a color resource or theme attribute.
#B7B7B7is a static value that won't respond to dark/light theme switches. Using a named color resource (e.g.,@color/icon_tint_default) or a theme attribute (e.g.,?attr/colorOnSurface) allows the icon to adapt automatically. Alternatively, leave the drawable untinted and applyapp:tintat each usage site.♻️ Proposed change
- android:fillColor="#B7B7B7"/> + android:fillColor="?attr/colorOnSurface"/>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/res/drawable/heat.xml` at line 8, The drawable hardcodes a static fill color ("android:fillColor=\"#B7B7B7\"") which prevents theme-aware tinting; replace that literal with a color resource or theme attribute (e.g., use a named color like `@color/icon_tint_default` or a theme attribute like ?attr/colorOnSurface) or remove the fillColor attribute so the vector can be tinted at usage via app:tint; update the heat.xml vector's android:fillColor to reference the chosen resource/attribute or delete the attribute to enable runtime tinting.app/src/main/res/drawable/refresh.xml (1)
7-8: Consider using a theme-aware tint instead of a hardcoded fill color.
android:fillColor="#B7B7B7"is a fixed gray that won't respond to light/dark mode switching or Material You dynamic color. For widget icons in particular, the system theme can vary independently of the app theme, making adaptive coloring especially important.♻️ Proposed refactor — switch to a theme-attributed tint
<path - android:pathData="M480,800q-134,0 -227,-93t-93,-227..." - android:fillColor="#B7B7B7"/> + android:pathData="M480,800q-134,0 -227,-93t-93,-227..." + android:fillColor="@android:color/white"/>Then apply the tint at the call site (e.g., in the widget
RemoteViews) viasetColorFilter, or define a dedicated color resource (e.g.,@color/icon_on_widget) and reference it here — keeping the path fill neutral and controlling color externally.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/res/drawable/refresh.xml` around lines 7 - 8, The drawable hardcodes android:fillColor="#B7B7B7" (in refresh.xml) which prevents theme-aware coloring; remove or neutralize the fillColor (leave path fill transparent or use a neutral color), and switch to tinting the vector at runtime by applying a theme attribute or color resource (e.g., `@color/icon_on_widget` or ?attr/colorControlNormal) from the widget/RemoteViews—use setColorFilter/setImageViewTintList or setImageViewBitmap with a tinted bitmap—so the icon responds to light/dark/Material You; keep the existing pathData intact and only change the fill handling and call sites that set the tint.app/src/main/res/drawable/arrow_forward.xml (1)
8-8: Replace hardcoded fill color with a theme attribute to ensure the icon adapts across dark and light themes.The hardcoded color
#B7B7B7appears in at least six usage locations without tint applied (ChangelogDialog.kt, Changelog.kt, and multiple instances in RootPage.kt). This causes the icon to display as a fixed gray regardless of the active theme.🎨 Proposed refactor
- android:fillColor="#B7B7B7"/> + android:fillColor="?attr/colorOnSurfaceVariant"/>This ensures the color adapts automatically without relying on tint application at each usage site.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/res/drawable/arrow_forward.xml` at line 8, Replace the hardcoded fillColor="#B7B7B7" in arrow_forward.xml with a theme attribute so the drawable adapts to dark/light themes; locate the android:fillColor attribute in arrow_forward.xml and change it to reference an appropriate theme color attribute (e.g., ?attr/colorOnSurface or ?attr/colorControlNormal/textColorSecondary depending on your design system), then ensure usages (ChangelogDialog.kt, Changelog.kt, RootPage.kt) rely on the drawable's theme-aware color rather than per-use tinting.app/src/main/res/drawable/heat_outlined.xml (1)
8-8: All drawable icons in this project use the same hardcoded gray color—this is a consistent pattern, not an oversight.The hardcoded
#B7B7B7appears in all 10 drawable XML files (alarm.xml, analytics.xml, arrow_forward.xml, check_circle.xml, check_list.xml, circle_border.xml, heat.xml, heat_outlined.xml, refresh.xml, view_week.xml). The project's color resources (colors.xml) currently define only Material Design palette colors (purples, teals, black, white) with no gray icon color token.If the project is planned to support dark/light theming in future, all icon drawables should be refactored together to use a theme attribute or shared color resource, not individually. This would be a coordinated, project-wide change rather than isolated to this file.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/res/drawable/heat_outlined.xml` at line 8, The drawables (including heat_outlined.xml) currently hardcode android:fillColor="#B7B7B7"; change these to reference a shared token instead of hardcoding—replace android:fillColor with either a theme attribute (e.g., ?attr/iconColor) or a color resource (e.g., `@color/icon_gray`) and update all 10 drawable files (alarm.xml, analytics.xml, arrow_forward.xml, check_circle.xml, check_list.xml, circle_border.xml, heat.xml, heat_outlined.xml, refresh.xml, view_week.xml) consistently; then add the new color resource in colors.xml or declare the attribute in your theme so icons will follow light/dark theming.app/src/main/java/com/shub39/grit/habits/data/repository/HabitRepository.kt (1)
136-146: Missing.flowOn(Dispatchers.Default)for consistency and thread safety.
getHabitsWithAnalytics()uses.flowOn(Dispatchers.Default)to keep itscombine/map work off the main thread.getHabitsWithStatus()performs equivalent CPU-bound work without that constraint, leaving it at the mercy of the collector's dispatcher.♻️ Proposed fix
override fun getHabitsWithStatus(): Flow<List<Pair<Habit, Boolean>>> { return habits.combine(habitStatuses) { habitsFlow, statusFlow -> habitsFlow.map { habit -> val dates = statusFlow .filter { it.habitId == habit.id } .map { it.date } habit to dates.any { it == LocalDate.now() } } - } + }.flowOn(Dispatchers.Default) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/habits/data/repository/HabitRepository.kt` around lines 136 - 146, The getHabitsWithStatus() implementation does CPU-bound combine/map work but lacks thread confinement; update the function (getHabitsWithStatus) to apply .flowOn(Dispatchers.Default) to the returned Flow so the combine/map runs off the main thread, and add an import for kotlinx.coroutines.Dispatchers if necessary; ensure the return type remains Flow<List<Pair<Habit, Boolean>>> and place .flowOn(Dispatchers.Default) after the combine block.app/src/main/java/com/shub39/grit/widgets/WidgetSize.kt (1)
5-9:data objectadds unnecessary data-class semantics to a constants holder.Plain
objectsuffices sincetoString,equals, andhashCodegenerated bydata objectserve no purpose here.♻️ Proposed fix
-data object WidgetSize { +object WidgetSize {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/widgets/WidgetSize.kt` around lines 5 - 9, Replace the unnecessary data object with a plain singleton object: change the declaration of WidgetSize from a `data object` to `object WidgetSize` so the constants (Width2, Width4, Height1, Height2) remain accessible but no data-class semantics (generated equals/hashCode/toString) are created for this constants holder; update any imports/usages if needed to reflect the object name remains the same.app/src/main/java/com/shub39/grit/widgets/habit_overview_widget/HabitOverviewWidget.kt (1)
77-95:updateAllraces ahead of the DB write, momentarily showing stale data.
onUpdateHabitlaunches coroutine A (DB write) andonUpdateWidgetindependently launches coroutine B (updateAll). Coroutine B typically finishes first, causing the widget to re-render from the Flow's current (pre-write) state before the DB write completes. MoveupdateAllinside the DB-write coroutine so it fires only after the mutation is committed.♻️ Proposed fix
-onUpdateHabit = { habitWithStatus -> - scope.launch { - if (habitWithStatus.second) { - repo.deleteHabitStatus( - habitId = habitWithStatus.first.id, - date = LocalDate.now() - ) - } else { - repo.insertHabitStatus( - habitStatus = HabitStatus( - habitId = habitWithStatus.first.id, - date = LocalDate.now() - ) - ) - } - } -}, -onUpdateWidget = { - scope.launch { [email protected](context) } -} +onUpdateHabit = { habitWithStatus -> + scope.launch { + if (habitWithStatus.second) { + repo.deleteHabitStatus( + habitId = habitWithStatus.first.id, + date = LocalDate.now() + ) + } else { + repo.insertHabitStatus( + habitStatus = HabitStatus( + habitId = habitWithStatus.first.id, + date = LocalDate.now() + ) + ) + } + [email protected](context) + } +}, +onUpdateWidget = { + scope.launch { [email protected](context) } +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/widgets/habit_overview_widget/HabitOverviewWidget.kt` around lines 77 - 95, The widget update races because onUpdateHabit and onUpdateWidget launch separate coroutines; change onUpdateHabit so the same coroutine that calls repo.deleteHabitStatus or repo.insertHabitStatus also calls [email protected](context) after the suspend DB call completes (i.e., inside the scope.launch in the onUpdateHabit lambda), ensuring updateAll runs only after repo.deleteHabitStatus / repo.insertHabitStatus finish rather than launching an independent coroutine.app/src/main/java/com/shub39/grit/core/domain/GritDatastore.kt (1)
51-52: Minor naming inconsistency:updatevssetconvention.All other setter methods in this interface use the
set*prefix (e.g.,setAppTheme,setSeedColor,setAmoledPref), but this one usesupdateLastChangelogShown. Consider renaming tosetLastChangelogShownfor consistency, or vice versa ifupdateis the preferred naming going forward.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/core/domain/GritDatastore.kt` around lines 51 - 52, The method name updateLastChangelogShown is inconsistent with the rest of the interface’s setter naming (setAppTheme, setSeedColor, setAmoledPref); rename updateLastChangelogShown to setLastChangelogShown (and update all call sites and implementations of GritDatastore along with any tests) so the getter/getter pair is getLastChangelogShown / setLastChangelogShown and naming is consistent across the interface.app/src/main/java/com/shub39/grit/widgets/habit_streak_widget/HabitStreakWidget.kt (1)
96-107: Cycling logic is safe due to UI gating, but consider a guard.When
sortedDatais empty,currentIndexis 0 andsortedData.size - 1is -1, so theelsebranch would callsortedData[1]causing anIndexOutOfBoundsException. This is safe in practice because the arrow button is only rendered whenhabitWithAnalytics != null(Line 181). However, a defensive check would prevent a crash if the UI structure changes.🛡️ Defensive guard
onChangeHabit = { + if (sortedData.isEmpty()) return@Content val nextId = if (currentIndex == sortedData.size - 1) { sortedData.first().habit.id } else { sortedData[currentIndex + 1].habit.id }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/widgets/habit_streak_widget/HabitStreakWidget.kt` around lines 96 - 107, Add a defensive guard in the onChangeHabit block to handle an empty sortedData before computing nextId: check that sortedData.isNotEmpty() (or sortedData.firstOrNull() != null) and that currentIndex is within bounds, and if not, return early (or skip calling updateHabitId). Specifically update the onChangeHabit lambda that computes nextId using currentIndex and sortedData, ensuring you only call scope.launch { updateHabitId(context, id, nextId) } when a valid nextId was determined from sortedData to avoid IndexOutOfBoundsException.app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/Changelog.kt (1)
75-108: Consider providing stable keys for lazy list items.The
forEach+ nesteditem/itemsapproach works but lacks stable keys. If the changelog data ever changes during the composable's lifetime (or for animation/state restoration), keyed items are safer. Since the data is static today this is a minor hardening.Suggested improvement
changelog.forEach { versionEntry -> item(key = "header-${versionEntry.version}") { Text( text = "# ${versionEntry.version}", ... ) } - items(versionEntry.changes) { change -> + items( + items = versionEntry.changes, + key = { "${versionEntry.version}-$it" } + ) { change -> ... } item(key = "spacer-${versionEntry.version}") { Spacer(modifier = Modifier.height(16.dp)) } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/Changelog.kt` around lines 75 - 108, Replace the current changelog.forEach + nested item/items usage with keyed lazy list entries so items have stable keys: iterate the top-level list using LazyListScope.items(changelog, key = { it.version }) (instead of changelog.forEach) so each version header and its content are tied to the version key, and for the inner changes use items(versionEntry.changes, key = { change /* or index if duplicates possible */ }) (instead of items(versionEntry.changes) without keys) or supply item(key = versionEntry.version) for the header; update references to the existing item and items(...) calls accordingly to ensure stable keys for both version entries and change rows.app/src/main/java/com/shub39/grit/app/GritApplication.kt (1)
36-42: Unhandled exceptions will silently cancel the coroutine and skip remaining widget previews.If
setWidgetPreviewsthrows for any receiver (e.g., missing widget metadata), subsequent receivers in the same coroutine won't get their previews set. Consider wrapping each call or launching independently.Also,
MainScope()is a fire-and-forget scope here—acceptable forApplication, but if you ever need structured shutdown, store the scope reference and cancel it.Suggested improvement
- `@SuppressLint`("CheckResult") - MainScope().launch { - manager.setWidgetPreviews(HabitOverviewWidgetReceiver::class) - manager.setWidgetPreviews(HabitStreakWidgetReceiver::class) - manager.setWidgetPreviews(AllTasksWidgetReceiver::class) - manager.setWidgetPreviews(HabitWeekChartWidgetReceiver::class) - } + val widgetReceivers = listOf( + HabitOverviewWidgetReceiver::class, + HabitStreakWidgetReceiver::class, + AllTasksWidgetReceiver::class, + HabitWeekChartWidgetReceiver::class, + ) + `@SuppressLint`("CheckResult") + MainScope().launch { + widgetReceivers.forEach { receiver -> + try { + manager.setWidgetPreviews(receiver) + } catch (e: Exception) { + android.util.Log.e("GritApplication", "Failed to set widget preview for $receiver", e) + } + } + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/app/GritApplication.kt` around lines 36 - 42, The coroutine launched with MainScope().launch calls manager.setWidgetPreviews for multiple receivers and will silently stop if any call throws; change to either wrap each manager.setWidgetPreviews(HabitOverviewWidgetReceiver::class), manager.setWidgetPreviews(HabitStreakWidgetReceiver::class), manager.setWidgetPreviews(AllTasksWidgetReceiver::class), manager.setWidgetPreviews(HabitWeekChartWidgetReceiver::class) in individual try/catch blocks to log/handle exceptions so one failure doesn't cancel the rest, or launch separate coroutines per receiver (e.g., scope.launch { ... }) so failures are isolated; also persist the MainScope() as a field (instead of fire-and-forget) so you can cancel it during shutdown if you need structured lifecycle management.app/src/main/java/com/shub39/grit/viewmodels/SettingsViewModel.kt (1)
138-146:getChangeLogs()is already called from withinonStart, which runs in the flow collection context — the extraviewModelScope.launchis redundant but harmless.The
onStartblock already executes in a coroutine context (it's a flow operator). The innerviewModelScope.launchcreates a separate coroutine that runs independently, meaning if the subscriber disappears immediately, this coroutine still completes. This is consistent with the existingobserveJob()pattern on Line 148, so no change is strictly needed, but worth noting for awareness.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/viewmodels/SettingsViewModel.kt` around lines 138 - 146, getChangeLogs() launches a redundant coroutine inside a caller that is already in a flow context; remove the inner viewModelScope.launch and perform the work directly so the call runs in the existing coroutine (i.e., replace the viewModelScope.launch block in getChangeLogs() with a direct _state.update that assigns changelog = changelogManager.changelogs.first()), keeping the same use of _state.update and changelogManager.changelogs.first(); if callers expect a suspending call, make getChangeLogs a suspend function or ensure callers invoke it from a coroutine context consistent with observeJob().app/src/main/java/com/shub39/grit/viewmodels/MainViewModel.kt (1)
122-122: In DEBUG builds, the changelog dialog will always appear on every app launch.The condition
BuildConfig.DEBUG || lastShownChangelog != BuildConfig.VERSION_NAMEshort-circuits in debug mode, so the dialog shows every time regardless of the persisted version. If this is intentional for development/testing, consider adding a brief comment to clarify intent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/viewmodels/MainViewModel.kt` at line 122, Split the current combined condition in MainViewModel so intent is explicit: replace the single if (BuildConfig.DEBUG || lastShownChangelog != BuildConfig.VERSION_NAME) with two branches — one for debug-only behavior (if (BuildConfig.DEBUG) { /* show changelog for testing */ }) and a separate branch checking persisted version (else if (lastShownChangelog != BuildConfig.VERSION_NAME) { ... }), or if you prefer to keep a single condition, add a clear comment next to the condition explaining that showing the changelog on every debug launch is intentional for testing; reference the BuildConfig.DEBUG flag, lastShownChangelog variable and BuildConfig.VERSION_NAME in your change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/build.gradle.kts`:
- Around line 158-216: The generated JSON may contain unescaped backslashes
because the current escaping only handles quotes at the point where item is
processed in the generateChangelogJson task (see the use of item.replace("\"",
"\\\"")); update the escape logic to first replace backslashes then quotes
(e.g., replace "\" with "\\\\" before escaping quotes) or switch to a proper
JSON serializer to build the changelog objects, ensuring the transformation is
applied inside the loop that constructs each entry's "changes" strings.
In `@app/src/main/assets/changelog.json`:
- Around line 3-12: changelog.json's "5.6.0" changes array is missing the entry
"Fixed settings UI on landscape mode"; run the Gradle task generateChangelogJson
(e.g., ./gradlew :app:generateChangelogJson) to regenerate
app/src/main/assets/changelog.json so the version "5.6.0" block in
changelog.json matches CHANGELOG.md and includes that missing entry.
In `@app/src/main/java/com/shub39/grit/core/data/ChangelogManager.kt`:
- Around line 20-29: getChangelogs() currently does synchronous file I/O and
JSON parsing on the collector dispatcher and has no error handling; change it to
perform the asset read and Json.decodeFromString<Changelog>(...) inside
withContext(Dispatchers.IO) (or otherwise on an IO dispatcher), wrap the
read+parse in a try/catch to handle
FileNotFoundException/IOException/SerializationException, and on error post a
safe fallback (e.g., an empty/default Changelog) to _changelogs.update; ensure
only the IO work is moved to Dispatchers.IO and that you still call
_changelogs.update from a coroutine context safe for state updates.
In
`@app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/RootPage.kt`:
- Around line 384-388: Replace the hardcoded "Changelog" literal in the
headlineContent Text inside RootPage.kt with the localized string resource;
locate the Text call in the headlineContent lambda (the Text composable
rendering "Changelog") and change it to use stringResource(Res.string.changelog)
(Res.string.changelog is already defined and imported in the sibling
Changelog.kt), ensuring the proper import for
androidx.compose.ui.res.stringResource is present.
In `@app/src/main/java/com/shub39/grit/viewmodels/MainViewModel.kt`:
- Around line 117-129: The current checkChangelog() both sets currentChangelog
in state and calls datastore.updateLastChangelogShown(BuildConfig.VERSION_NAME),
which may persist the version before the UI is shown; remove the persistence
from checkChangelog() so it only sets _state.update { it.copy(currentChangelog =
changeLogs.firstOrNull()) } (using changelogManager.changelogs.first() and
BuildConfig.VERSION_NAME as before), and instead perform
datastore.updateLastChangelogShown(BuildConfig.VERSION_NAME) inside
dismissChangelog() when the user actually closes/dismisses the dialog; ensure
dismissChangelog() clears currentChangelog from state and then calls
datastore.updateLastChangelogShown(...) so the shown flag is only saved after
user acknowledgment.
In
`@app/src/main/java/com/shub39/grit/widgets/habit_streak_widget/HabitStreakWidget.kt`:
- Around line 255-295: Replace all hardcoded user-facing strings in
HabitStreakWidget.kt (the Text calls that currently use "Current Streak", "Best
Streak", and the " Day " suffix) with string resources: add keys like
current_streak, best_streak, and day_suffix (or a formatted string with a %d
placeholder) to res/values/strings.xml, then update the Text invocations to use
context.getString(R.string.current_streak),
context.getString(R.string.best_streak), and
context.getString(R.string.day_suffix) (or
context.getString(R.string.day_with_count, habitWithAnalytics.currentStreak)) so
the streak number is formatted via getString rather than concatenating literal "
Day ". Ensure you use the same context instance used elsewhere in this file (the
one used for context.getString at line ~391) or obtain LocalContext when needed.
- Around line 360-378: In the narrow-layout branch of the HabitStreakWidget (the
Column that renders the best-streak Texts), the secondary-background content is
incorrectly using GlanceTheme.colors.onPrimary for the
“${habitWithAnalytics.bestStreak} Day ” and/or the "Best Streak" Text; change
those Text style color usages to GlanceTheme.colors.onSecondary to match the
wide-width branch and ensure readable contrast (locate the Column/Text blocks in
HabitStreakWidget.kt that render the best streak in the narrow branch and swap
onPrimary -> onSecondary in their TextStyle color properties).
In
`@app/src/main/java/com/shub39/grit/widgets/habit_weekchart_widget/HabitWeekChartWidget.kt`:
- Around line 227-262: In HabitWeekChartWidget.kt, fix the division-by-zero and
premature rounding in the bar height calculation: compute maxData as now but if
maxData == 0.0 treat the scale ratio as 0.0 to avoid NaN (or use
maxData.coerceAtLeast(epsilon) for a safe denominator), calculate the height
using the raw double (e.g., ratio = double / maxData) rather than
double.roundToInt(), and only round for the displayed label (keep Text using
double.roundToInt() or formatted value); update the expression in the
Box.height(...) inside the data.forEach block to use the safe ratio-based
computation so heights are stable and proportional when values are small or all
zeros.
In `@app/src/main/res/drawable/analytics.xml`:
- Line 8: Replace the hardcoded android:fillColor="#B7B7B7" in analytics.xml
with a theme-aware or named color resource: either reference a theme attribute
like ?attr/colorOnSurface (or another appropriate Material attribute) to support
light/dark/dynamic color, or extract the hex into a named color in colors.xml
(e.g., `@color/neutral_grey`) and reference that; update any usages to use the new
resource/attribute so the icon adapts to theme changes.
In `@app/src/main/res/drawable/arrow_forward.xml`:
- Around line 1-5: The vector drawable for the arrow_forward icon needs RTL
mirroring: add android:autoMirrored="true" to the root <vector> element so the
icon flips automatically in RTL locales; locate the <vector
xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp"
android:height="24dp" android:viewportWidth="960" android:viewportHeight="960">
and update it to include android:autoMirrored="true".
In `@app/src/main/res/drawable/heat_outlined.xml`:
- Around line 6-8: The path element in heat_outlined.xml is missing the
android:fillType="evenOdd" attribute causing inner voids to render filled;
update the <path> (the element with android:pathData starting "M239,801.19...")
to include android:fillType="evenOdd" alongside android:fillColor so the
outlined flame's internal cutouts render correctly.
In `@app/src/main/res/xml/habit_streak_widget.xml`:
- Around line 5-13: The widget currently sets android:maxResizeHeight equal to
android:minHeight/android:minResizeHeight, which makes vertical resizing a
no-op; either (A) if fixed height is intended, change
android:resizeMode="horizontal|vertical" to android:resizeMode="horizontal" so
vertical resize is disabled, or (B) if vertical resize should be allowed,
increase android:maxResizeHeight (for example to "422dp" like in
all_tasks_widget.xml) so there is a positive vertical resize range; update the
android:maxResizeHeight attribute (and android:maxResizeWidth if needed)
accordingly while keeping android:minHeight and android:minResizeHeight as-is.
In `@app/src/main/res/xml/habit_week_chart_widget.xml`:
- Around line 5-13: The widget's vertical resize is ineffective because
android:maxResizeHeight ("132dp") equals android:minHeight and
android:minResizeHeight (all "132dp"), so adjust the XML: either increase
android:maxResizeHeight to a larger dp value (e.g., >132dp) to permit vertical
expansion, or change android:resizeMode from "horizontal|vertical" to
"horizontal" only; update the attributes android:maxResizeHeight,
android:minHeight, android:minResizeHeight and android:resizeMode in
habit_week_chart_widget.xml accordingly so the chosen behavior (resizable
vertically or fixed vertically) is enforced.
In `@shared/core/src/commonMain/composeResources/values-hu/strings.xml`:
- Line 117: The localized string with key "weeks" in values-hu (string
name="weeks") was changed to "Hét" (singular) but likely needs the Hungarian
plural "Hetek" for contexts like "X weeks ago" or plural unit labels; check
where the "weeks" resource is used (e.g., timeline/relative-time formatting,
unit labels, Compose components referencing R.string.weeks) and if it is
displayed as a plural/unit or with a numeric prefix, revert the translation back
to "Hetek" (or use the appropriate plural resource) to match the usage context.
In `@shared/core/src/commonMain/composeResources/values-ru/strings.xml`:
- Line 139: The string resource server_channel_description starts with a
lowercase letter; update its value to begin with an uppercase letter to match
other channel/description strings (e.g., make "канал для отправки серверу
статуса обновлений" into "Канал для отправки серверу статуса обновлений") so the
resource key server_channel_description follows the same capitalization style as
channel_description and similar entries.
In `@shared/core/src/commonMain/composeResources/values-zh-rCN/strings.xml`:
- Line 120: The translation for the string resource with name "weeks" currently
uses "周内", which implies "within the week" and may not match the intended
neutral plural label; update the value of the <string name="weeks"> entry to a
more appropriate neutral translation such as "周" or "数周" so the label correctly
conveys "weeks" as a standalone term.
---
Nitpick comments:
In `@app/src/main/java/com/shub39/grit/app/GritApplication.kt`:
- Around line 36-42: The coroutine launched with MainScope().launch calls
manager.setWidgetPreviews for multiple receivers and will silently stop if any
call throws; change to either wrap each
manager.setWidgetPreviews(HabitOverviewWidgetReceiver::class),
manager.setWidgetPreviews(HabitStreakWidgetReceiver::class),
manager.setWidgetPreviews(AllTasksWidgetReceiver::class),
manager.setWidgetPreviews(HabitWeekChartWidgetReceiver::class) in individual
try/catch blocks to log/handle exceptions so one failure doesn't cancel the
rest, or launch separate coroutines per receiver (e.g., scope.launch { ... }) so
failures are isolated; also persist the MainScope() as a field (instead of
fire-and-forget) so you can cancel it during shutdown if you need structured
lifecycle management.
In `@app/src/main/java/com/shub39/grit/core/domain/GritDatastore.kt`:
- Around line 51-52: The method name updateLastChangelogShown is inconsistent
with the rest of the interface’s setter naming (setAppTheme, setSeedColor,
setAmoledPref); rename updateLastChangelogShown to setLastChangelogShown (and
update all call sites and implementations of GritDatastore along with any tests)
so the getter/getter pair is getLastChangelogShown / setLastChangelogShown and
naming is consistent across the interface.
In
`@app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/Changelog.kt`:
- Around line 75-108: Replace the current changelog.forEach + nested item/items
usage with keyed lazy list entries so items have stable keys: iterate the
top-level list using LazyListScope.items(changelog, key = { it.version })
(instead of changelog.forEach) so each version header and its content are tied
to the version key, and for the inner changes use items(versionEntry.changes,
key = { change /* or index if duplicates possible */ }) (instead of
items(versionEntry.changes) without keys) or supply item(key =
versionEntry.version) for the header; update references to the existing item and
items(...) calls accordingly to ensure stable keys for both version entries and
change rows.
In `@app/src/main/java/com/shub39/grit/habits/data/repository/HabitRepository.kt`:
- Around line 136-146: The getHabitsWithStatus() implementation does CPU-bound
combine/map work but lacks thread confinement; update the function
(getHabitsWithStatus) to apply .flowOn(Dispatchers.Default) to the returned Flow
so the combine/map runs off the main thread, and add an import for
kotlinx.coroutines.Dispatchers if necessary; ensure the return type remains
Flow<List<Pair<Habit, Boolean>>> and place .flowOn(Dispatchers.Default) after
the combine block.
In `@app/src/main/java/com/shub39/grit/viewmodels/MainViewModel.kt`:
- Line 122: Split the current combined condition in MainViewModel so intent is
explicit: replace the single if (BuildConfig.DEBUG || lastShownChangelog !=
BuildConfig.VERSION_NAME) with two branches — one for debug-only behavior (if
(BuildConfig.DEBUG) { /* show changelog for testing */ }) and a separate branch
checking persisted version (else if (lastShownChangelog !=
BuildConfig.VERSION_NAME) { ... }), or if you prefer to keep a single condition,
add a clear comment next to the condition explaining that showing the changelog
on every debug launch is intentional for testing; reference the
BuildConfig.DEBUG flag, lastShownChangelog variable and BuildConfig.VERSION_NAME
in your change.
In `@app/src/main/java/com/shub39/grit/viewmodels/SettingsViewModel.kt`:
- Around line 138-146: getChangeLogs() launches a redundant coroutine inside a
caller that is already in a flow context; remove the inner viewModelScope.launch
and perform the work directly so the call runs in the existing coroutine (i.e.,
replace the viewModelScope.launch block in getChangeLogs() with a direct
_state.update that assigns changelog = changelogManager.changelogs.first()),
keeping the same use of _state.update and changelogManager.changelogs.first();
if callers expect a suspending call, make getChangeLogs a suspend function or
ensure callers invoke it from a coroutine context consistent with observeJob().
In
`@app/src/main/java/com/shub39/grit/widgets/habit_overview_widget/HabitOverviewWidget.kt`:
- Around line 77-95: The widget update races because onUpdateHabit and
onUpdateWidget launch separate coroutines; change onUpdateHabit so the same
coroutine that calls repo.deleteHabitStatus or repo.insertHabitStatus also calls
[email protected](context) after the suspend DB call completes
(i.e., inside the scope.launch in the onUpdateHabit lambda), ensuring updateAll
runs only after repo.deleteHabitStatus / repo.insertHabitStatus finish rather
than launching an independent coroutine.
In
`@app/src/main/java/com/shub39/grit/widgets/habit_streak_widget/HabitStreakWidget.kt`:
- Around line 96-107: Add a defensive guard in the onChangeHabit block to handle
an empty sortedData before computing nextId: check that sortedData.isNotEmpty()
(or sortedData.firstOrNull() != null) and that currentIndex is within bounds,
and if not, return early (or skip calling updateHabitId). Specifically update
the onChangeHabit lambda that computes nextId using currentIndex and sortedData,
ensuring you only call scope.launch { updateHabitId(context, id, nextId) } when
a valid nextId was determined from sortedData to avoid
IndexOutOfBoundsException.
In `@app/src/main/java/com/shub39/grit/widgets/WidgetSize.kt`:
- Around line 5-9: Replace the unnecessary data object with a plain singleton
object: change the declaration of WidgetSize from a `data object` to `object
WidgetSize` so the constants (Width2, Width4, Height1, Height2) remain
accessible but no data-class semantics (generated equals/hashCode/toString) are
created for this constants holder; update any imports/usages if needed to
reflect the object name remains the same.
In `@app/src/main/res/drawable/arrow_forward.xml`:
- Line 8: Replace the hardcoded fillColor="#B7B7B7" in arrow_forward.xml with a
theme attribute so the drawable adapts to dark/light themes; locate the
android:fillColor attribute in arrow_forward.xml and change it to reference an
appropriate theme color attribute (e.g., ?attr/colorOnSurface or
?attr/colorControlNormal/textColorSecondary depending on your design system),
then ensure usages (ChangelogDialog.kt, Changelog.kt, RootPage.kt) rely on the
drawable's theme-aware color rather than per-use tinting.
In `@app/src/main/res/drawable/check_circle.xml`:
- Line 8: Replace the hardcoded hex fillColor in check_circle.xml
(android:fillColor="#B7B7B7") with a color resource or theme attribute so the
icon adapts to theming; create or reuse a color in res/values/colors.xml (e.g.,
ic_tint) or reference a theme attribute (e.g., ?attr/iconTint) and update the
android:fillColor in the drawable to use that resource reference instead.
In `@app/src/main/res/drawable/check_list.xml`:
- Line 8: The drawable uses a hardcoded android:fillColor="#B7B7B7" which
prevents theme/dark-mode adaptation; replace hardcoded literals in
check_list.xml (and the other added drawables: alarm.xml, analytics.xml,
arrow_forward.xml, check_circle.xml, circle_border.xml, heat.xml,
heat_outlined.xml, refresh.xml, view_week.xml) with a theme-aware reference such
as `@color/widget_icon_tint` or an attribute like ?attr/colorControlNormal, and
add corresponding color resources in res/values/colors.xml and
res/values-night/colors.xml (or ensure the attribute is provided by your theme)
so the icon tint adapts to light/dark/dynamic themes.
In `@app/src/main/res/drawable/heat_outlined.xml`:
- Line 8: The drawables (including heat_outlined.xml) currently hardcode
android:fillColor="#B7B7B7"; change these to reference a shared token instead of
hardcoding—replace android:fillColor with either a theme attribute (e.g.,
?attr/iconColor) or a color resource (e.g., `@color/icon_gray`) and update all 10
drawable files (alarm.xml, analytics.xml, arrow_forward.xml, check_circle.xml,
check_list.xml, circle_border.xml, heat.xml, heat_outlined.xml, refresh.xml,
view_week.xml) consistently; then add the new color resource in colors.xml or
declare the attribute in your theme so icons will follow light/dark theming.
In `@app/src/main/res/drawable/heat.xml`:
- Line 8: The drawable hardcodes a static fill color
("android:fillColor=\"#B7B7B7\"") which prevents theme-aware tinting; replace
that literal with a color resource or theme attribute (e.g., use a named color
like `@color/icon_tint_default` or a theme attribute like ?attr/colorOnSurface) or
remove the fillColor attribute so the vector can be tinted at usage via
app:tint; update the heat.xml vector's android:fillColor to reference the chosen
resource/attribute or delete the attribute to enable runtime tinting.
In `@app/src/main/res/drawable/refresh.xml`:
- Around line 7-8: The drawable hardcodes android:fillColor="#B7B7B7" (in
refresh.xml) which prevents theme-aware coloring; remove or neutralize the
fillColor (leave path fill transparent or use a neutral color), and switch to
tinting the vector at runtime by applying a theme attribute or color resource
(e.g., `@color/icon_on_widget` or ?attr/colorControlNormal) from the
widget/RemoteViews—use setColorFilter/setImageViewTintList or setImageViewBitmap
with a tinted bitmap—so the icon responds to light/dark/Material You; keep the
existing pathData intact and only change the fill handling and call sites that
set the tint.
app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/RootPage.kt
Show resolved
Hide resolved
Signed-off-by: shub39 <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/Changelog.kt (2)
38-41: Consider renaming toChangelogScreento avoid shadowing theChangelogtype alias.The composable function name
Changelogis identical to the imported type aliasChangelog = List<VersionEntry>(line 26). While Kotlin resolves this by context, it hampers readability at call sites and in navigation graphs. A name likeChangelogScreenwould disambiguate cleanly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/Changelog.kt` around lines 38 - 41, Rename the composable function Changelog to ChangelogScreen to avoid shadowing the Changelog type alias (Changelog = List<VersionEntry>). Update the function declaration public symbol Changelog(...) to ChangelogScreen(...), and update all call sites and navigation references that invoke Changelog (including any NavGraph routes, composable() usages, and imports) to use ChangelogScreen so the type alias and composable are no longer identically named.
74-106: Provide stable keys for LazyColumn items to improve recomposition efficiency.The
itemanditemscalls inside theforEachloop lack keys. If the changelog list changes (e.g., new version prepended), LazyColumn will recompose all items instead of only the diff. Using the version string and change text as keys makes diffing efficient and also preserves scroll state.Proposed fix
changelog.forEach { versionEntry -> - item { + item(key = "header-${versionEntry.version}") { Text( text = versionEntry.version, style = MaterialTheme.typography.headlineLarge.copy( fontWeight = FontWeight.Bold ) ) } - items(versionEntry.changes) { change -> + items( + items = versionEntry.changes, + key = { "${versionEntry.version}-$it" } + ) { change -> Row(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/Changelog.kt` around lines 74 - 106, The LazyColumn entries produced inside changelog.forEach lack stable keys; update the header and spacer item calls to use item(key = versionEntry.version) and the changes list to use items(versionEntry.changes, key = { change -> change }) (or a composite key like "${versionEntry.version}-$change" if change text may duplicate) so headers, change rows and spacers have stable keys tied to versionEntry.version and change; adjust the spacer item to a keyed item (e.g., key = "spacer-${versionEntry.version}") to ensure correct diffing and preserved scroll state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/Changelog.kt`:
- Around line 56-59: The Icon's contentDescription is hardcoded ("Navigate
Back"); replace it with a localized stringResource call (e.g.,
stringResource(R.string.navigate_back) or the project's
Res.strings.navigate_back variant) so the back-button uses the same i18n pattern
as other text in this file (look for other uses of stringResource in
Changelog.kt to match style) and add the navigate_back entry to the app's
strings resources if it doesn't exist.
---
Nitpick comments:
In
`@app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/Changelog.kt`:
- Around line 38-41: Rename the composable function Changelog to ChangelogScreen
to avoid shadowing the Changelog type alias (Changelog = List<VersionEntry>).
Update the function declaration public symbol Changelog(...) to
ChangelogScreen(...), and update all call sites and navigation references that
invoke Changelog (including any NavGraph routes, composable() usages, and
imports) to use ChangelogScreen so the type alias and composable are no longer
identically named.
- Around line 74-106: The LazyColumn entries produced inside changelog.forEach
lack stable keys; update the header and spacer item calls to use item(key =
versionEntry.version) and the changes list to use items(versionEntry.changes,
key = { change -> change }) (or a composite key like
"${versionEntry.version}-$change" if change text may duplicate) so headers,
change rows and spacers have stable keys tied to versionEntry.version and
change; adjust the spacer item to a keyed item (e.g., key =
"spacer-${versionEntry.version}") to ensure correct diffing and preserved scroll
state.
app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/Changelog.kt
Show resolved
Hide resolved
Signed-off-by: shub39 <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@app/src/main/java/com/shub39/grit/widgets/habit_streak_widget/HabitStreakWidget.kt`:
- Around line 187-206: In HabitStreakWidget.kt the two interactive Image
composables (the refresh image that calls onUpdateWidget and the arrow_forward
image that calls onChangeHabit then onUpdateWidget) set contentDescription =
null — change them to meaningful descriptions (e.g., "Refresh" and "Next habit")
by supplying contentDescription strings (preferably from string resources like
R.string.refresh and R.string.next_habit) so screen readers announce the
controls; keep the existing clickable handlers (onUpdateWidget, onChangeHabit)
unchanged.
In
`@app/src/main/java/com/shub39/grit/widgets/habit_weekchart_widget/HabitWeekChartWidget.kt`:
- Around line 115-138: The preview provider providePreview currently calls
provideContent { Content(...) } without the GlanceTheme wrapper, so theme colors
used inside Content (GlanceTheme.colors.*) are missing; wrap the Content call
inside GlanceTheme { ... } in providePreview to mirror provideGlance's theming
(i.e., change providePreview to call provideContent { GlanceTheme { Content(...)
} }) so the preview uses the same theme as the real widget.
---
Duplicate comments:
In
`@app/src/main/java/com/shub39/grit/core/presentation/settings/ui/section/RootPage.kt`:
- Around line 368-394: The review indicates the changelog ListItem is correct
(uses stringResource(Res.string.changelog), detachedItemShape(), and
onNavigateToChangelog()), but contains a duplicate approval comment; remove the
duplicate review comment or mark the conversation as resolved so there is a
single approval for the changelog ListItem change.
In `@app/src/main/java/com/shub39/grit/viewmodels/MainViewModel.kt`:
- Around line 117-146: Remove the duplicate review annotation by deleting the
stray "[duplicate_comment]" marker from the PR comment; keep the implemented
behavior as-is (checkChangelog() only updates _state and dismissChangelog()
persists BuildConfig.VERSION_NAME after dismissal), and confirm no code changes
are needed to functions checkChangelog(), dismissChangelog(), or
checkSubscription().
In
`@app/src/main/java/com/shub39/grit/widgets/habit_weekchart_widget/HabitWeekChartWidget.kt`:
- Line 240: The bar height calculation is rounding the data too early: replace
the use of double.roundToInt() inside the ratio with the raw floating value so
the ratio is computed with full precision (i.e. change the expression used in
the .height call from (90 * (double.roundToInt() / maxData)).dp to compute 90 *
(double / maxData) and only round if/where needed after multiplying), ensuring
you use double.toFloat() or cast appropriately for .dp units and optionally
clamp the result to a non-negative range; update the code around the .height((90
* (double.roundToInt() / maxData)).dp) call in HabitWeekChartWidget to use
double / maxData instead of double.roundToInt().
| Box(GlanceModifier.padding(start = 16.dp)) { | ||
| Image( | ||
| provider = ImageProvider(R.drawable.refresh), | ||
| contentDescription = null, | ||
| colorFilter = ColorFilter.tint(GlanceTheme.colors.onSurface), | ||
| modifier = GlanceModifier.clickable { onUpdateWidget() } | ||
| ) | ||
| } | ||
| Spacer(modifier = GlanceModifier.width(4.dp)) | ||
| Box(GlanceModifier.padding(end = 16.dp)) { | ||
| Image( | ||
| provider = ImageProvider(R.drawable.arrow_forward), | ||
| contentDescription = null, | ||
| colorFilter = ColorFilter.tint(GlanceTheme.colors.onSurface), | ||
| modifier = GlanceModifier.clickable { | ||
| onChangeHabit() | ||
| onUpdateWidget() | ||
| } | ||
| ) | ||
| } |
There was a problem hiding this comment.
Interactive images lack contentDescription — weak accessibility.
Both the refresh (Line 190) and arrow_forward (Line 199) images are interactive (clickable) but have contentDescription = null. Screen readers will have no announcement for these controls. While this same pattern exists in HabitWeekChartWidget, adding concise descriptions (e.g., "Refresh" / "Next habit") would improve accessibility.
♿ Proposed improvement
Image(
provider = ImageProvider(R.drawable.refresh),
- contentDescription = null,
+ contentDescription = context.getString(R.string.refresh),
colorFilter = ColorFilter.tint(GlanceTheme.colors.onSurface),
modifier = GlanceModifier.clickable { onUpdateWidget() }
)
}
Spacer(modifier = GlanceModifier.width(4.dp))
Box(GlanceModifier.padding(end = 16.dp)) {
Image(
provider = ImageProvider(R.drawable.arrow_forward),
- contentDescription = null,
+ contentDescription = context.getString(R.string.next_habit),
colorFilter = ColorFilter.tint(GlanceTheme.colors.onSurface),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@app/src/main/java/com/shub39/grit/widgets/habit_streak_widget/HabitStreakWidget.kt`
around lines 187 - 206, In HabitStreakWidget.kt the two interactive Image
composables (the refresh image that calls onUpdateWidget and the arrow_forward
image that calls onChangeHabit then onUpdateWidget) set contentDescription =
null — change them to meaningful descriptions (e.g., "Refresh" and "Next habit")
by supplying contentDescription strings (preferably from string resources like
R.string.refresh and R.string.next_habit) so screen readers announce the
controls; keep the existing clickable handlers (onUpdateWidget, onChangeHabit)
unchanged.
| override suspend fun providePreview(context: Context, widgetCategory: Int) { | ||
| provideContent { | ||
| Content( | ||
| onUpdateWidget = {}, | ||
| onChangeHabit = {}, | ||
| habitWithAnalytics = HabitWithAnalytics( | ||
| habit = Habit( | ||
| id = 1, | ||
| title = "Exercise", | ||
| description = "40 mins daily", | ||
| time = LocalDateTime.now(), | ||
| days = setOf(), | ||
| index = 1, | ||
| reminder = false | ||
| ), | ||
| statuses = listOf(), | ||
| weeklyComparisonData = (0..7).map { it.toDouble() }, | ||
| weekDayFrequencyData = mapOf(), | ||
| currentStreak = 12, | ||
| bestStreak = 20, | ||
| startedDaysAgo = 100 | ||
| ) | ||
| ) | ||
| } |
There was a problem hiding this comment.
providePreview is missing the GlanceTheme {} wrapper present in provideGlance.
Content references GlanceTheme.colors.* throughout. Without the wrapper in providePreview, the widget picker preview will fall back to un-themed default colors, potentially misrepresenting the widget's actual appearance.
🐛 Proposed fix
override suspend fun providePreview(context: Context, widgetCategory: Int) {
provideContent {
+ GlanceTheme {
Content(
onUpdateWidget = {},
onChangeHabit = {},
habitWithAnalytics = HabitWithAnalytics(
...
)
)
+ }
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| override suspend fun providePreview(context: Context, widgetCategory: Int) { | |
| provideContent { | |
| Content( | |
| onUpdateWidget = {}, | |
| onChangeHabit = {}, | |
| habitWithAnalytics = HabitWithAnalytics( | |
| habit = Habit( | |
| id = 1, | |
| title = "Exercise", | |
| description = "40 mins daily", | |
| time = LocalDateTime.now(), | |
| days = setOf(), | |
| index = 1, | |
| reminder = false | |
| ), | |
| statuses = listOf(), | |
| weeklyComparisonData = (0..7).map { it.toDouble() }, | |
| weekDayFrequencyData = mapOf(), | |
| currentStreak = 12, | |
| bestStreak = 20, | |
| startedDaysAgo = 100 | |
| ) | |
| ) | |
| } | |
| override suspend fun providePreview(context: Context, widgetCategory: Int) { | |
| provideContent { | |
| GlanceTheme { | |
| Content( | |
| onUpdateWidget = {}, | |
| onChangeHabit = {}, | |
| habitWithAnalytics = HabitWithAnalytics( | |
| habit = Habit( | |
| id = 1, | |
| title = "Exercise", | |
| description = "40 mins daily", | |
| time = LocalDateTime.now(), | |
| days = setOf(), | |
| index = 1, | |
| reminder = false | |
| ), | |
| statuses = listOf(), | |
| weeklyComparisonData = (0..7).map { it.toDouble() }, | |
| weekDayFrequencyData = mapOf(), | |
| currentStreak = 12, | |
| bestStreak = 20, | |
| startedDaysAgo = 100 | |
| ) | |
| ) | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@app/src/main/java/com/shub39/grit/widgets/habit_weekchart_widget/HabitWeekChartWidget.kt`
around lines 115 - 138, The preview provider providePreview currently calls
provideContent { Content(...) } without the GlanceTheme wrapper, so theme colors
used inside Content (GlanceTheme.colors.*) are missing; wrap the Content call
inside GlanceTheme { ... } in providePreview to mirror provideGlance's theming
(i.e., change providePreview to call provideContent { GlanceTheme { Content(...)
} }) so the preview uses the same theme as the real widget.
Signed-off-by: shub39 <[email protected]>
Summary by CodeRabbit
New Features
Improvements