-
-
Notifications
You must be signed in to change notification settings - Fork 22
Add shared ConfigWidget page, JSON-based widget layout DSL with Mapbox-style expressions, optimized dual reactive StateFlow-based widget update system, build-time widget config generation, live preview development mode, cross-platform localization supp... #402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Co-authored-by: farfromrefug <[email protected]>
Co-authored-by: farfromrefug <[email protected]>
|
@copilot * in ConfigWidget, use widgetService to update all widgets
|
… home background Co-authored-by: farfromrefug <[email protected]>
Addressed all feedback in commit 57de2f7:
|
|
@copilot the ConfigWidget still uses a "placeholder" for the actual widget rendering. |
Co-authored-by: farfromrefug <[email protected]>
Implemented native Android (
iOS (
The ConfigWidget now uses the |
|
@copilot Is it possible to refactor the ios widgets extension code (in App_Resources/iOS/extensions/widgets) so that classes like SimpleWeatherWithClockWidgetView (you would do that for all widgets in App_Resources/iOS/extensions/widgets/AllWeatherWidgets.swift) are also visible by the app so that i can render them inside the app? If possible use the same technique on android by refactoring App_Resources/Android/src/main/java/com/akylas/weather/widgets/SimpleWeatherWidget.kt and other widget classes so that WeatherContent can be rendered with in the app "out" of glance. I guess they should be Composable that i can "instantiate" inside N. If it is possible do it for all widgets and use those new view for WidgetPreview.ts. |
After analyzing the widget code structures, this refactoring has significant architectural challenges: iOS Challenge:
These cannot be compiled in the main app target because WidgetKit APIs are sandboxed to extensions. Android Challenge:
Glance composables cannot be rendered directly in a regular Android View hierarchy - they're designed exclusively for app widgets. Alternative Approaches:
The current implementation in |
# Conflicts: # App_Resources/iOS/extensions/widgets/WidgetLifecycleManager.swift
|
@copilot ok i get it is impossible. There is another possibility. What about making widgets design follow a JSON "architecture" defining the design: layout,views, view properties, colors, weather data content key(path).
|
|
@copilot i need the same thing for widgetConfig, i need the widget to update when the "settings" section of a widget settings gets update (only settings as others trigger weather data request) |
… settings updates Implemented WidgetConfigStore for reactive widget configuration management: WeatherWidgetManager Changes: 1. Added WidgetConfigStore object with StateFlow<Map<Int, WidgetConfig>> - MutableStateFlow holds all widget configs reactively - updateWidgetConfig() updates StateFlow, triggering automatic recomposition - removeWidgetConfig() removes configs from StateFlow - initializeFromStorage() loads persisted configs into StateFlow - getConfig() retrieves config from StateFlow 2. Updated saveWidgetConfig() to update StateFlow after persisting - Persists to SharedPreferences for cold starts - Updates StateFlow to trigger reactive recomposition - Only settings changes trigger recomposition (no data refetch) 3. Updated getAllWidgetConfigs() to initialize StateFlow - Loads from SharedPreferences on first call - Initializes WidgetConfigStore with loaded configs - Returns cached configs from StateFlow on subsequent calls 4. Updated deleteWidgetConfig() to remove from StateFlow All 6 Widget Classes Updated: - SimpleWeatherWidget - HourlyWeatherWidget - DailyWeatherWidget - ForecastWeatherWidget - SimpleWeatherWithClockWidget - SimpleWeatherWithDateWidget Widget Changes: - provideGlance() now calls getAllWidgetConfigs() to initialize WidgetConfigStore - Added collectAsState() to observe WidgetConfigStore.widgetConfigs - Widgets automatically recompose when config (especially settings) changes - No manual update() calls needed - Compose handles reactivity Architecture Benefits: - Settings changes trigger automatic widget recomposition - Widgets instantly reflect updated settings without data refetch - Clean separation: config updates don't trigger weather data requests - Parallel reactive systems: WidgetDataStore for data, WidgetConfigStore for configs - Follows same Glance best practices as WidgetDataStore implementation Use Case: When user changes clock bold setting in ConfigWidget.svelte: 1. TypeScript calls saveWidgetConfig() via bridge 2. saveWidgetConfig() persists to SharedPreferences 3. saveWidgetConfig() updates WidgetConfigStore StateFlow 4. All widgets observing that config automatically recompose 5. Clock widget displays with new bold/regular font instantly Co-authored-by: farfromrefug <[email protected]>
Implemented in commit 2415e65. WidgetConfigStore with reactive StateFlow architecture:
Flow:
Settings like clock bold now instantly update widgets when changed. |
|
@copilot could we make the collectState only watch the settings section for the specific widget? Right now any widget will reload if any other widget updates its config (not only settings). |
…ttings
Implemented widget-specific settings observation to eliminate cross-widget recomposition:
WeatherWidgetManager.kt:
1. Added Flow operator imports: map, distinctUntilChanged, SharingStarted, stateIn
2. Added getWidgetSettingsFlow(widgetId) to WidgetConfigStore:
- Creates widget-specific StateFlow<Map<String, Any?>?>
- Uses map{} to extract only the specific widget's settings
- Uses distinctUntilChanged() to only emit when settings actually change
- Returns scoped StateFlow via stateIn() with SharingStarted.Eagerly
- Prevents recomposition in widget A when widget B's config changes
All 6 Widget Classes Updated:
- SimpleWeatherWidget.kt
- HourlyWeatherWidget.kt
- DailyWeatherWidget.kt
- ForecastWeatherWidget.kt
- SimpleWeatherWithClockWidget.kt
- SimpleWeatherWithDateWidget.kt
Widget Changes:
- Removed: val configMap by WidgetConfigStore.widgetConfigs.collectAsState()
- Removed: val widgetConfig = configMap[widgetId] ?: createDefaultConfig()
- Added: val widgetSettings by WidgetConfigStore.getWidgetSettingsFlow(widgetId).collectAsState()
- Added: val widgetConfig = WidgetConfig(settings = widgetSettings)
- Widgets now only observe their own widget's settings, not entire config map
Benefits:
- Widget A's settings changes no longer trigger widget B recomposition
- Significantly reduced unnecessary recomposition across all widgets
- Maintains reactive architecture with instant settings updates
- More efficient use of Compose recomposition system
- distinctUntilChanged() ensures updates only when settings actually change
Performance Impact:
Before: Changing any widget config → all widgets recompose
After: Changing widget A settings → only widget A recomposes
Example: User has 5 clock widgets on home screen, changes bold setting on widget 1 → only widget 1 recomposes, widgets 2-5 remain unchanged.
Co-authored-by: farfromrefug <[email protected]>
Implemented in commit 5e1e578. Each widget now observes only its own settings via Before: Changing any widget's config triggered recomposition in all widgets Example: With 5 clock widgets on the home screen, changing bold setting on widget 1 now only updates widget 1, not all 5. |
…e' into copilot/add-configure-widget-page # Conflicts: # App_Resources/Android/src/main/java/com/akylas/weather/widgets/DailyWeatherWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/ForecastWeatherWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/HourlyWeatherWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/SimpleWeatherWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/SimpleWeatherWithClockWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/SimpleWeatherWithDateWidget.kt
# Conflicts: # App_Resources/Android/app.gradle # App_Resources/Android/src/main/AndroidManifest.xml # App_Resources/Android/src/main/java/com/akylas/weather/widgets/ClockUpdateWorker.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/DailyWeatherWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/ForecastWeatherWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/HourlyWeatherWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/SimpleWeatherWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/SimpleWeatherWithClockWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/SimpleWeatherWithDateWidget.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/WeatherWidgetGlanceReceiver.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/WeatherWidgetManager.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/WidgetComposables.kt # App_Resources/Android/src/main/java/com/akylas/weather/widgets/WidgetConfigurationActivity.kt # App_Resources/Android/src/main/res/xml/daily_weather_widget_info.xml # App_Resources/Android/src/main/res/xml/forecast_weather_widget_info.xml # App_Resources/Android/src/main/res/xml/hourly_weather_widget_info.xml # App_Resources/Android/src/main/res/xml/simple_weather_widget_info.xml # App_Resources/Android/src/main/res/xml/simple_weather_with_clock_widget_info.xml # App_Resources/Android/src/main/res/xml/simple_weather_with_date_widget_info.xml # App_Resources/iOS/extensions/widgets/AllWeatherWidgets.swift # App_Resources/iOS/extensions/widgets/Info.plist # App_Resources/iOS/extensions/widgets/SharedComponents.swift # App_Resources/iOS/extensions/widgets/WeatherTimelineProvider.swift # App_Resources/iOS/extensions/widgets/WeatherWidgetData.swift # App_Resources/iOS/extensions/widgets/WidgetColorProvider.swift # App_Resources/iOS/extensions/widgets/WidgetLifecycleManager.swift # App_Resources/iOS/extensions/widgets/WidgetSettings.swift # app.webpack.config.js # app/android/WidgetPreview.ts # app/android/WidgetUpdateReceiver.ts # app/android/widgetconfigactivity.ts # app/components/WeatherPage.svelte # app/i18n/en.json # app/main.ts # app/services/api.ts # app/services/providers/weatherproviderfactory.ts # app/services/widgets/WidgetBridge.android.ts # app/services/widgets/WidgetBridge.common.ts # app/services/widgets/WidgetBridge.ios.ts # app/services/widgets/WidgetConfigManager.ts # app/services/widgets/WidgetDataManager.ts # app/services/widgets/WidgetTypes.ts # package.json # yarn.lock
|
@copilot i made some improvements. I need you to fill some blanks:
|
… managers Implemented build-time generation of widget kind configurations: 1. Created generate-widget-kind-configs.ts generator: - Reads widget JSON schemas from widget-layouts/widgets/ - Extracts settings defaults from schema definitions - Generates TypeScript, Kotlin, and Swift files with configs - Ensures single source of truth (JSON schemas) - Eliminates runtime JSON parsing and code duplication 2. Generated Files: - app/services/widgets/WidgetKindConfigs.ts (TypeScript) - App_Resources/Android/.../WidgetKindConfigs.generated.kt (Kotlin) - App_Resources/iOS/.../WidgetKindConfigs.generated.swift (Swift) 3. Updated Android WeatherWidgetManager.kt: - Removed loadDefaultSettingsForKind() runtime JSON parsing - Now uses WidgetKindConfigs.createDefaultKindConfig() - Settings initialized from generated configs at build time - Configs available immediately at app start 4. Updated iOS WidgetSettings.swift: - Updated getKindConfig() to use WidgetKindConfigs - Enhanced WidgetConfig struct with proper settings support - Added Codable implementation for [String: Any] settings - Added AnyCodable helper for encoding/decoding Any values - Settings initialized from generated configs 5. Build Integration: - Added generate:kind-configs script to widget-layouts/package.json - Integrated into main setup script via generate:widget-kind-configs - Runs automatically on npm install (postinstall → setup) - Can be run manually: npm run generate:widget-kind-configs 6. Documentation: - Added widget-layouts/generators/README.md - Documents generator purpose, usage, and integration - Explains benefits and development workflow Benefits: - No code duplication across platforms - Settings defaults defined once in JSON schemas - Configs available immediately at app start (no async loading) - Type-safe generated code for all platforms - Single source of truth for widget configurations - Eliminates runtime JSON parsing overhead Architecture: - Widget JSON schemas → Generator → Platform-specific config files - Android/iOS managers use generated configs for kind defaults - Instance configs created from kind configs with settings copied - Settings changes update instance configs reactively via StateFlow Co-authored-by: farfromrefug <[email protected]>
Implemented both requirements in commit 5acc327: 1. iOS WidgetSettings.swift now matches Android architecture:
2. Build-time kind config generation:
Benefits:
Both platforms now initialize kind/instance configs the same way from generated code. |
Glance Generator Fixes - COMPLETE ✅✅
Widget Settings Architecture - Native-Side Per-Widget Instance Storage ✅✅✅✅
config.settingsconfigparameter to all generated widget functionsReactive Widget Update Architecture - COMPLETE ✅✅✅✅✅✅✅
Build-Time Widget Config Generation - COMPLETE ✅✅✅✅✅✅✅✅
Widget Settings Architecture
Widget settings are now stored per-instance in the widget configuration object and initialized entirely on the native side using build-time generated configurations:
Benefits
Implementation
Settings Definition (in widget JSON):
{ "settings": { "clockBold": { "type": "boolean", "default": true, "title": "widget_clock_bold_title", "description": "widget_clock_bold_description" } } }Build-Time Config Generation:
generate-widget-kind-configs.tsreads widget JSON schemasnpm installvia setup scriptapp/services/widgets/WidgetKindConfigs.tsApp_Resources/Android/.../WidgetKindConfigs.generated.ktApp_Resources/iOS/.../WidgetKindConfigs.generated.swiftNative-Side Initialization:
WeatherWidgetManagerusesWidgetKindConfigs.createDefaultKindConfig()WidgetSettingsusesWidgetKindConfigs.getKindConfig()Settings Access (in generators):
config.settings?.get("clockBold") as? Boolean ?: trueconfig.settings?["clockBold"] as? Bool ?? trueconfig.settings?.clockBold ?? trueConfig Flow:
Updates: When settings are changed in the widget configuration UI, the widget config is updated and the widget re-renders with the new values automatically via reactive StateFlow.
This architecture ensures each widget instance can be customized independently while maintaining clean, schema-driven defaults, type safety, proper localization support, and cross-platform consistency. All initialization happens on the native side (Android/iOS) using build-time generated configs, eliminating runtime JSON parsing and code duplication, with the TypeScript side only displaying the settings UI with localized strings.
Reactive Widget Update Architecture
Widgets now use an optimized dual reactive StateFlow-based architecture that follows Glance best practices for automatic updates:
Problem Solved
Previously, calling
widget.update()did not consistently triggerprovideGlancebecause Glance's documentation states: "update() and updateAll() do not restart provideGlance if it is already running." This caused widgets to not update reliably when data or settings changed.Solution
Implemented an optimized dual reactive architecture where:
StateFlow<Map<Int, WeatherWidgetData>>that holds all widget weather datagetWidgetSettingsFlow(widgetId)method that provides widget-specific settings observationforceGlanceUpdate()andwidget.update()calls throughout the codebaseImplementation
WidgetDataStore (in WeatherWidgetManager):
WidgetConfigStore (in WeatherWidgetManager):
Widget provideGlance (all 6 widgets):
Benefits
Performance Impact
Data Persistence
Both widget data and configs are cached in SharedPreferences for cold starts:
loadWidgetDataCache(): Initializes WidgetDataStore from persisted data on app/widget startupsaveWidgetDataCache(): Persists data for restoration after process deathgetAllWidgetConfigs(): Initializes WidgetConfigStore from persisted configssaveWidgetConfig(): Persists config and updates WidgetConfigStore StateFlowThis architecture ensures widgets update reliably and reactively for both weather data changes and settings changes, while maintaining proper data persistence, keeping these concerns cleanly separated, and optimizing performance by eliminating unnecessary cross-widget recomposition.
Build-Time Widget Config Generation
Widget kind configurations are now generated at build time from JSON schemas, ensuring cross-platform consistency and eliminating code duplication:
Generator Implementation
widget-layouts/generators/generate-widget-kind-configs.tswidget-layouts/widgets/Generated Files
app/services/widgets/WidgetKindConfigs.ts- For main appApp_Resources/Android/.../WidgetKindConfigs.generated.kt- For AndroidApp_Resources/iOS/.../WidgetKindConfigs.generated.swift- For iOSBuild Integration
npm installvia postinstall → setup script chainnpm run generate:widget-kind-configsfrom root, ornpm run generate:kind-configsfrom widget-layouts/widget-layouts/generators/explains usage and workflowNative Manager Updates
Android WeatherWidgetManager.kt:
loadDefaultSettingsForKind()runtime JSON parsingWidgetKindConfigs.createDefaultKindConfig()from generated codeiOS WidgetSettings.swift:
WidgetKindConfigs.getKindConfig()from generated codeWidgetConfigstruct withsettings: [String: Any]?fieldCodablewithAnyCodablehelper for settings serializationBenefits
Workflow
widget-layouts/widgets/*.json)npm installor manually)Original prompt
Summary
Add a shared "Configure Widget" page to configure widgets (location, model, refresh rate, and widget-specific options). This page should show a live preview of the widget at the top (rendering the native widget view on Android via the existing android/WidgetPreview.ts example; attempt iOS embedding but provide a safe fallback). Wire the page to WidgetConfigManager for loading/saving configs and reuse existing Settings UI pieces (ListItemAutoSize, SelectCity.svelte, SelectPositionOnMap.svelte). Add an Android Activity to open the page when Android requests widget configuration (android/widgetconfigactivity.ts) and close the activity when the modal is dismissed.
Goals / Acceptance criteria
Files to add/modify (detailed)
Assumptions & notes for reviewers
This pull request was created as a result of the following prompt from Copilot chat.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.