feat: in-app What's new sheet for #459#496
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a "What's New" feature: domain models and entries, ViewModel evaluation/force-show logic, persistence of last-seen version, AppVersionInfo/BuildKonfig wiring and DI, UI sheet and history screen, navigation/profile integration, and a 600ms debounce before showing the sheet. ChangesWhat's New changelog feature
Sequence DiagramsequenceDiagram
participant User
participant App
participant WhatsNewVM as WhatsNewViewModel
participant AppVersionInfo
participant TweaksRepo as TweaksRepository
participant WhatsNewEntries
participant UI as WhatsNewSheet
User->>App: Open app / navigate to home
App->>WhatsNewVM: instantiate ViewModel
WhatsNewVM->>AppVersionInfo: read versionCode
AppVersionInfo-->>WhatsNewVM: return versionCode
WhatsNewVM->>TweaksRepo: getLastSeenWhatsNewVersionCode()
TweaksRepo-->>WhatsNewVM: return lastSeen (nullable)
alt new or unseen version
WhatsNewVM->>WhatsNewEntries: lookup entry for versionCode
WhatsNewEntries-->>WhatsNewVM: return entry?
alt entry exists and showAsSheet
WhatsNewVM->>App: emit pendingEntry
else
WhatsNewVM->>TweaksRepo: setLastSeenWhatsNewVersionCode(current)
end
else already seen
WhatsNewVM->>TweaksRepo: setLastSeenWhatsNewVersionCode(current)
end
App->>App: debounce 600ms
App->>UI: render WhatsNewSheet when pendingEntry present
User->>UI: dismiss or view history
alt dismiss
UI->>WhatsNewVM: markSeen()
WhatsNewVM->>TweaksRepo: setLastSeenWhatsNewVersionCode(version)
else view history
UI->>App: navigate to WhatsNewHistoryScreen
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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: 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
`@composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt`:
- Around line 22-46: Wrap the body of evaluate() in a safe error-handling block
(use runCatching or try/catch) to prevent an IOException from propagating out of
viewModelScope.launch; catch exceptions from
tweaksRepository.getLastSeenWhatsNewVersionCode().first() and from any
tweaksRepository.setLastSeenWhatsNewWhatsNewVersionCode calls, log the error and
treat it as “seen” by setting last-seen to current (or bail out safely) so the
app won’t crash; update evaluate() accordingly (refer to evaluate(),
getLastSeenWhatsNewVersionCode(),
tweaksRepository.setLastSeenWhatsNewVersionCode(), and the viewModelScope.launch
usage and mirror the runCatching pattern used in ProfileViewModel).
In
`@core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/whatsnew/WhatsNewHistoryScreen.kt`:
- Around line 48-55: The back navigation Icon inside WhatsNewHistoryScreen
currently sets contentDescription = null which hides it from accessibility
services; update the Icon (inside the IconButton that calls onNavigateBack) to
provide a localized, descriptive contentDescription (e.g., use a string resource
like R.string.back or a passed-in localized label) so accessibility tools
announce the button as "Back" (or the localized equivalent) while leaving the
IconButton's onClick as-is; ensure the string is obtained via the appropriate
Compose localization API used in this module and referenced in the Icon's
contentDescription property.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 46aaafd7-af93-49f3-9a71-bb66f61cfd4a
📒 Files selected for processing (21)
build-logic/convention/src/main/kotlin/BuildKonfigConventionPlugin.ktcomposeApp/src/commonMain/kotlin/zed/rainxch/githubstore/Main.ktcomposeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/di/ViewModelsModule.ktcomposeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/navigation/AppNavigation.ktcomposeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/navigation/GithubStoreGraph.ktcomposeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/navigation/NavigationUtils.ktcomposeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.ktcore/data/src/commonMain/kotlin/zed/rainxch/core/data/di/SharedModule.ktcore/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.ktcore/data/src/commonMain/kotlin/zed/rainxch/core/data/services/BuildKonfigAppVersionInfo.ktcore/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/WhatsNewEntries.ktcore/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/WhatsNewEntry.ktcore/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.ktcore/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/AppVersionInfo.ktcore/presentation/src/commonMain/composeResources/values/strings.xmlcore/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/whatsnew/WhatsNewHistoryScreen.ktcore/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/whatsnew/WhatsNewSheet.ktfeature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileAction.ktfeature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileRoot.ktfeature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileViewModel.ktfeature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/components/sections/Options.kt
| navigationIcon = { | ||
| IconButton(onClick = onNavigateBack) { | ||
| Icon( | ||
| imageVector = Icons.AutoMirrored.Filled.ArrowBack, | ||
| contentDescription = null, | ||
| ) | ||
| } | ||
| }, |
There was a problem hiding this comment.
Provide a contentDescription for the back navigation icon.
contentDescription = null silences TalkBack entirely for this IconButton. "When you set the contentDescription parameter to null, you indicate to the Android framework that this element does not have associated actions or state" — the opposite of what a navigation icon button represents. The Android accessibility docs show that an Icon inside an IconButton should carry a localized contentDescription; only Text composables are exempt because accessibility services read the text itself.
🛡️ Proposed fix
- Icon(
- imageVector = Icons.AutoMirrored.Filled.ArrowBack,
- contentDescription = null,
- )
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = stringResource(Res.string.navigate_back), // add key if missing
+ )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/whatsnew/WhatsNewHistoryScreen.kt`
around lines 48 - 55, The back navigation Icon inside WhatsNewHistoryScreen
currently sets contentDescription = null which hides it from accessibility
services; update the Icon (inside the IconButton that calls onNavigateBack) to
provide a localized, descriptive contentDescription (e.g., use a string resource
like R.string.back or a passed-in localized label) so accessibility tools
announce the button as "Back" (or the localized equivalent) while leaving the
IconButton's onClick as-is; ensure the string is obtained via the appropriate
Compose localization API used in this module and referenced in the Icon's
contentDescription property.
Treating a null lastSeenWhatsNewVersionCode as Int.MIN_VALUE so the first launch after the feature ships still surfaces the sheet for the current version. Previously the controller silently advanced the bookmark to current and the sheet never appeared.
…hatsnew/WhatsNewViewModel.kt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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
`@composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt`:
- Around line 15-17: The class header for WhatsNewViewModel is malformed and
missing members; fix it by closing the constructor and adding the missing
constructor parameter "private val appVersionInfo: AppVersionInfo", change the
header to ") : ViewModel() {" so the class extends ViewModel, and restore the
missing state properties by adding "private val _pendingEntry =
MutableStateFlow<WhatsNewEntry?>(null)" and "val pendingEntry:
StateFlow<WhatsNewEntry?> = _pendingEntry.asStateFlow()"; ensure the imports for
MutableStateFlow, StateFlow and asStateFlow remain and that existing usages of
appVersionInfo, _pendingEntry and pendingEntry compile against these added
members.
- Around line 45-51: The markSeen() coroutine can throw a DataStore IOException
and crash because the call to tweaksRepository.setLastSeenWhatsNewVersionCode
inside viewModelScope.launch has no error handling; update markSeen() to catch
exceptions from that write (specifically IOException but you can catch
Exception/Throwable if preferred), e.g. wrap the repository call in a try/catch
inside the launched coroutine and handle/log the error (use processLogger/Logger
or viewModelScope's logger) so failures (full disk, IO issues) do not propagate
to the UncaughtExceptionHandler and crash the app; locate markSeen(),
viewModelScope.launch, and tweaksRepository.setLastSeenWhatsNewVersionCode to
apply the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: becec074-915c-4541-894f-551fbd616b85
📒 Files selected for processing (1)
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/whatsnew/WhatsNewViewModel.kt
DataStore writes can throw IOException (full disk, file lock). Without a catch, the exception propagated through viewModelScope.launch to the default uncaught handler and crashed the app on something as benign as a low-storage device dismissing the what's-new sheet. Wrap the init evaluate path and markSeen persistence in try/catch and log via Kermit so failures stay observable but non-fatal.
Closes #459.
Summary
ModalBottomSheetthat appears once perversionCodebump on first launch after an update.showAsSheet = falseso bug-fix-only patches stay silent and credible.HomeScreen, OAuth/rate-limit/session dialogs are not active, and a 600ms debounce has elapsed after Home becomes the destination.WhatsNewHistoryScreen).WhatsNewEntriesincore/domain— one entry per release, hand-written in concise voice.Why this shape
UX research (paraphrased): on update users have a low-grade "did anything change?" question. Without a surface, new features stay invisible and "you didn't tell me X existed" turns into support load. But over-using the surface (Slack/Notion-style) trains dismissal. The two big calibrations are:
lastSeenWhatsNewVersionCode < currentVersionCode, withshowAsSheet=falseper-version flag for silent patches.Files of note
core/domain/.../model/WhatsNewEntry.kt,WhatsNewEntries.kt— model + the entry list (edit this every release).core/domain/.../system/AppVersionInfo.kt— small interface;BuildKonfigAppVersionInforeads fromBuildKonfig.VERSION_CODE(newly exposed).core/presentation/.../components/whatsnew/WhatsNewSheet.kt,WhatsNewHistoryScreen.kt— the sheet and the history screen.composeApp/.../whatsnew/WhatsNewViewModel.kt— controller; computespendingEntry, persists viaTweaksRepository.setLastSeenWhatsNewVersionCode.composeApp/.../Main.kt— host + display gating.Edge cases covered
lastSeen == null) → mark current as seen, no sheet.lastSeen > current) → no-op;setLastSeenWhatsNewVersionCodeonly writes when the new value is greater, so downgrade can't poison the bookmark.What's NOT in this PR (resisted scope creep)
Test plan
lastSeenWhatsNewVersionCodeis set to current.projectVersionCodeto 16 ingradle/libs.versions.toml, add a 1.9.0 entry toWhatsNewEntries.allwithshowAsSheet=true, rebuild → sheet appears on launch after Home settles.showAsSheet=false→ no sheet,lastSeensilently advances to 16.AuthenticationScreen.Summary by CodeRabbit