feat: headlines widget v61 + OS widget#919
feat: headlines widget v61 + OS widget#919jvsena42 wants to merge 13 commits intofeat/price-widget-v61from
Conversation
| private fun ToggleRow( | ||
| content: @Composable RowScope.() -> Unit, | ||
| isEnabled: Boolean, | ||
| onToggle: () -> Unit, |
There was a problem hiding this comment.
CLAUDE.md: ToggleRow composable is missing modifier: Modifier = Modifier parameter
ToggleRow is a @Composable function but has no modifier parameter at all. toggleEnabled: Boolean = true is the only optional parameter, but modifier: Modifier = Modifier must come before it as the first optional parameter.
Rule: CLAUDE.md — "ALWAYS declare modifier: Modifier = Modifier, as the FIRST optional parameter in composable declarations"
| onToggle: () -> Unit, | |
| private fun ToggleRow( | |
| content: @Composable RowScope.() -> Unit, | |
| isEnabled: Boolean, | |
| onToggle: () -> Unit, | |
| modifier: Modifier = Modifier, | |
| toggleEnabled: Boolean = true, | |
| ) |
| dataRepository.fetchArticles() | ||
| .onSuccess { preferencesStore.cacheArticles(it) } | ||
| .onFailure { | ||
| Logger.warn("Failed to refresh headlines", it, context = TAG) |
There was a problem hiding this comment.
CLAUDE.md: Duplicate error logging
dataRepository.fetchArticles() delegates to newsService.fetchData(), which already has an internal .onFailure { Logger.warn("Failed to fetch news", ...) }. This .onFailure here (and the equivalent one in AppWidgetConfigViewModel.saveHeadlines at line 127) causes every failure to be logged twice.
Rule: CLAUDE.md — "NEVER duplicate error logging in .onFailure {} if the called method already logs the same error internally"
Remove the .onFailure logging from both call sites, or remove the logging inside NewsService.fetchData() and keep it at the callers.
| .background(Colors.White10) | ||
| .clickableAlpha { | ||
| val intent = Intent(Intent.ACTION_VIEW, link.toUri()) | ||
| context.startActivity(intent) |
There was a problem hiding this comment.
Bug: HeadlineCardSmall calls startActivity without empty-link guard
If link is empty or has no scheme (possible when the news API returns an article with an empty link), calling startActivity(Intent(ACTION_VIEW, "".toUri())) throws ActivityNotFoundException and crashes. Notably, the new HeadlinesGlanceContent in this same PR correctly guards:
val tapIntent = if (article != null && article.link.isNotEmpty()) {
Intent(Intent.ACTION_VIEW, article.link.toUri())...
} else {
Intent(context, AppWidgetConfigActivity::class.java)...
}Apply the same guard here. (The same issue exists in the pre-existing HeadlineCard wide variant, but HeadlineCardSmall is new code introduced by this PR.)
FIGMA
Stacks on top of #914. This PR:
Description
The Headlines widget now ships as a system widget.
HeadlinesGlanceWidgetis registered viaHeadlinesGlanceReceiverin the manifest, usesSizeMode.Responsiveto switch between a Wide layout (343x152dp, source/time row under the headline) and a Compact layout (163x192dp, headline-only with optional time pinned bottom-right), and reuses the foundation'sGlanceWidgetScaffold+ tap-to-edit intent. Tapping the widget opens the article URL when present, falling back toAppWidgetConfigActivityfor unconfigured/no-data instances. The Glance content reads cached articles fromAppWidgetPreferencesStoreand renders a random one.The shared
AppWidgetConfigViewModelandAppWidgetConfigScreenwere extended to handle Headlines: aHeadlinePreferencesfield (in-app) mapped to/fromHomeHeadlinePreferences(datastore),toggleShowTime/toggleShowSourceactions, branchedresetPreferences/saveAndFinishperAppWidgetType, and a CONTENT section listing Title / Source / Time toggles. Save now persists the entry, registers the widget, and warms the article cache viaAppWidgetDataRepository.fetchArticles().AppWidgetRefreshWorkerwas updated to refresh Headlines instances alongside Price.In-app,
HeadlineCardis split into wide and compact variants. Wide: full-width card with a 4-lineTitle, plus a Brand-colored source / White64 time row. Compact: 163x192dp card with a 4-line title and optional time at the bottom. The widget title row, newspaper icon, and "Source" label have all been removed in favor of the v61 layout.HeadlinesPreviewScreenmirrors the price preview — top-bar title, description, divider, Widget Settings row showing Default/Custom,WidgetSizeCarouselbetween the small and wide cards, and Save/Delete buttons.HeadlinesEditScreenis restructured into a CONTENT section usingCaption13Upheaders and toggle rows (Title is locked-on, Source and Time are user-toggleable) with dividers, replacing the previous flat list.Manifest, XML widget info (
appwidget_info_headlines.xml), and a static preview layout (appwidget_preview_headlines.xml) are added to register the new provider with a 4x2 default placement and 2x2 minimum, matching the price widget. Three new strings (widgets__widget__content,widgets__widget__save,widgets__widget__settings) were added tostrings.xml.Preview
os-widget.webm
app-widget.webm
QA Notes
System widget
In-app widget
Build & lint
./gradlew compileDevDebugKotlinclean./gradlew testDevDebugUnitTestclean (existingHeadlineCardTest/HeadlinesPreviewContentTestupdated for the new layout)./gradlew detektclean