Repository navigation
feat: app-list widget - #52
wassupluke wants to merge 15 commits into
Conversation
- Render per-widget text alignment on app-list items (was stored but never applied); also aligns the label's own text gravity. - Apply edge-to-edge window insets to the configure screen so content clears the status/nav bars at targetSdk 36. - Style the empty-state view with dynamic color and per-widget font size instead of a static color. - Fall back to the placeholder icon drawable on icon-load failure instead of leaving a blank cell. - Remove hardcoded SeekBar min/max in the configure layout now that runtime code is the single source of truth. - Guard getViewAt against an out-of-range position instead of crashing.
- Item layout is a horizontal row (icon beside label) instead of stacked. - Replace GridView.AUTO_FIT (which re-measures and collapses to one column) with an integer column count computed from the widget's current width; recompute on onAppWidgetOptionsChanged (resize). - Fold the column count into the RemoteViews collection's adapter data URI so the host rebuilds the grid when columns change (it caches column layout and ignores setNumColumns on a plain update) — fixes reconfigure not reflowing. - Set the tap fill-in on the icon and label views only, not the whole cell. - Icon size scales with font (larger when icon-only, smaller inline).
- AppListConfigureActivity: replace the bottom 'Add widget' button with a top-right action-bar item (checkmark); save() now reads its own views. - Widen icon-only grid cells (footprint = icon + padding + spacing) and drop the icon's trailing margin when there's no label, so a small-font icon grid no longer packs cells tight enough to clip the icons.
Icon-only is the only mode that reflows into multiple columns, so the separate vertical/horizontal layout preference was redundant. Remove the AppListLayout enum, its store accessors, the config-screen toggle, and its strings. Add a DEFAULT_WIDTH_DP fallback so hosts that report no size still yield a real icon grid instead of collapsing to one column.
Reviewer's GuideImplements a new app-list widget family with a pure data layer, per-widget SharedPreferences store, RemoteViews-based collection provider/service, a configuration activity with menu-based saving, and all associated resources and docs; grid column reflow is driven by display mode and widget size instead of a separate layout toggle, with inline icons aligned per-widget text alignment and font size-driven icon sizing. Sequence diagram for configuring and using the app-list widgetsequenceDiagram
actor User
participant ConfigureActivity as AppListConfigureActivity
participant Store as AppListStore
participant Manager as AppWidgetManager
participant Provider as AppListWidgetProvider
participant Service as AppListRemoteViewsService
participant Factory as AppListFactory
participant PackageManager
participant TargetApp
User->>ConfigureActivity: open via APPWIDGET_CONFIGURE
ConfigureActivity->>PackageManager: queryIntentActivities(ACTION_MAIN, CATEGORY_LAUNCHER)
PackageManager-->>ConfigureActivity: launchable apps
ConfigureActivity->>Store: setSelectedPackages(id, packages)
ConfigureActivity->>Store: setDisplayMode(id, AppDisplayMode)
ConfigureActivity->>Store: setFontSize(id, size)
ConfigureActivity->>Store: setTextAlign(id, Settings.TextAlign)
ConfigureActivity->>Manager: getInstance(this)
ConfigureActivity->>Provider: renderAppListWidgets(context, mgr, [id])
Note over Provider,Service: Widget RemoteViews + collection set up
Provider->>Manager: updateAppWidget(id, RemoteViews)
Manager->>Service: startService(AppListRemoteViewsService)
Service->>Factory: onGetViewFactory(intent)
Factory->>Store: selectedPackages(context, id)
Factory->>PackageManager: queryIntentActivities(ACTION_MAIN, CATEGORY_LAUNCHER)
Factory->>AppSelection: resolveSelected(stored, installed)
Factory->>AppSelection: sortAlphabetically(entries, locale)
Factory-->>Manager: getViewAt(position) with fill-in EXTRA_PACKAGE
User->>Provider: tap app icon (PendingIntent ACTION_LAUNCH_APP)
Provider->>Provider: launchApp(context, EXTRA_PACKAGE)
Provider->>PackageManager: getLaunchIntentForPackage(packageName)
PackageManager-->>Provider: launch Intent
Provider->>TargetApp: startActivity(launch)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The app-list query/sorting logic is duplicated between
AppListConfigureActivity.loadApps()andAppListFactory.onDataSetChanged(); consider extracting a shared helper to keep selection ordering and filtering behavior in sync over time. - The
DEFAULT_WIDTH_DPfallback incomputeColumnsis hard-coded to matchapp_list_widget_info.xmlminWidth; consider deriving this from resources or a single shared constant so future layout tweaks don’t silently desync column computation from the widget-info.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The app-list query/sorting logic is duplicated between `AppListConfigureActivity.loadApps()` and `AppListFactory.onDataSetChanged()`; consider extracting a shared helper to keep selection ordering and filtering behavior in sync over time.
- The `DEFAULT_WIDTH_DP` fallback in `computeColumns` is hard-coded to match `app_list_widget_info.xml` minWidth; consider deriving this from resources or a single shared constant so future layout tweaks don’t silently desync column computation from the widget-info.
## Individual Comments
### Comment 1
<location path="app/src/main/java/com/wassupluke/widgets/data/AppSelection.kt" line_range="9-11" />
<code_context>
+/** Pure, JVM-testable helpers for the app-list widget. No Android APIs. */
+object AppSelection {
+
+ /** Locale-aware, case-insensitive-primary alphabetical sort by label. */
+ fun sortAlphabetically(entries: List<AppEntry>, locale: Locale): List<AppEntry> {
+ val collator = Collator.getInstance(locale)
+ return entries.sortedWith(Comparator { a, b -> collator.compare(a.label, b.label) })
+ }
</code_context>
<issue_to_address>
**issue (bug_risk):** Collator configuration doesn't match the "case-insensitive-primary" comment.
The implementation doesn’t currently enforce the documented "case-insensitive-primary" behavior. `Collator.getInstance(locale)` may use a strength that distinguishes case or diacritics depending on the locale. Please explicitly set `collator.strength = Collator.PRIMARY` (and likely `collator.isCaseLevel = false`) so the sort order consistently matches the comment across locales.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| /** Locale-aware, case-insensitive-primary alphabetical sort by label. */ | ||
| fun sortAlphabetically(entries: List<AppEntry>, locale: Locale): List<AppEntry> { | ||
| val collator = Collator.getInstance(locale) |
There was a problem hiding this comment.
issue (bug_risk): Collator configuration doesn't match the "case-insensitive-primary" comment.
The implementation doesn’t currently enforce the documented "case-insensitive-primary" behavior. Collator.getInstance(locale) may use a strength that distinguishes case or diacritics depending on the locale. Please explicitly set collator.strength = Collator.PRIMARY (and likely collator.isCaseLevel = false) so the sort order consistently matches the comment across locales.
There was a problem hiding this comment.
Correct — Collator.getInstance() defaults to TERTIARY, which distinguishes case, contradicting the KDoc. Set strength = Collator.PRIMARY so the sort ignores case and accents as documented.
|
Overall comments:
|
Icon-only widgets now render as grid rows (one factory item per row of up to MAX_COLUMNS icons) laid out by the new AppGridLayout, so a partial last row honors the widget's START/CENTER/END alignment instead of left-packing. The collection is a single column; each row lays out its own icon columns with per-icon tap targets. Extract the duplicated launcher-query logic into PackageManager.launcherApps(), and sort with a PRIMARY-strength collator for case/accent-insensitive ordering.
Give the alarm widget its own text-alignment setting (defaulting to the weather value so upgrades keep the shared alignment). WidgetStyle.gravity now takes an explicit align, and each provider passes its own. Rebuild the settings screen into Material cards grouped by Appearance / Weather / Alarm / Permissions, replacing the single status line with per-permission check/cross rows, and extract a shared bindAlignGroup() helper.
Summary
Adds a third widget family: an app-list widget — a home-screen grid of user-selected apps, configured per instance, where taps launch the app. Follows the existing pure-data-layer + thin-Android-glue split used by the weather and alarm widgets.
What's included
data/, JVM-unit-tested):AppEntrymodel,AppDisplayMode(icon / label / icon+label), andAppSelectionencode/decode helpers.AppListStore): selected packages, display mode, font size, and text alignment keyed byappWidgetId.AppListWidgetProvider): builds theRemoteViewscollection, computes reflow columns, and wires per-widget launch taps.AppListConfigureActivity) + manifest wiring, layouts, strings, and widget-info resources.Recent cleanup
The final commit drops the separate vertical/horizontal layout toggle — icon-only is the only mode that reflows into multiple columns, so the preference was redundant. Columns are now derived purely from the display mode, with a
DEFAULT_WIDTH_DPfallback so hosts that report no size still yield a real grid instead of collapsing to one column.Testing
./gradlew :app:testDebugUnitTest— green (26 tasks, fresh--rerun-tasksrun).Summary by Sourcery
Introduce a new configurable app-list widget family that displays a home-screen grid of user-selected apps and wires launch behavior per widget instance.
New Features:
Enhancements:
Build:
Documentation:
Tests: