Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
d3f5d3a
chore(ios): widgets building
farfromrefug Nov 26, 2025
30da1f3
Initial plan
Copilot Nov 26, 2025
7d18cd2
Add ConfigWidget page and integrate with Settings
Copilot Nov 26, 2025
5868e5c
Address code review feedback - remove unused code
Copilot Nov 26, 2025
57de2f7
Address feedback: use widgetService, mount ConfigWidget properly, add…
Copilot Nov 26, 2025
0449e37
Implement native WidgetPreview component for Android and iOS fallback
Copilot Nov 26, 2025
964ac4c
Merge remote-tracking branch 'origin/master'
farfromrefug Nov 26, 2025
f841d87
chore: improvements
farfromrefug Nov 26, 2025
17ed00b
Merge remote-tracking branch 'origin/copilot/add-configure-widget-page'
farfromrefug Nov 26, 2025
ded98e9
Merge branch 'master' into copilot/add-configure-widget-page
farfromrefug Nov 26, 2025
fc78ee9
Add JSON-based widget layout DSL with schema, widget definitions, and…
Copilot Nov 26, 2025
1149428
Fix code review issues: safe expression evaluation, NativeScript comp…
Copilot Nov 26, 2025
0b423ac
Add iOS SwiftUI and Android Glance code generators, plus image genera…
Copilot Nov 26, 2025
2a0d874
chore: upgrade
farfromrefug Nov 28, 2025
d1c9f50
chore: crash fix
farfromrefug Dec 1, 2025
f0f298d
chore: ios build fix
farfromrefug Dec 1, 2025
b9f416c
chore: updated html renderer
farfromrefug Dec 12, 2025
345856b
chore: pacakge
farfromrefug Dec 12, 2025
fe65693
chore: updated
farfromrefug Dec 12, 2025
112c567
Merge remote-tracking branch 'origin/copilot/add-configure-widget-pag…
farfromrefug Dec 12, 2025
3e029f9
chore: nativescript svelte generator
farfromrefug Dec 12, 2025
0a42f46
Refactor glance generator to use conditional properties instead of la…
Copilot Dec 12, 2025
1648e0d
Update SimpleWeatherWidget to use generated code from refactored JSON…
Copilot Dec 12, 2025
3d408c2
Implement Mapbox-style expressions for Glance generator with SimpleWe…
Copilot Dec 12, 2025
0b62f60
Migrate all widgets to Mapbox-style expressions with generated Glance…
Copilot Dec 12, 2025
57a17a5
Update Swift generator, NativeScript renderer, and HTML renderer to s…
Copilot Dec 12, 2025
83a0681
Complete integration: Swift, NativeScript, and HTML all use Mapbox ex…
Copilot Dec 12, 2025
93c980a
Fix code review issues in mapbox-expressions: use strict equality and…
Copilot Dec 12, 2025
81d591c
Update NativeScript and HTML renderers to use Mapbox expressions, int…
Copilot Dec 12, 2025
93e455c
Update NativeScript and HTML renderers with Mapbox expressions, integ…
Copilot Dec 12, 2025
4f26666
Fix Glance generator issues: null height, missing commas, color paths…
Copilot Dec 13, 2025
626417c
chore: glance generator
farfromrefug Dec 17, 2025
7e2ad23
chore: more fixes
farfromrefug Dec 17, 2025
a1f8652
Fix Mapbox expression compilation in forEach limit - compile expressi…
Copilot Dec 17, 2025
3ab33ab
chore: building widgets
farfromrefug Dec 21, 2025
2f5601c
Implement comprehensive widget update system with deduplication and l…
Copilot Dec 21, 2025
1b83eed
chore: many improvements
farfromrefug Dec 26, 2025
a543c59
Add live widget preview development mode with --env.liveWidgetPreview…
Copilot Dec 26, 2025
acd4187
Add LiveWidgetPreviewPlugin.js file at project root (tools is a submo…
Copilot Dec 26, 2025
04467a6
Refactor live widget preview to use existing nativescript-svelte-gene…
Copilot Dec 26, 2025
a0fbc80
chore: improvements
farfromrefug Jan 1, 2026
035f550
chore: old widgets
farfromrefug Jan 1, 2026
86f9a7b
Improve all widget designs to match Old implementations and fix issues
Copilot Jan 1, 2026
dbc18c9
Fix widget JSON issues: simplify SimpleWidget layout and use supporte…
Copilot Jan 1, 2026
7646f07
Add scrollable sections and landscape modes to widgets
Copilot Jan 1, 2026
ad9f365
chore: trying to fix clock
farfromrefug Jan 1, 2026
846ef59
Update generators and fix clock permissions - do not modify generated…
Copilot Jan 1, 2026
0aebee6
Add SCHEDULE_EXACT_ALARM permission request for clock widgets
Copilot Jan 1, 2026
3bdb397
Merge remote-tracking branch 'origin/copilot/add-configure-widget-pag…
farfromrefug Jan 1, 2026
0f998f4
chore: i made some fixes as LazyRow does not exist in glance.
farfromrefug Jan 2, 2026
e272ab8
chore: working exact alarm request
farfromrefug Jan 2, 2026
4f9dd85
Update SimpleWeatherWidget JSON to match Old widget layout with all s…
Copilot Jan 2, 2026
9082210
Complete widget JSON updates - all widgets now match Old implementations
Copilot Jan 2, 2026
cba1633
Fix Glance LazyRow references and temperature truncation in clock/dat…
Copilot Jan 2, 2026
b9747c1
Fix all widget issues: item sizes, localization, clock bold setting, …
Copilot Jan 2, 2026
d78d134
Add @setting and localization support to NativeScript and iOS generators
Copilot Jan 2, 2026
9c4134c
Fix glance generator: prevent double data prefix and remove unnecessa…
Copilot Jan 2, 2026
345b8f2
chore: improvements
farfromrefug Jan 2, 2026
6783198
chore: build fix
farfromrefug Jan 2, 2026
24ca008
Refactor widget settings from global prefs to per-widget config storage
Copilot Jan 2, 2026
3fe8baf
Merge remote-tracking branch 'origin/copilot/add-configure-widget-pag…
farfromrefug Jan 2, 2026
1faceb9
Implement native-side settings initialization from JSON widget schemas
Copilot Jan 2, 2026
8585727
Update all Android widgets to load and pass config with settings to g…
Copilot Jan 2, 2026
020d471
chore: many improvements
farfromrefug Jan 4, 2026
b764edb
chore: cleanp
farfromrefug Jan 4, 2026
5219c80
Implement StateFlow-based reactive widget data architecture for all w…
Copilot Jan 4, 2026
9dd0e91
chore: update
farfromrefug Jan 4, 2026
2415e65
Add reactive StateFlow-based widget config architecture for automatic…
Copilot Jan 4, 2026
5e1e578
Optimize widget config observation to only watch specific widget's se…
Copilot Jan 4, 2026
425b842
chore: update
farfromrefug Jan 4, 2026
75e8697
Merge remote-tracking branch 'origin/copilot/add-configure-widget-pag…
farfromrefug Jan 4, 2026
d00004b
chore: building
farfromrefug Jan 5, 2026
602fd22
Merge branch 'master' into copilot/add-configure-widget-page
farfromrefug Jan 5, 2026
c22c3a5
Merge branch 'master' into copilot/add-configure-widget-page
farfromrefug Jan 6, 2026
4df38a1
chore: update
farfromrefug Jan 6, 2026
5acc327
Add webpack-time widget kind config generation and update iOS/Android…
Copilot Jan 6, 2026
7f6b2b4
fix: temperature line sometimes appearing black in chart view
farfromrefug Jan 9, 2026
9f74027
chore: generator fix
farfromrefug Jan 9, 2026
3063e99
chore: android native deps update
farfromrefug Jan 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 53 additions & 8 deletions App_Resources/Android/app.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ def BUILD_TOOLS_PATH = "$rootDir/build-tools"

apply plugin: "androidx.baselineprofile"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "org.jetbrains.kotlin.plugin.compose" // Add this line
apply plugin: 'kotlinx-serialization'

android {

// we only enable split if specified as an arg or if in debug and abiFilters is used(through cli)
def splitEnabled = (gradle.startParameter.taskNames.contains("assembleDebug") && project.hasProperty('abiFilters')) || project.hasProperty('splitEnabled');
defaultConfig {
Expand Down Expand Up @@ -41,6 +44,13 @@ android {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3"
}
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
Expand Down Expand Up @@ -133,22 +143,39 @@ android {
experimentalProperties["android.experimental.r8.dex-startup-optimization"] = true
}

def runCreateProguard = {
println("Running createProguard script")
exec {
workingDir "$USER_PROJECT_ROOT"
println("node tools/scripts/createProguard.js ${rootDir} ${getAppResourcesPath()}")
commandLine 'node', "tools/scripts/createProguard.js", "${rootDir}", "${getAppResourcesPath()}"
}
}

task createProguard {
dependsOn 'buildMetadata'
// don't depend on buildMetadata – we call the logic where needed instead
doLast {
exec {
workingDir "$USER_PROJECT_ROOT"
println("node tools/scripts/createProguard.js ${rootDir} ${getAppResourcesPath()}")
commandLine 'node', "tools/scripts/createProguard.js", "${rootDir}", "${getAppResourcesPath()}"
}
runCreateProguard()
}
}
project.tasks.configureEach {
if (name =~ /buildMetadata/) {
it.finalizedBy(createProguard)
// ensure proguard is generated after buildMetadata finishes if not created already
it.doFirst {
println("buildMetadata")
}
it.doLast {
runCreateProguard()
}
}
// ensure proguard is available before any minification run (also catches benchmark variants)
if (name =~ /minify(Debug|Release)WithR8/) {
it.dependsOn(createProguard)
it.dependsOn("buildMetadata")
it.doFirst {
println("minify")
}
// still ensure minify tasks are ordered after the createProguard task if it's scheduled
it.mustRunAfter(createProguard)
}
}

Expand Down Expand Up @@ -185,6 +212,24 @@ dependencies {

implementation "androidx.profileinstaller:profileinstaller:1.4.1"
baselineProfile project(':baselineprofile')

// Glance for widgets
implementation "androidx.glance:glance-appwidget:1.2.0-rc01"
implementation "androidx.glance:glance-preview:1.2.0-rc01"
implementation "androidx.glance:glance-appwidget-preview:1.2.0-rc01"
implementation "androidx.glance:glance-material3:1.2.0-rc01"

// WorkManager for scheduling
implementation "androidx.work:work-runtime-ktx:2.8.1"

// Compose dependencies
implementation "androidx.compose.ui:ui:1.5.3"
implementation "androidx.compose.material3:material3:1.1.2"
implementation "androidx.compose.runtime:runtime:1.5.3"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0"
implementation 'androidx.compose.ui:ui-unit:1.9.4'


}


Expand Down
15 changes: 7 additions & 8 deletions App_Resources/Android/before-plugins.gradle
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
ext {
compileSdk = 35
targetSdk = 35
kotlinVersion = "2.1.0"
androidBuildToolsVersion = "8.12.1"
buildToolsVersion = "35.0.0"
compileSdk = 36
targetSdk = 36
kotlinVersion = "2.2.0"
androidBuildToolsVersion = "8.13.2"
buildToolsVersion = "36.1.0"
androidxVersion = "1.15.0"
androidXAppCompat = "1.7.0"
androidXAppCompatVersion = "1.7.0"
androidXFragment = "1.8.5"
androidXTransition = "1.5.1"
androidXRecyclerViewVersion = "1.4.0-rc01"
androidXRecyclerViewVersion = "1.4.0"
androidxViewPager2Version = "1.1.0"
androidXSwipeRefreshLayoutViewVersion = "1.2.0-alpha01"
androidXMaterial = "1.12.0"
androidXBrowser = "1.8.0"
androidXPreferenceVersion = "1.2.1"
okHttpVersion = "4.12.0"
frescoVersion = "3.4.0"
lottieVersion = "6.2.0"
ndkVersion = "27.2.12479018"
ndkVersion = "27.3.13750724"
licenseReporterVersion = "2.4"
sentryVersion = "7.22.6"
sentryGradleVersion = "4.14.1"
Expand Down
1 change: 1 addition & 0 deletions App_Resources/Android/native-api-usage.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"whitelist-plugins-usages": true,
"whitelist": [
"com.akylas.weather*:*",
"android.view:View.OnApplyWindowInsetsListener",
"android.view:WindowInsets",
"android.graphics:Insets",
Expand Down
3 changes: 3 additions & 0 deletions App_Resources/Android/rootbuildscript.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ repositories {
}
dependencies {
classpath 'androidx.benchmark:benchmark-baseline-profile-gradle-plugin:1.4.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21" // or your version
classpath "org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:2.2.21"
classpath "org.jetbrains.kotlin:kotlin-serialization:2.2.21"
}
130 changes: 130 additions & 0 deletions App_Resources/Android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!-- <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
Expand Down Expand Up @@ -86,5 +89,132 @@
<action android:name="__PACKAGE__.QUERY_WEATHER" />
</intent-filter>
</receiver>

<!-- Simple Weather Widget -->
<receiver
android:name="__PACKAGE__.widgets.SimpleWeatherWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_OPTIONS_CHANGED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
<action android:name="android.appwidget.action.ACTION_APPWIDGET_DISABLED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/simple_weather_widget_info" />
</receiver>

<!-- Simple Weather Widget with Date -->
<receiver
android:name="__PACKAGE__.widgets.SimpleWeatherWithDateWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_OPTIONS_CHANGED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
<action android:name="android.appwidget.action.ACTION_APPWIDGET_DISABLED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/simple_weather_with_date_widget_info" />
</receiver>

<!-- Simple Weather Widget with Clock -->
<receiver
android:name="__PACKAGE__.widgets.SimpleWeatherWithClockWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_OPTIONS_CHANGED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
<action android:name="android.appwidget.action.ACTION_APPWIDGET_DISABLED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/simple_weather_with_clock_widget_info" />
</receiver>

<!-- Hourly Weather Widget -->
<receiver
android:name="__PACKAGE__.widgets.HourlyWeatherWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_OPTIONS_CHANGED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
<action android:name="android.appwidget.action.ACTION_APPWIDGET_DISABLED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/hourly_weather_widget_info" />
</receiver>

<!-- Daily Weather Widget -->
<receiver
android:name="__PACKAGE__.widgets.DailyWeatherWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_OPTIONS_CHANGED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
<action android:name="android.appwidget.action.ACTION_APPWIDGET_DISABLED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/daily_weather_widget_info" />
</receiver>

<!-- Forecast Weather Widget -->
<receiver
android:name=".widgets.ForecastWeatherWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_OPTIONS_CHANGED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
<action android:name="android.appwidget.action.ACTION_APPWIDGET_DISABLED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/forecast_weather_widget_info" />
</receiver>

<!-- Widget Configuration Activity -->
<activity
android:name=".widgets.WidgetConfActivity"
android:exported="true"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_OPTIONS_CHANGED" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
<action android:name="android.appwidget.action.ACTION_APPWIDGET_DISABLED" />
</intent-filter>
</activity>
<receiver
android:name=".WidgetUpdateReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="__PACKAGE__.WIDGET_UPDATE_REQUEST" />
<action android:name="__PACKAGE__.WIDGET_ADDED" />
<action android:name="__PACKAGE__.WIDGET_REMOVED" />
</intent-filter>
</receiver>
<receiver
android:name=".widgets.ThemeChangeReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.CONFIGURATION_CHANGED" />
</intent-filter>
</receiver>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.akylas.weather.widgets

import android.content.Context
import android.content.Intent
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.os.Build
import android.app.AlarmManager
import androidx.work.*
import java.util.concurrent.TimeUnit

/**
* Worker to update clock widget every minute
*/
class ClockUpdateWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {

private val LOG_TAG = "ClockUpdateWorker"

override suspend fun doWork(): Result {
WidgetsLogger.d(LOG_TAG, "doWork started (input=${inputData.keyValueMap})")
return try {
// Update clock widget
val appWidgetManager = AppWidgetManager.getInstance(applicationContext)
val widgetIds = appWidgetManager.getAppWidgetIds(
ComponentName(applicationContext, SimpleWeatherWithClockWidgetReceiver::class.java)
)

if (widgetIds.isNotEmpty()) {
val intent = Intent(applicationContext, SimpleWeatherWithClockWidgetReceiver::class.java)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds)
applicationContext.sendBroadcast(intent)
}

WidgetsLogger.i(LOG_TAG, "doWork finished successfully")
Result.success()
} catch (t: Throwable) {
WidgetsLogger.e(LOG_TAG, "doWork failed", t)
Result.failure()
}
}

companion object {
private const val CLOCK_UPDATE_WORK_TAG = "clock_widget_update"

fun scheduleClockUpdates(context: Context) {
// Check if we have permission for exact alarms on Android 12+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
if (alarmManager?.canScheduleExactAlarms() == false) {
WidgetsLogger.w("ClockUpdateWorker", "Cannot schedule exact alarms - permission not granted")
// Fall back to periodic work request which doesn't require exact alarm permission
schedulePeriodicUpdates(context)
return
}
}

schedulePeriodicUpdates(context)
}

private fun schedulePeriodicUpdates(context: Context) {
val updateRequest = PeriodicWorkRequestBuilder<ClockUpdateWorker>(
15, // Minimum is 15 minutes for PeriodicWork
TimeUnit.MINUTES
)
.setInitialDelay(0, TimeUnit.MINUTES)
.addTag(CLOCK_UPDATE_WORK_TAG)
.build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
CLOCK_UPDATE_WORK_TAG,
ExistingPeriodicWorkPolicy.REPLACE,
updateRequest
)
}

fun cancelClockUpdates(context: Context) {
WorkManager.getInstance(context).cancelAllWorkByTag(CLOCK_UPDATE_WORK_TAG)
}
}
}
Loading