From 0170e299a671f7c5f3d0b92963c0023ed6253866 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Mon, 30 Mar 2026 23:05:38 -0500 Subject: [PATCH 01/26] docs: add alarm widget + global font size design doc --- docs/plans/2026-03-30-alarm-widget-design.md | 111 +++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 docs/plans/2026-03-30-alarm-widget-design.md diff --git a/docs/plans/2026-03-30-alarm-widget-design.md b/docs/plans/2026-03-30-alarm-widget-design.md new file mode 100644 index 0000000..9fd6271 --- /dev/null +++ b/docs/plans/2026-03-30-alarm-widget-design.md @@ -0,0 +1,111 @@ +# Alarm Widget + Global Font Size — Design + +**Date:** 2026-03-30 +**Status:** Approved + +## Overview + +Add a new home screen widget that displays the next scheduled system alarm. Extend the Settings page with a global font size slider that applies to all widgets. Each widget gets its own independent tap-target app setting. + +## DataStore Changes (`WeatherDataStore.kt`) + +New keys: + +| Key | Type | Default | Purpose | +|-|-|-|-| +| `FONT_SIZE` | `intPreferencesKey("font_size")` | 48 | Global font size for all widgets | +| `ALARM_WIDGET_TAP_PACKAGE` | `stringPreferencesKey("alarm_widget_tap_package")` | `""` | Tap target for alarm widget (empty = MainActivity) | +| `ALARM_TEXT` | `stringPreferencesKey("alarm_text")` | `"No alarm"` | Cached alarm display string | + +Migration: rename `WIDGET_TAP_PACKAGE` → `WEATHER_WIDGET_TAP_PACKAGE`. On first read, if old key exists, copy value to new key and clear old key. + +`WIDGET_TEXT_COLOR` and `WIDGET_DYNAMIC_COLOR` remain unchanged — already global. + +`WeatherWidget` hardcoded `48.sp` replaced with `FONT_SIZE` value. + +## New Files + +### `widget/AlarmWidget.kt` +- Extends `GlanceAppWidget` +- Reads `WIDGET_TEXT_COLOR`, `WIDGET_DYNAMIC_COLOR`, `FONT_SIZE`, `ALARM_WIDGET_TAP_PACKAGE`, `ALARM_TEXT` from DataStore via `collectAsState()` +- Displays `ALARM_TEXT` using same color/dynamic color logic as `WeatherWidget` (reuse `parseColorSafe()`) +- Tap action: `actionStartActivity` using `ALARM_WIDGET_TAP_PACKAGE`; empty/null falls back to MainActivity +- Wrapped in `GlanceTheme` + +### `widget/AlarmWidgetReceiver.kt` +- Extends `GlanceAppWidgetReceiver` +- Overrides `onReceive()` to handle: + - `android.app.action.NEXT_ALARM_CLOCK_CHANGED` + - `android.intent.action.BOOT_COMPLETED` + - `android.intent.action.TIME_SET` + - `android.intent.action.TIMEZONE_CHANGED` + - `android.appwidget.action.APPWIDGET_UPDATE` (passes to super) +- On alarm-related broadcasts: reads `AlarmManager.nextAlarmClock`, formats with `DateFormat.getTimeInstance(DateFormat.SHORT)` (respects device 12/24h), saves to `ALARM_TEXT` in DataStore, calls `AlarmWidget().updateAll(context)` +- Uses `goAsync()` for the coroutine work + +## Settings Changes + +### `SettingsUiState` +Add fields: `fontSize: Int = 48`, `alarmWidgetTapPackage: String = ""` + +### `SettingsViewModel` +- `onSetFontSize(size: Int)` — writes `FONT_SIZE`; calls `WeatherWidget().updateAll()` and `AlarmWidget().updateAll()` +- `onSetAlarmWidgetTapPackage(pkg: String)` — writes `ALARM_WIDGET_TAP_PACKAGE`; calls `AlarmWidget().updateAll()` +- `onSetWidgetTapPackage` → renamed to `onSetWeatherWidgetTapPackage` + +### `SettingsScreen` +- Font size slider (range 12–96, step 1, default 48) added to the shared widget settings section near existing color controls +- New "Alarm Widget" section with app-picker for `ALARM_WIDGET_TAP_PACKAGE` + +## Manifest Changes (`AndroidManifest.xml`) + +New receiver: +```xml + + + + + + + + + + +``` + +New file: `res/xml/alarm_widget_info.xml` — widget provider metadata (updatePeriodMillis=0, min dimensions, description). + +Add `` if not present. + +## Update Architecture + +``` +System alarm change + │ + ▼ +AlarmWidgetReceiver.onReceive() + │ + ├── AlarmManager.nextAlarmClock + ├── format with DateFormat.SHORT + ├── save to ALARM_TEXT in DataStore + └── AlarmWidget().updateAll(context) + │ + ▼ + Glance re-renders from DataStore state +``` + +## Files to Create/Modify + +| Action | File | +|-|-| +| Modify | `data/WeatherDataStore.kt` | +| Modify | `widget/WeatherWidget.kt` | +| Modify | `ui/settings/SettingsViewModel.kt` | +| Modify | `ui/settings/SettingsScreen.kt` | +| Modify | `ui/MainActivity.kt` (rename tap package references) | +| Modify | `AndroidManifest.xml` | +| Create | `widget/AlarmWidget.kt` | +| Create | `widget/AlarmWidgetReceiver.kt` | +| Create | `res/xml/alarm_widget_info.xml` | From 9db366f82b704c7262be629147925e8e301e253e Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Mon, 30 Mar 2026 23:09:05 -0500 Subject: [PATCH 02/26] docs: add alarm widget implementation plan --- .../2026-03-30-alarm-widget-implementation.md | 734 ++++++++++++++++++ 1 file changed, 734 insertions(+) create mode 100644 docs/plans/2026-03-30-alarm-widget-implementation.md diff --git a/docs/plans/2026-03-30-alarm-widget-implementation.md b/docs/plans/2026-03-30-alarm-widget-implementation.md new file mode 100644 index 0000000..5fed76c --- /dev/null +++ b/docs/plans/2026-03-30-alarm-widget-implementation.md @@ -0,0 +1,734 @@ +# Alarm Widget + Global Font Size — Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add a home screen alarm widget showing the next scheduled system alarm, and a global font size setting that applies to all widgets. + +**Architecture:** A new `AlarmWidget` (Glance) + `AlarmWidgetReceiver` (BroadcastReceiver) reads the next alarm via `AlarmManager.nextAlarmClock`, caches it to DataStore, and re-renders on `NEXT_ALARM_CLOCK_CHANGED`, `BOOT_COMPLETED`, `TIME_SET`, and `TIMEZONE_CHANGED`. Font size and text color are shared DataStore keys consumed by both widgets. + +**Tech Stack:** Kotlin, Glance 1.1.1, DataStore Preferences, AlarmManager, BroadcastReceiver, Robolectric (tests). + +--- + +### Task 1: Add DataStore keys + +**Files:** +- Modify: `app/src/main/java/com/wassupluke/simpleweather/data/WeatherDataStore.kt:40-56` + +**Step 1: Add the four new keys and default constant** + +Add inside the `WeatherDataStore` object after line 55 (`WIDGET_DYNAMIC_COLOR`): + +```kotlin +val FONT_SIZE = intPreferencesKey("font_size") +val ALARM_WIDGET_TAP_PACKAGE = stringPreferencesKey("alarm_widget_tap_package") +val ALARM_TEXT = stringPreferencesKey("alarm_text") + +const val DEFAULT_FONT_SIZE = 48 +``` + +**Step 2: Compile-check** + +```bash +./gradlew :app:compileDebugKotlin +``` +Expected: BUILD SUCCESSFUL + +**Step 3: Commit** + +```bash +git add app/src/main/java/com/wassupluke/simpleweather/data/WeatherDataStore.kt +git commit -m "feat: add FONT_SIZE, ALARM_TEXT, ALARM_WIDGET_TAP_PACKAGE DataStore keys" +``` + +--- + +### Task 2: Add string resources + +**Files:** +- Modify: `app/src/main/res/values/strings.xml` + +**Step 1: Add new strings** + +Find `strings.xml` and add (alongside the existing widget-related strings): + +```xml +Widget font size +Alarm widget +Alarm widget tap action +Simple Alarm Widget +``` + +**Step 2: Compile-check** + +```bash +./gradlew :app:compileDebugKotlin +``` +Expected: BUILD SUCCESSFUL + +**Step 3: Commit** + +```bash +git add app/src/main/res/values/strings.xml +git commit -m "feat: add string resources for alarm widget and font size" +``` + +--- + +### Task 3: Update SettingsUiState and ViewModel + +**Files:** +- Modify: `app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModel.kt` +- Test: `app/src/test/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModelTest.kt` + +**Step 1: Write failing tests** + +Open `SettingsViewModelTest.kt` and add at the end of the class (before the closing `}`): + +```kotlin +@Test +fun `setFontSize writes to DataStore`() = runTest(testDispatcher) { + val vm = SettingsViewModel(application, mockRepository, testDispatcher) + backgroundScope.launch { vm.uiState.collect {} } + advanceUntilIdle() + vm.setFontSize(32) + advanceUntilIdle() + val state = vm.uiState.filter { it.fontSize == 32 }.first() + assertEquals(32, state.fontSize) +} + +@Test +fun `setAlarmWidgetTapPackage writes to DataStore`() = runTest(testDispatcher) { + val vm = SettingsViewModel(application, mockRepository, testDispatcher) + backgroundScope.launch { vm.uiState.collect {} } + advanceUntilIdle() + vm.setAlarmWidgetTapPackage("com.example.clock") + advanceUntilIdle() + val state = vm.uiState.filter { it.alarmWidgetTapPackage == "com.example.clock" }.first() + assertEquals("com.example.clock", state.alarmWidgetTapPackage) +} + +@Test +fun `fontSize defaults to 48`() = runTest(testDispatcher) { + val vm = SettingsViewModel(application, mockRepository, testDispatcher) + backgroundScope.launch { vm.uiState.collect {} } + advanceUntilIdle() + val state = vm.uiState.filter { it.fontSize == 48 }.first() + assertEquals(48, state.fontSize) +} +``` + +**Step 2: Run tests to verify they fail** + +```bash +./gradlew :app:testDebugUnitTest --tests "com.wassupluke.simpleweather.ui.settings.SettingsViewModelTest.setFontSize*" +./gradlew :app:testDebugUnitTest --tests "com.wassupluke.simpleweather.ui.settings.SettingsViewModelTest.setAlarmWidgetTapPackage*" +``` +Expected: FAIL — `setFontSize` and `setAlarmWidgetTapPackage` do not exist yet. + +**Step 3: Update SettingsUiState** + +Change the `data class SettingsUiState` (lines 20-29) to add two new fields: + +```kotlin +data class SettingsUiState( + val useDeviceLocation: Boolean = false, + val locationQuery: String = "", + val locationDisplayName: String = "", + val tempUnit: String = WeatherDataStore.DEFAULT_TEMP_UNIT, + val updateIntervalMinutes: Int = WeatherDataStore.DEFAULT_INTERVAL_MINUTES, + val widgetTextColor: String = "white", + val widgetDynamicColor: Boolean = false, + val widgetTapPackage: String = "", + val fontSize: Int = WeatherDataStore.DEFAULT_FONT_SIZE, + val alarmWidgetTapPackage: String = "" +) +``` + +**Step 4: Update prefsToUiState** + +In `prefsToUiState` (lines 38-49), add two new fields to the returned `SettingsUiState`: + +```kotlin +fontSize = prefs[WeatherDataStore.FONT_SIZE] ?: WeatherDataStore.DEFAULT_FONT_SIZE, +alarmWidgetTapPackage = prefs[WeatherDataStore.ALARM_WIDGET_TAP_PACKAGE] ?: "" +``` + +**Step 5: Add new ViewModel methods** + +Add after `setWidgetTapPackage` (line 88): + +```kotlin +fun setFontSize(size: Int) { + viewModelScope.launch(dispatcher) { + context.dataStore.edit { it[WeatherDataStore.FONT_SIZE] = size } + WeatherWidget().updateAll(context) + AlarmWidget().updateAll(context) + } +} + +fun setAlarmWidgetTapPackage(pkg: String) { + viewModelScope.launch(dispatcher) { + context.dataStore.edit { it[WeatherDataStore.ALARM_WIDGET_TAP_PACKAGE] = pkg } + AlarmWidget().updateAll(context) + } +} +``` + +Also update `setWidgetTextColor` and `setWidgetDynamicColor` to call `AlarmWidget().updateAll(context)` after the existing `WeatherWidget().updateAll(context)` call. Both widgets share the same color settings. + +Add the import at the top of the file: +```kotlin +import com.wassupluke.simpleweather.widget.AlarmWidget +``` + +**Step 6: Run tests to verify they pass** + +```bash +./gradlew :app:testDebugUnitTest --tests "com.wassupluke.simpleweather.ui.settings.SettingsViewModelTest" +``` +Expected: All tests PASS + +**Step 7: Commit** + +```bash +git add app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModel.kt +git add app/src/test/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModelTest.kt +git commit -m "feat: add fontSize and alarmWidgetTapPackage to SettingsViewModel" +``` + +--- + +### Task 4: Update WeatherWidget to use FONT_SIZE + +**Files:** +- Modify: `app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidget.kt` + +**Step 1: Read the font size from prefs** + +In `WeatherWidget.provideGlance`, after the existing prefs reads (around line 38), add: + +```kotlin +val fontSize = prefs[WeatherDataStore.FONT_SIZE] ?: WeatherDataStore.DEFAULT_FONT_SIZE +``` + +**Step 2: Pass fontSize to WeatherWidgetContent** + +Change the `WeatherWidgetContent` call (lines 69-73) to pass fontSize: + +```kotlin +WeatherWidgetContent( + displayTemp = displayTemp, + textColorProvider = textColorProvider, + tapAction = tapAction, + fontSize = fontSize +) +``` + +**Step 3: Update WeatherWidgetContent signature and body** + +Change the function signature (line 84-88) to add the parameter: + +```kotlin +private fun WeatherWidgetContent( + displayTemp: String, + textColorProvider: ColorProvider, + tapAction: Action, + fontSize: Int +) +``` + +Replace the hardcoded `fontSize = 48.sp` (line 98) with: + +```kotlin +fontSize = fontSize.sp, +``` + +**Step 4: Compile-check** + +```bash +./gradlew :app:compileDebugKotlin +``` +Expected: BUILD SUCCESSFUL + +**Step 5: Commit** + +```bash +git add app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidget.kt +git commit -m "feat: WeatherWidget reads font size from DataStore instead of hardcoded 48sp" +``` + +--- + +### Task 5: Create AlarmWidget + +**Files:** +- Create: `app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt` + +**Step 1: Create the file** + +```kotlin +package com.wassupluke.simpleweather.widget + +import android.annotation.SuppressLint +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.sp +import androidx.datastore.preferences.core.emptyPreferences +import androidx.glance.* +import androidx.glance.action.Action +import androidx.glance.action.actionStartActivity +import androidx.glance.action.clickable +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.provideContent +import androidx.glance.layout.* +import androidx.glance.GlanceTheme +import androidx.glance.text.* +import androidx.glance.unit.ColorProvider +import com.wassupluke.simpleweather.data.WeatherDataStore +import com.wassupluke.simpleweather.data.dataStore +import com.wassupluke.simpleweather.data.parseColorSafe +import com.wassupluke.simpleweather.data.resolveDynamicColor +import com.wassupluke.simpleweather.ui.MainActivity + +class AlarmWidget : GlanceAppWidget() { + + override suspend fun provideGlance(context: Context, id: GlanceId) { + provideContent { + GlanceTheme { + val prefs by context.dataStore.data.collectAsState(initial = emptyPreferences()) + val alarmText = prefs[WeatherDataStore.ALARM_TEXT] ?: "No alarm" + val colorString = prefs[WeatherDataStore.WIDGET_TEXT_COLOR] ?: "white" + val dynamicColor = prefs.resolveDynamicColor() + val fontSize = prefs[WeatherDataStore.FONT_SIZE] ?: WeatherDataStore.DEFAULT_FONT_SIZE + + val textColorProvider: ColorProvider = if (dynamicColor) { + GlanceTheme.colors.primary + } else { + val resolved = parseColorSafe(colorString)?.let { argb -> + Color( + red = android.graphics.Color.red(argb) / 255f, + green = android.graphics.Color.green(argb) / 255f, + blue = android.graphics.Color.blue(argb) / 255f, + alpha = android.graphics.Color.alpha(argb) / 255f + ) + } ?: Color.White + ColorProvider(resolved) + } + + val tapPackage = prefs[WeatherDataStore.ALARM_WIDGET_TAP_PACKAGE] + val tapAction: Action = if (!tapPackage.isNullOrEmpty()) { + val launchIntent = context.packageManager.getLaunchIntentForPackage(tapPackage) + if (launchIntent?.component != null) actionStartActivity(launchIntent.component!!) + else actionStartActivity() + } else { + actionStartActivity() + } + + AlarmWidgetContent( + alarmText = alarmText, + textColorProvider = textColorProvider, + tapAction = tapAction, + fontSize = fontSize + ) + } + } + } +} + +@SuppressLint("RestrictedApi") +@Composable +private fun AlarmWidgetContent( + alarmText: String, + textColorProvider: ColorProvider, + tapAction: Action, + fontSize: Int +) { + Box( + modifier = GlanceModifier + .fillMaxSize() + .clickable(tapAction), + contentAlignment = Alignment.Center + ) { + Text( + text = alarmText, + style = TextStyle( + fontSize = fontSize.sp, + fontWeight = FontWeight.Normal, + color = textColorProvider + ) + ) + } +} +``` + +**Step 2: Compile-check** + +```bash +./gradlew :app:compileDebugKotlin +``` +Expected: BUILD SUCCESSFUL + +**Step 3: Commit** + +```bash +git add app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt +git commit -m "feat: add AlarmWidget Glance widget" +``` + +--- + +### Task 6: Create AlarmWidgetReceiver + +**Files:** +- Create: `app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidgetReceiver.kt` + +**Step 1: Create the file** + +```kotlin +package com.wassupluke.simpleweather.widget + +import android.app.AlarmManager +import android.content.Context +import android.content.Intent +import androidx.datastore.preferences.core.edit +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetReceiver +import com.wassupluke.simpleweather.data.WeatherDataStore +import com.wassupluke.simpleweather.data.dataStore +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.text.DateFormat +import java.util.Date + +class AlarmWidgetReceiver : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget = AlarmWidget() + + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + when (intent.action) { + AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED, + Intent.ACTION_BOOT_COMPLETED, + Intent.ACTION_TIME_CHANGED, + Intent.ACTION_TIMEZONE_CHANGED -> updateAlarmText(context) + } + } + + private fun updateAlarmText(context: Context) { + val pendingResult = goAsync() + CoroutineScope(Dispatchers.IO).launch { + try { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val nextAlarm = alarmManager.nextAlarmClock + val alarmText = if (nextAlarm == null) { + "No alarm" + } else { + DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(nextAlarm.triggerTime)) + } + context.dataStore.edit { it[WeatherDataStore.ALARM_TEXT] = alarmText } + AlarmWidget().updateAll(context) + } finally { + pendingResult.finish() + } + } + } +} +``` + +**Step 2: Compile-check** + +```bash +./gradlew :app:compileDebugKotlin +``` +Expected: BUILD SUCCESSFUL + +**Step 3: Commit** + +```bash +git add app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidgetReceiver.kt +git commit -m "feat: add AlarmWidgetReceiver with alarm change broadcast handling" +``` + +--- + +### Task 7: Widget provider XML and AndroidManifest + +**Files:** +- Create: `app/src/main/res/xml/alarm_widget_info.xml` +- Modify: `app/src/main/AndroidManifest.xml` + +**Step 1: Create alarm_widget_info.xml** + +```xml + + +``` + +**Step 2: Update AndroidManifest.xml** + +Add `RECEIVE_BOOT_COMPLETED` permission after the existing permissions (line 6): + +```xml + +``` + +Add the alarm widget receiver inside `` after the existing `WeatherWidgetReceiver` block (after line 40): + +```xml + + + + + + + + + + +``` + +**Step 3: Full build to verify** + +```bash +./gradlew assembleDebug +``` +Expected: BUILD SUCCESSFUL + +**Step 4: Commit** + +```bash +git add app/src/main/res/xml/alarm_widget_info.xml app/src/main/AndroidManifest.xml +git commit -m "feat: register AlarmWidgetReceiver and alarm_widget_info provider in manifest" +``` + +--- + +### Task 8: Update SettingsScreen UI + +**Files:** +- Modify: `app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt` + +**Step 1: Add new parameters to SettingsScreenContent** + +Add two new lambda parameters to `SettingsScreenContent` after `onSetWidgetDynamicColor` (line 68): + +```kotlin +onSetFontSize: (Int) -> Unit, +onSetAlarmWidgetTapPackage: (String) -> Unit, +``` + +**Step 2: Add showAlarmAppPicker state** + +After the existing `var showAppPicker by remember { mutableStateOf(false) }` (line 79), add: + +```kotlin +var showAlarmAppPicker by remember { mutableStateOf(false) } +``` + +**Step 3: Add font size slider** + +Add the following block after the dynamic color / color section (after the `HorizontalDivider` at line 292, before the weather tap action section): + +```kotlin +HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + +Text(stringResource(R.string.title_font_size), style = MaterialTheme.typography.titleSmall) +Slider( + value = uiState.fontSize.toFloat(), + onValueChange = { onSetFontSize(it.toInt()) }, + valueRange = 12f..96f, + steps = 83, + modifier = Modifier.fillMaxWidth() +) +Text( + text = "${uiState.fontSize}sp", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant +) +``` + +**Step 4: Add alarm widget section** + +Add the following block after the weather widget tap action section (after the existing `showAppPicker` ModalBottomSheet block, before the final `Spacer`): + +```kotlin +HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + +Text(stringResource(R.string.title_alarm_widget), style = MaterialTheme.typography.titleSmall) + +Text( + stringResource(R.string.title_alarm_widget_tap_action), + style = MaterialTheme.typography.titleSmall +) + +val selectedAlarmAppInfo = remember(uiState.alarmWidgetTapPackage) { + if (uiState.alarmWidgetTapPackage.isEmpty()) null + else runCatching { + context.packageManager.getApplicationInfo(uiState.alarmWidgetTapPackage, 0) + }.getOrNull() +} + +Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { showAlarmAppPicker = true } + .padding(vertical = 8.dp) +) { + if (selectedAlarmAppInfo != null) { + val icon by produceState(null, uiState.alarmWidgetTapPackage) { + value = withContext(Dispatchers.IO) { + runCatching { + context.packageManager + .getApplicationIcon(uiState.alarmWidgetTapPackage) + .toBitmap() + .asImageBitmap() + }.getOrNull() + } + } + if (icon != null) { + Image( + bitmap = icon!!, + contentDescription = null, + modifier = Modifier.size(40.dp).padding(end = 8.dp) + ) + } else { + Spacer(Modifier.size(40.dp).padding(end = 8.dp)) + } + Text( + text = selectedAlarmAppInfo.loadLabel(context.packageManager).toString(), + modifier = Modifier.weight(1f) + ) + } else if (uiState.alarmWidgetTapPackage.isNotEmpty()) { + Spacer(Modifier.size(40.dp).padding(end = 8.dp)) + Text( + text = stringResource(R.string.label_selected_app_not_found), + modifier = Modifier.weight(1f), + color = MaterialTheme.colorScheme.error + ) + } else { + Spacer(Modifier.size(40.dp).padding(end = 8.dp)) + Text( + text = stringResource(R.string.label_widget_tap_none), + modifier = Modifier.weight(1f), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Icon( + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) +} + +if (showAlarmAppPicker) { + ModalBottomSheet(onDismissRequest = { showAlarmAppPicker = false }) { + LazyColumn(contentPadding = PaddingValues(bottom = 16.dp)) { + item { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { + onSetAlarmWidgetTapPackage("") + showAlarmAppPicker = false + } + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Spacer(Modifier.size(40.dp).padding(end = 12.dp)) + Text( + text = stringResource(R.string.label_widget_tap_none), + modifier = Modifier.weight(1f) + ) + if (uiState.alarmWidgetTapPackage.isEmpty()) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + } + } + items(installedApps) { entry -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { + onSetAlarmWidgetTapPackage(entry.pkg) + showAlarmAppPicker = false + } + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + if (entry.icon != null) { + Image( + bitmap = entry.icon, + contentDescription = null, + modifier = Modifier.size(40.dp).padding(end = 12.dp) + ) + } else { + Spacer(Modifier.size(40.dp).padding(end = 12.dp)) + } + Text(entry.label, modifier = Modifier.weight(1f)) + if (entry.pkg == uiState.alarmWidgetTapPackage) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + } + } + } + } +} +``` + +**Step 5: Wire up SettingsScreen to ViewModel** + +In the `SettingsScreen` composable (around line 442), add the two new lambda arguments to the `SettingsScreenContent` call: + +```kotlin +onSetFontSize = { viewModel.setFontSize(it) }, +onSetAlarmWidgetTapPackage = { viewModel.setAlarmWidgetTapPackage(it) }, +``` + +**Step 6: Update all preview calls** + +Each `SettingsScreenContent(...)` call in the preview functions needs the two new lambdas added: + +```kotlin +onSetFontSize = {}, +onSetAlarmWidgetTapPackage = {}, +``` + +**Step 7: Full build and tests** + +```bash +./gradlew assembleDebug +./gradlew :app:testDebugUnitTest +``` +Expected: BUILD SUCCESSFUL, all tests PASS + +**Step 8: Commit** + +```bash +git add app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt +git commit -m "feat: add font size slider and alarm widget tap-action picker to Settings" +``` From 637ea710986aaf869bcb2838f37bcc7fd826cf58 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Mon, 30 Mar 2026 23:10:37 -0500 Subject: [PATCH 03/26] feat: add FONT_SIZE, ALARM_TEXT, ALARM_WIDGET_TAP_PACKAGE DataStore keys --- .../com/wassupluke/simpleweather/data/WeatherDataStore.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/wassupluke/simpleweather/data/WeatherDataStore.kt b/app/src/main/java/com/wassupluke/simpleweather/data/WeatherDataStore.kt index f48a592..68e01d1 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/data/WeatherDataStore.kt +++ b/app/src/main/java/com/wassupluke/simpleweather/data/WeatherDataStore.kt @@ -53,4 +53,9 @@ object WeatherDataStore { val WIDGET_TEXT_COLOR = stringPreferencesKey("widget_text_color") val WIDGET_TAP_PACKAGE = stringPreferencesKey("widget_tap_package") val WIDGET_DYNAMIC_COLOR = booleanPreferencesKey("widget_dynamic_color") + val FONT_SIZE = intPreferencesKey("font_size") + val ALARM_WIDGET_TAP_PACKAGE = stringPreferencesKey("alarm_widget_tap_package") + val ALARM_TEXT = stringPreferencesKey("alarm_text") + + const val DEFAULT_FONT_SIZE = 48 } From 0ed2a6aa4f6e742442f9c0046c094fb814459655 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Mon, 30 Mar 2026 23:11:26 -0500 Subject: [PATCH 04/26] feat: add string resources for alarm widget and font size --- app/src/main/res/values/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 08b7a63..7ddf37a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,6 +24,11 @@ Set + Widget font size + Alarm widget + Alarm widget tap action + Simple Alarm Widget + Widget tap action None (opens Simple Weather) Selected app not found From 26bcb865b407c8f6400249927bbd550d0e4b0b6e Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Mon, 30 Mar 2026 23:13:23 -0500 Subject: [PATCH 05/26] feat: add fontSize and alarmWidgetTapPackage to SettingsViewModel (stub AlarmWidget) --- .../ui/settings/SettingsViewModel.kt | 26 ++++++++++++++-- .../simpleweather/widget/AlarmWidget.kt | 9 ++++++ .../ui/settings/SettingsViewModelTest.kt | 31 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt diff --git a/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModel.kt index 8a34e80..3bb03e8 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModel.kt @@ -9,6 +9,7 @@ import com.wassupluke.simpleweather.data.WeatherRepository import com.wassupluke.simpleweather.data.dataStore import com.wassupluke.simpleweather.data.resolveDynamicColor import androidx.glance.appwidget.updateAll +import com.wassupluke.simpleweather.widget.AlarmWidget import com.wassupluke.simpleweather.widget.WeatherWidget import com.wassupluke.simpleweather.worker.WorkScheduler import kotlinx.coroutines.CoroutineDispatcher @@ -25,7 +26,9 @@ data class SettingsUiState( val updateIntervalMinutes: Int = WeatherDataStore.DEFAULT_INTERVAL_MINUTES, val widgetTextColor: String = "white", val widgetDynamicColor: Boolean = false, - val widgetTapPackage: String = "" + val widgetTapPackage: String = "", + val fontSize: Int = WeatherDataStore.DEFAULT_FONT_SIZE, + val alarmWidgetTapPackage: String = "" ) class SettingsViewModel( @@ -44,7 +47,9 @@ class SettingsViewModel( updateIntervalMinutes = prefs[WeatherDataStore.UPDATE_INTERVAL_MINUTES] ?: WeatherDataStore.DEFAULT_INTERVAL_MINUTES, widgetTextColor = prefs[WeatherDataStore.WIDGET_TEXT_COLOR] ?: "white", widgetDynamicColor = prefs.resolveDynamicColor(), - widgetTapPackage = prefs[WeatherDataStore.WIDGET_TAP_PACKAGE] ?: "" + widgetTapPackage = prefs[WeatherDataStore.WIDGET_TAP_PACKAGE] ?: "", + fontSize = prefs[WeatherDataStore.FONT_SIZE] ?: WeatherDataStore.DEFAULT_FONT_SIZE, + alarmWidgetTapPackage = prefs[WeatherDataStore.ALARM_WIDGET_TAP_PACKAGE] ?: "" ) } @@ -70,6 +75,7 @@ class SettingsViewModel( viewModelScope.launch(dispatcher) { context.dataStore.edit { it[WeatherDataStore.WIDGET_TEXT_COLOR] = raw } WeatherWidget().updateAll(context) + AlarmWidget().updateAll(context) } } @@ -77,6 +83,7 @@ class SettingsViewModel( viewModelScope.launch(dispatcher) { context.dataStore.edit { it[WeatherDataStore.WIDGET_DYNAMIC_COLOR] = enabled } WeatherWidget().updateAll(context) + AlarmWidget().updateAll(context) } } @@ -87,6 +94,21 @@ class SettingsViewModel( } } + fun setFontSize(size: Int) { + viewModelScope.launch(dispatcher) { + context.dataStore.edit { it[WeatherDataStore.FONT_SIZE] = size } + WeatherWidget().updateAll(context) + AlarmWidget().updateAll(context) + } + } + + fun setAlarmWidgetTapPackage(pkg: String) { + viewModelScope.launch(dispatcher) { + context.dataStore.edit { it[WeatherDataStore.ALARM_WIDGET_TAP_PACKAGE] = pkg } + AlarmWidget().updateAll(context) + } + } + fun setUseDeviceLocation(use: Boolean) { viewModelScope.launch(dispatcher) { context.dataStore.edit { it[WeatherDataStore.USE_DEVICE_LOCATION] = use } diff --git a/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt b/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt new file mode 100644 index 0000000..37c8245 --- /dev/null +++ b/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt @@ -0,0 +1,9 @@ +package com.wassupluke.simpleweather.widget + +import android.content.Context +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.GlanceId + +class AlarmWidget : GlanceAppWidget() { + override suspend fun provideGlance(context: Context, id: GlanceId) {} +} diff --git a/app/src/test/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModelTest.kt b/app/src/test/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModelTest.kt index 365aa29..70b93ea 100644 --- a/app/src/test/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModelTest.kt +++ b/app/src/test/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModelTest.kt @@ -138,4 +138,35 @@ class SettingsViewModelTest { val state = vm.uiState.filter { !it.widgetDynamicColor }.first() assertEquals(false, state.widgetDynamicColor) } + + @Test + fun `setFontSize writes to DataStore`() = runTest(testDispatcher) { + val vm = SettingsViewModel(application, mockRepository, testDispatcher) + backgroundScope.launch { vm.uiState.collect {} } + advanceUntilIdle() + vm.setFontSize(32) + advanceUntilIdle() + val state = vm.uiState.filter { it.fontSize == 32 }.first() + assertEquals(32, state.fontSize) + } + + @Test + fun `setAlarmWidgetTapPackage writes to DataStore`() = runTest(testDispatcher) { + val vm = SettingsViewModel(application, mockRepository, testDispatcher) + backgroundScope.launch { vm.uiState.collect {} } + advanceUntilIdle() + vm.setAlarmWidgetTapPackage("com.example.clock") + advanceUntilIdle() + val state = vm.uiState.filter { it.alarmWidgetTapPackage == "com.example.clock" }.first() + assertEquals("com.example.clock", state.alarmWidgetTapPackage) + } + + @Test + fun `fontSize defaults to 48`() = runTest(testDispatcher) { + val vm = SettingsViewModel(application, mockRepository, testDispatcher) + backgroundScope.launch { vm.uiState.collect {} } + advanceUntilIdle() + val state = vm.uiState.filter { it.fontSize == 48 }.first() + assertEquals(48, state.fontSize) + } } From a4d02a8c4e5eae1c39c6f82f2566ddc525992fa7 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Mon, 30 Mar 2026 23:15:12 -0500 Subject: [PATCH 06/26] feat: WeatherWidget reads FONT_SIZE from DataStore instead of hardcoded 48sp --- .../wassupluke/simpleweather/widget/WeatherWidget.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidget.kt b/app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidget.kt index 41bb317..7169733 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidget.kt +++ b/app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidget.kt @@ -66,10 +66,13 @@ class WeatherWidget : GlanceAppWidget() { actionStartActivity() } + val fontSize = prefs[WeatherDataStore.FONT_SIZE] ?: WeatherDataStore.DEFAULT_FONT_SIZE + WeatherWidgetContent( displayTemp = displayTemp, textColorProvider = textColorProvider, - tapAction = tapAction + tapAction = tapAction, + fontSize = fontSize ) } } @@ -84,7 +87,8 @@ class WeatherWidget : GlanceAppWidget() { private fun WeatherWidgetContent( displayTemp: String, textColorProvider: ColorProvider, - tapAction: Action + tapAction: Action, + fontSize: Int ) { Box( modifier = GlanceModifier @@ -95,7 +99,7 @@ private fun WeatherWidgetContent( Text( text = displayTemp, style = TextStyle( - fontSize = 48.sp, + fontSize = fontSize.sp, fontWeight = FontWeight.Normal, color = textColorProvider ) From 5d848ba834ecdf4d3cb5fd5657f5eb611b826736 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Mon, 30 Mar 2026 23:21:04 -0500 Subject: [PATCH 07/26] feat: implement AlarmWidget Glance widget --- .../simpleweather/widget/AlarmWidget.kt | 90 ++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt b/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt index 37c8245..829bb0c 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt +++ b/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt @@ -1,9 +1,95 @@ package com.wassupluke.simpleweather.widget +import android.annotation.SuppressLint import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.sp +import androidx.datastore.preferences.core.emptyPreferences +import androidx.glance.* +import androidx.glance.action.Action +import androidx.glance.action.actionStartActivity +import androidx.glance.action.clickable import androidx.glance.appwidget.GlanceAppWidget -import androidx.glance.GlanceId +import androidx.glance.appwidget.provideContent +import androidx.glance.layout.* +import androidx.glance.GlanceTheme +import androidx.glance.text.* +import androidx.glance.unit.ColorProvider +import com.wassupluke.simpleweather.data.WeatherDataStore +import com.wassupluke.simpleweather.data.dataStore +import com.wassupluke.simpleweather.data.parseColorSafe +import com.wassupluke.simpleweather.data.resolveDynamicColor +import com.wassupluke.simpleweather.ui.MainActivity class AlarmWidget : GlanceAppWidget() { - override suspend fun provideGlance(context: Context, id: GlanceId) {} + + override suspend fun provideGlance(context: Context, id: GlanceId) { + provideContent { + GlanceTheme { + val prefs by context.dataStore.data.collectAsState(initial = emptyPreferences()) + val alarmText = prefs[WeatherDataStore.ALARM_TEXT] ?: "No alarm" + val colorString = prefs[WeatherDataStore.WIDGET_TEXT_COLOR] ?: "white" + val dynamicColor = prefs.resolveDynamicColor() + val fontSize = prefs[WeatherDataStore.FONT_SIZE] ?: WeatherDataStore.DEFAULT_FONT_SIZE + + val textColorProvider: ColorProvider = if (dynamicColor) { + GlanceTheme.colors.primary + } else { + val resolved = parseColorSafe(colorString)?.let { argb -> + Color( + red = android.graphics.Color.red(argb) / 255f, + green = android.graphics.Color.green(argb) / 255f, + blue = android.graphics.Color.blue(argb) / 255f, + alpha = android.graphics.Color.alpha(argb) / 255f + ) + } ?: Color.White + ColorProvider(resolved) + } + + val tapPackage = prefs[WeatherDataStore.ALARM_WIDGET_TAP_PACKAGE] + val tapAction: Action = if (!tapPackage.isNullOrEmpty()) { + val launchIntent = context.packageManager.getLaunchIntentForPackage(tapPackage) + if (launchIntent?.component != null) actionStartActivity(launchIntent.component!!) + else actionStartActivity() + } else { + actionStartActivity() + } + + AlarmWidgetContent( + alarmText = alarmText, + textColorProvider = textColorProvider, + tapAction = tapAction, + fontSize = fontSize + ) + } + } + } +} + +@SuppressLint("RestrictedApi") +@Composable +private fun AlarmWidgetContent( + alarmText: String, + textColorProvider: ColorProvider, + tapAction: Action, + fontSize: Int +) { + Box( + modifier = GlanceModifier + .fillMaxSize() + .clickable(tapAction), + contentAlignment = Alignment.Center + ) { + Text( + text = alarmText, + style = TextStyle( + fontSize = fontSize.sp, + fontWeight = FontWeight.Normal, + color = textColorProvider + ) + ) + } } From 3fcb49592cccd2ef9925ffea70fbcb57e709e77c Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Mon, 30 Mar 2026 23:22:29 -0500 Subject: [PATCH 08/26] feat: add AlarmWidgetReceiver with alarm change broadcast handling --- .../widget/AlarmWidgetReceiver.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidgetReceiver.kt diff --git a/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidgetReceiver.kt b/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidgetReceiver.kt new file mode 100644 index 0000000..990b2b2 --- /dev/null +++ b/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidgetReceiver.kt @@ -0,0 +1,49 @@ +package com.wassupluke.simpleweather.widget + +import android.app.AlarmManager +import android.content.Context +import android.content.Intent +import androidx.datastore.preferences.core.edit +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetReceiver +import androidx.glance.appwidget.updateAll +import com.wassupluke.simpleweather.data.WeatherDataStore +import com.wassupluke.simpleweather.data.dataStore +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.text.DateFormat +import java.util.Date + +class AlarmWidgetReceiver : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget = AlarmWidget() + + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + when (intent.action) { + AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED, + Intent.ACTION_BOOT_COMPLETED, + Intent.ACTION_TIME_CHANGED, + Intent.ACTION_TIMEZONE_CHANGED -> updateAlarmText(context) + } + } + + private fun updateAlarmText(context: Context) { + val pendingResult = goAsync() + CoroutineScope(Dispatchers.IO).launch { + try { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val nextAlarm = alarmManager.nextAlarmClock + val alarmText = if (nextAlarm == null) { + "No alarm" + } else { + DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(nextAlarm.triggerTime)) + } + context.dataStore.edit { it[WeatherDataStore.ALARM_TEXT] = alarmText } + AlarmWidget().updateAll(context) + } finally { + pendingResult.finish() + } + } + } +} From ca67d44fdd1bcc8c550abfac185c901ba7a4d755 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Mon, 30 Mar 2026 23:23:25 -0500 Subject: [PATCH 09/26] feat: register AlarmWidgetReceiver and alarm_widget_info in manifest --- app/src/main/AndroidManifest.xml | 16 ++++++++++++++++ app/src/main/res/xml/alarm_widget_info.xml | 10 ++++++++++ 2 files changed, 26 insertions(+) create mode 100644 app/src/main/res/xml/alarm_widget_info.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6fb6cc4..a51e4df 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + @@ -39,5 +40,20 @@ android:resource="@xml/weather_widget_info" /> + + + + + + + + + + + diff --git a/app/src/main/res/xml/alarm_widget_info.xml b/app/src/main/res/xml/alarm_widget_info.xml new file mode 100644 index 0000000..e445b6f --- /dev/null +++ b/app/src/main/res/xml/alarm_widget_info.xml @@ -0,0 +1,10 @@ + + From 4695608db4c996e13b2d109cc89ed4aab4b163e9 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Tue, 31 Mar 2026 06:36:39 -0500 Subject: [PATCH 10/26] feat: add font size slider and alarm widget tap-action picker to Settings --- .../ui/settings/SettingsScreen.kt | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt b/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt index e32df8d..97d4e6b 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt @@ -66,6 +66,8 @@ internal fun SettingsScreenContent( onSetWidgetTextColor: (String) -> Unit, onSetWidgetTapPackage: (String) -> Unit, onSetWidgetDynamicColor: (Boolean) -> Unit, + onSetFontSize: (Int) -> Unit, + onSetAlarmWidgetTapPackage: (String) -> Unit, ) { var locationInput by remember { mutableStateOf("") } var locationInputInitialized by remember { mutableStateOf(false) } @@ -77,6 +79,7 @@ internal fun SettingsScreenContent( } var showAppPicker by remember { mutableStateOf(false) } + var showAlarmAppPicker by remember { mutableStateOf(false) } val context = LocalContext.current val installedApps by produceState(emptyList()) { @@ -291,6 +294,22 @@ internal fun SettingsScreenContent( HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + Text(stringResource(R.string.title_font_size), style = MaterialTheme.typography.titleSmall) + Slider( + value = uiState.fontSize.toFloat(), + onValueChange = { onSetFontSize(it.toInt()) }, + valueRange = 12f..96f, + steps = 83, + modifier = Modifier.fillMaxWidth() + ) + Text( + text = "${uiState.fontSize}sp", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + Text( stringResource(R.string.title_widget_tap_action), style = MaterialTheme.typography.titleSmall @@ -418,6 +437,137 @@ internal fun SettingsScreenContent( } } + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + + Text(stringResource(R.string.title_alarm_widget), style = MaterialTheme.typography.titleSmall) + + Text( + stringResource(R.string.title_alarm_widget_tap_action), + style = MaterialTheme.typography.titleSmall + ) + + val selectedAlarmAppInfo = remember(uiState.alarmWidgetTapPackage) { + if (uiState.alarmWidgetTapPackage.isEmpty()) null + else runCatching { + context.packageManager.getApplicationInfo(uiState.alarmWidgetTapPackage, 0) + }.getOrNull() + } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { showAlarmAppPicker = true } + .padding(vertical = 8.dp) + ) { + if (selectedAlarmAppInfo != null) { + val icon by produceState(null, uiState.alarmWidgetTapPackage) { + value = withContext(Dispatchers.IO) { + runCatching { + context.packageManager + .getApplicationIcon(uiState.alarmWidgetTapPackage) + .toBitmap() + .asImageBitmap() + }.getOrNull() + } + } + if (icon != null) { + Image( + bitmap = icon!!, + contentDescription = null, + modifier = Modifier.size(40.dp).padding(end = 8.dp) + ) + } else { + Spacer(Modifier.size(40.dp).padding(end = 8.dp)) + } + Text( + text = selectedAlarmAppInfo.loadLabel(context.packageManager).toString(), + modifier = Modifier.weight(1f) + ) + } else if (uiState.alarmWidgetTapPackage.isNotEmpty()) { + Spacer(Modifier.size(40.dp).padding(end = 8.dp)) + Text( + text = stringResource(R.string.label_selected_app_not_found), + modifier = Modifier.weight(1f), + color = MaterialTheme.colorScheme.error + ) + } else { + Spacer(Modifier.size(40.dp).padding(end = 8.dp)) + Text( + text = stringResource(R.string.label_widget_tap_none), + modifier = Modifier.weight(1f), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Icon( + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + if (showAlarmAppPicker) { + ModalBottomSheet(onDismissRequest = { showAlarmAppPicker = false }) { + LazyColumn(contentPadding = PaddingValues(bottom = 16.dp)) { + item { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { + onSetAlarmWidgetTapPackage("") + showAlarmAppPicker = false + } + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Spacer(Modifier.size(40.dp).padding(end = 12.dp)) + Text( + text = stringResource(R.string.label_widget_tap_none), + modifier = Modifier.weight(1f) + ) + if (uiState.alarmWidgetTapPackage.isEmpty()) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + } + } + items(installedApps) { entry -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { + onSetAlarmWidgetTapPackage(entry.pkg) + showAlarmAppPicker = false + } + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + if (entry.icon != null) { + Image( + bitmap = entry.icon, + contentDescription = null, + modifier = Modifier.size(40.dp).padding(end = 12.dp) + ) + } else { + Spacer(Modifier.size(40.dp).padding(end = 12.dp)) + } + Text(entry.label, modifier = Modifier.weight(1f)) + if (entry.pkg == uiState.alarmWidgetTapPackage) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + } + } + } + } + } + Spacer(Modifier.height(16.dp)) } } @@ -449,6 +599,8 @@ fun SettingsScreen( onSetWidgetTextColor = { viewModel.setWidgetTextColor(it) }, onSetWidgetTapPackage = { viewModel.setWidgetTapPackage(it) }, onSetWidgetDynamicColor = { viewModel.setWidgetDynamicColor(it) }, + onSetFontSize = { viewModel.setFontSize(it) }, + onSetAlarmWidgetTapPackage = { viewModel.setAlarmWidgetTapPackage(it) }, ) } @@ -476,6 +628,8 @@ private fun SettingsScreenEmptyPreview() { onSetWidgetTextColor = {}, onSetWidgetTapPackage = {}, onSetWidgetDynamicColor = {}, + onSetFontSize = {}, + onSetAlarmWidgetTapPackage = {}, ) } } @@ -497,6 +651,8 @@ private fun SettingsScreenDeviceLocationPreview() { onSetWidgetTextColor = {}, onSetWidgetTapPackage = {}, onSetWidgetDynamicColor = {}, + onSetFontSize = {}, + onSetAlarmWidgetTapPackage = {}, ) } } @@ -522,6 +678,8 @@ private fun SettingsScreenManualLocationPreview() { onSetWidgetTextColor = {}, onSetWidgetTapPackage = {}, onSetWidgetDynamicColor = {}, + onSetFontSize = {}, + onSetAlarmWidgetTapPackage = {}, ) } } From b5d96bfbe9695bece863151e441cf00fd312c2a3 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Tue, 31 Mar 2026 06:38:33 -0500 Subject: [PATCH 11/26] fix: remove redundant double section title in alarm widget settings --- .../wassupluke/simpleweather/ui/settings/SettingsScreen.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt b/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt index 97d4e6b..1ed1984 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt @@ -441,11 +441,6 @@ internal fun SettingsScreenContent( Text(stringResource(R.string.title_alarm_widget), style = MaterialTheme.typography.titleSmall) - Text( - stringResource(R.string.title_alarm_widget_tap_action), - style = MaterialTheme.typography.titleSmall - ) - val selectedAlarmAppInfo = remember(uiState.alarmWidgetTapPackage) { if (uiState.alarmWidgetTapPackage.isEmpty()) null else runCatching { From 4e892810efed227b809b8e9a065fd84cf7f1020f Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Tue, 31 Mar 2026 21:42:06 -0500 Subject: [PATCH 12/26] refactor: rename package and app to Wasses Widgets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit applicationId and namespace: com.wassupluke.simpleweather → com.wassupluke.wasseswidgets App name: Simple Weather → Wasses Widgets --- app/build.gradle.kts | 4 ++-- .../data/WeatherDataStore.kt | 2 +- .../data/WeatherRepository.kt | 6 ++--- .../data/api/NetworkModule.kt | 2 +- .../data/api/OpenMeteoService.kt | 2 +- .../data/api/WeatherApiModels.kt | 2 +- .../ui/MainActivity.kt | 22 ++++++++++--------- .../ui/settings/SettingsScreen.kt | 8 +++---- .../ui/settings/SettingsViewModel.kt | 16 +++++++------- .../ui/theme/SimpleWeatherTheme.kt | 16 +++++++++++++- .../widget/AlarmWidget.kt | 12 +++++----- .../widget/AlarmWidgetReceiver.kt | 6 ++--- .../widget/WeatherWidget.kt | 12 +++++----- .../widget/WeatherWidgetReceiver.kt | 2 +- .../worker/WeatherFetchWorker.kt | 10 ++++----- .../worker/WorkScheduler.kt | 2 +- app/src/main/res/values/strings.xml | 11 +++++----- .../data/WeatherDataStoreTest.kt | 2 +- .../data/WeatherRepositoryTest.kt | 4 ++-- .../data/api/OpenMeteoServiceTest.kt | 2 +- .../ui/settings/SettingsViewModelTest.kt | 8 +++---- 21 files changed, 83 insertions(+), 68 deletions(-) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/data/WeatherDataStore.kt (98%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/data/WeatherRepository.kt (93%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/data/api/NetworkModule.kt (95%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/data/api/OpenMeteoService.kt (93%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/data/api/WeatherApiModels.kt (92%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/ui/MainActivity.kt (83%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/ui/settings/SettingsScreen.kt (99%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/ui/settings/SettingsViewModel.kt (93%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/ui/theme/SimpleWeatherTheme.kt (52%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/widget/AlarmWidget.kt (91%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/widget/AlarmWidgetReceiver.kt (91%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/widget/WeatherWidget.kt (92%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/widget/WeatherWidgetReceiver.kt (84%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/worker/WeatherFetchWorker.kt (76%) rename app/src/main/java/com/wassupluke/{simpleweather => wasseswidgets}/worker/WorkScheduler.kt (95%) rename app/src/test/java/com/wassupluke/{simpleweather => wasseswidgets}/data/WeatherDataStoreTest.kt (97%) rename app/src/test/java/com/wassupluke/{simpleweather => wasseswidgets}/data/WeatherRepositoryTest.kt (97%) rename app/src/test/java/com/wassupluke/{simpleweather => wasseswidgets}/data/api/OpenMeteoServiceTest.kt (97%) rename app/src/test/java/com/wassupluke/{simpleweather => wasseswidgets}/ui/settings/SettingsViewModelTest.kt (96%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e9cc4c6..9b0d705 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,11 +13,11 @@ val gitVersionName = providers.exec { }.standardOutput.asText.map { it.trim().removePrefix("v").ifEmpty { "0.0.0" } } android { - namespace = "com.wassupluke.simpleweather" + namespace = "com.wassupluke.wasseswidgets" compileSdk = 36 defaultConfig { - applicationId = "com.wassupluke.simpleweather" + applicationId = "com.wassupluke.wasseswidgets" minSdk = 26 targetSdk = 36 versionCode = gitVersionCode.get() diff --git a/app/src/main/java/com/wassupluke/simpleweather/data/WeatherDataStore.kt b/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt similarity index 98% rename from app/src/main/java/com/wassupluke/simpleweather/data/WeatherDataStore.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt index 68e01d1..f52bf2b 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/data/WeatherDataStore.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.data +package com.wassupluke.wasseswidgets.data import android.content.Context import android.graphics.Color as AndroidColor diff --git a/app/src/main/java/com/wassupluke/simpleweather/data/WeatherRepository.kt b/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherRepository.kt similarity index 93% rename from app/src/main/java/com/wassupluke/simpleweather/data/WeatherRepository.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherRepository.kt index 6750793..b5570e9 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/data/WeatherRepository.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherRepository.kt @@ -1,10 +1,10 @@ -package com.wassupluke.simpleweather.data +package com.wassupluke.wasseswidgets.data import android.content.Context import android.location.Geocoder import androidx.datastore.preferences.core.edit -import com.wassupluke.simpleweather.data.api.NetworkModule -import com.wassupluke.simpleweather.data.api.OpenMeteoService +import com.wassupluke.wasseswidgets.data.api.NetworkModule +import com.wassupluke.wasseswidgets.data.api.OpenMeteoService import java.util.Locale class WeatherRepository( diff --git a/app/src/main/java/com/wassupluke/simpleweather/data/api/NetworkModule.kt b/app/src/main/java/com/wassupluke/wasseswidgets/data/api/NetworkModule.kt similarity index 95% rename from app/src/main/java/com/wassupluke/simpleweather/data/api/NetworkModule.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/data/api/NetworkModule.kt index 8044239..3b7e6ff 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/data/api/NetworkModule.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/data/api/NetworkModule.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.data.api +package com.wassupluke.wasseswidgets.data.api import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import kotlinx.serialization.json.Json diff --git a/app/src/main/java/com/wassupluke/simpleweather/data/api/OpenMeteoService.kt b/app/src/main/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoService.kt similarity index 93% rename from app/src/main/java/com/wassupluke/simpleweather/data/api/OpenMeteoService.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoService.kt index 595a298..8b0d181 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/data/api/OpenMeteoService.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoService.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.data.api +package com.wassupluke.wasseswidgets.data.api import retrofit2.http.GET import retrofit2.http.Query diff --git a/app/src/main/java/com/wassupluke/simpleweather/data/api/WeatherApiModels.kt b/app/src/main/java/com/wassupluke/wasseswidgets/data/api/WeatherApiModels.kt similarity index 92% rename from app/src/main/java/com/wassupluke/simpleweather/data/api/WeatherApiModels.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/data/api/WeatherApiModels.kt index fc077de..738bb02 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/data/api/WeatherApiModels.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/data/api/WeatherApiModels.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.data.api +package com.wassupluke.wasseswidgets.data.api import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/wassupluke/simpleweather/ui/MainActivity.kt b/app/src/main/java/com/wassupluke/wasseswidgets/ui/MainActivity.kt similarity index 83% rename from app/src/main/java/com/wassupluke/simpleweather/ui/MainActivity.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/ui/MainActivity.kt index 83dfa9c..1d7d416 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/ui/MainActivity.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/ui/MainActivity.kt @@ -1,24 +1,25 @@ -package com.wassupluke.simpleweather.ui +package com.wassupluke.wasseswidgets.ui import android.Manifest import android.annotation.SuppressLint import android.content.pm.PackageManager import android.os.Bundle -import androidx.core.content.ContextCompat import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels +import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import com.wassupluke.simpleweather.data.WeatherDataStore -import com.wassupluke.simpleweather.data.WeatherRepository -import com.wassupluke.simpleweather.data.dataStore -import com.wassupluke.simpleweather.ui.settings.SettingsScreen -import com.wassupluke.simpleweather.ui.settings.SettingsViewModel -import com.wassupluke.simpleweather.ui.theme.SimpleWeatherTheme -import com.wassupluke.simpleweather.worker.WorkScheduler -import com.google.android.gms.location.LocationServices import androidx.lifecycle.lifecycleScope +import com.google.android.gms.location.LocationServices +import com.wassupluke.wasseswidgets.data.WeatherDataStore +import com.wassupluke.wasseswidgets.data.WeatherRepository +import com.wassupluke.wasseswidgets.data.dataStore +import com.wassupluke.wasseswidgets.ui.settings.SettingsScreen +import com.wassupluke.wasseswidgets.ui.settings.SettingsViewModel +import com.wassupluke.wasseswidgets.ui.theme.SimpleWeatherTheme +import com.wassupluke.wasseswidgets.worker.WorkScheduler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -37,6 +38,7 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() setContent { SimpleWeatherTheme { diff --git a/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt b/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsScreen.kt similarity index 99% rename from app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsScreen.kt index 1ed1984..0b15d2c 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsScreen.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.ui.settings +package com.wassupluke.wasseswidgets.ui.settings import android.Manifest import android.content.Intent @@ -35,9 +35,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.wassupluke.simpleweather.R -import com.wassupluke.simpleweather.data.parseColorSafe -import com.wassupluke.simpleweather.ui.theme.SimpleWeatherTheme +import com.wassupluke.wasseswidgets.R +import com.wassupluke.wasseswidgets.data.parseColorSafe +import com.wassupluke.wasseswidgets.ui.theme.SimpleWeatherTheme import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModel.kt similarity index 93% rename from app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModel.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModel.kt index 3bb03e8..a0c4aea 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModel.kt @@ -1,17 +1,17 @@ -package com.wassupluke.simpleweather.ui.settings +package com.wassupluke.wasseswidgets.ui.settings import android.app.Application import androidx.datastore.preferences.core.edit import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import com.wassupluke.simpleweather.data.WeatherDataStore -import com.wassupluke.simpleweather.data.WeatherRepository -import com.wassupluke.simpleweather.data.dataStore -import com.wassupluke.simpleweather.data.resolveDynamicColor +import com.wassupluke.wasseswidgets.data.WeatherDataStore +import com.wassupluke.wasseswidgets.data.WeatherRepository +import com.wassupluke.wasseswidgets.data.dataStore +import com.wassupluke.wasseswidgets.data.resolveDynamicColor import androidx.glance.appwidget.updateAll -import com.wassupluke.simpleweather.widget.AlarmWidget -import com.wassupluke.simpleweather.widget.WeatherWidget -import com.wassupluke.simpleweather.worker.WorkScheduler +import com.wassupluke.wasseswidgets.widget.AlarmWidget +import com.wassupluke.wasseswidgets.widget.WeatherWidget +import com.wassupluke.wasseswidgets.worker.WorkScheduler import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* diff --git a/app/src/main/java/com/wassupluke/simpleweather/ui/theme/SimpleWeatherTheme.kt b/app/src/main/java/com/wassupluke/wasseswidgets/ui/theme/SimpleWeatherTheme.kt similarity index 52% rename from app/src/main/java/com/wassupluke/simpleweather/ui/theme/SimpleWeatherTheme.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/ui/theme/SimpleWeatherTheme.kt index a530558..38abdaf 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/ui/theme/SimpleWeatherTheme.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/ui/theme/SimpleWeatherTheme.kt @@ -1,11 +1,16 @@ -package com.wassupluke.simpleweather.ui.theme +package com.wassupluke.wasseswidgets.ui.theme +import android.app.Activity import android.os.Build import androidx.compose.material3.MaterialTheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat @Composable fun SimpleWeatherTheme(content: @Composable () -> Unit) { @@ -16,6 +21,15 @@ fun SimpleWeatherTheme(content: @Composable () -> Unit) { lightColorScheme() } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + // Set status bar icons to dark since we are using a light theme + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = true + } + } + MaterialTheme( colorScheme = colorScheme, content = content diff --git a/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt similarity index 91% rename from app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt index 829bb0c..bc536ec 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.widget +package com.wassupluke.wasseswidgets.widget import android.annotation.SuppressLint import android.content.Context @@ -18,11 +18,11 @@ import androidx.glance.layout.* import androidx.glance.GlanceTheme import androidx.glance.text.* import androidx.glance.unit.ColorProvider -import com.wassupluke.simpleweather.data.WeatherDataStore -import com.wassupluke.simpleweather.data.dataStore -import com.wassupluke.simpleweather.data.parseColorSafe -import com.wassupluke.simpleweather.data.resolveDynamicColor -import com.wassupluke.simpleweather.ui.MainActivity +import com.wassupluke.wasseswidgets.data.WeatherDataStore +import com.wassupluke.wasseswidgets.data.dataStore +import com.wassupluke.wasseswidgets.data.parseColorSafe +import com.wassupluke.wasseswidgets.data.resolveDynamicColor +import com.wassupluke.wasseswidgets.ui.MainActivity class AlarmWidget : GlanceAppWidget() { diff --git a/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidgetReceiver.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt similarity index 91% rename from app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidgetReceiver.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt index 990b2b2..00de669 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/widget/AlarmWidgetReceiver.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.widget +package com.wassupluke.wasseswidgets.widget import android.app.AlarmManager import android.content.Context @@ -7,8 +7,8 @@ import androidx.datastore.preferences.core.edit import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidgetReceiver import androidx.glance.appwidget.updateAll -import com.wassupluke.simpleweather.data.WeatherDataStore -import com.wassupluke.simpleweather.data.dataStore +import com.wassupluke.wasseswidgets.data.WeatherDataStore +import com.wassupluke.wasseswidgets.data.dataStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt similarity index 92% rename from app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidget.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt index 7169733..d4107f3 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.widget +package com.wassupluke.wasseswidgets.widget import android.annotation.SuppressLint import android.content.Context @@ -18,11 +18,11 @@ import androidx.glance.layout.* import androidx.glance.GlanceTheme import androidx.glance.text.* import androidx.glance.unit.ColorProvider -import com.wassupluke.simpleweather.data.WeatherDataStore -import com.wassupluke.simpleweather.data.dataStore -import com.wassupluke.simpleweather.data.parseColorSafe -import com.wassupluke.simpleweather.data.resolveDynamicColor -import com.wassupluke.simpleweather.ui.MainActivity +import com.wassupluke.wasseswidgets.data.WeatherDataStore +import com.wassupluke.wasseswidgets.data.dataStore +import com.wassupluke.wasseswidgets.data.parseColorSafe +import com.wassupluke.wasseswidgets.data.resolveDynamicColor +import com.wassupluke.wasseswidgets.ui.MainActivity import kotlin.math.roundToInt class WeatherWidget : GlanceAppWidget() { diff --git a/app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidgetReceiver.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidgetReceiver.kt similarity index 84% rename from app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidgetReceiver.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidgetReceiver.kt index 3017bc7..493f2f0 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/widget/WeatherWidgetReceiver.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidgetReceiver.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.widget +package com.wassupluke.wasseswidgets.widget import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidgetReceiver diff --git a/app/src/main/java/com/wassupluke/simpleweather/worker/WeatherFetchWorker.kt b/app/src/main/java/com/wassupluke/wasseswidgets/worker/WeatherFetchWorker.kt similarity index 76% rename from app/src/main/java/com/wassupluke/simpleweather/worker/WeatherFetchWorker.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/worker/WeatherFetchWorker.kt index f174fd5..c630fb4 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/worker/WeatherFetchWorker.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/worker/WeatherFetchWorker.kt @@ -1,13 +1,13 @@ -package com.wassupluke.simpleweather.worker +package com.wassupluke.wasseswidgets.worker import android.content.Context import androidx.glance.appwidget.updateAll import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import com.wassupluke.simpleweather.data.WeatherDataStore -import com.wassupluke.simpleweather.data.WeatherRepository -import com.wassupluke.simpleweather.data.dataStore -import com.wassupluke.simpleweather.widget.WeatherWidget +import com.wassupluke.wasseswidgets.data.WeatherDataStore +import com.wassupluke.wasseswidgets.data.WeatherRepository +import com.wassupluke.wasseswidgets.data.dataStore +import com.wassupluke.wasseswidgets.widget.WeatherWidget import kotlinx.coroutines.flow.first class WeatherFetchWorker( diff --git a/app/src/main/java/com/wassupluke/simpleweather/worker/WorkScheduler.kt b/app/src/main/java/com/wassupluke/wasseswidgets/worker/WorkScheduler.kt similarity index 95% rename from app/src/main/java/com/wassupluke/simpleweather/worker/WorkScheduler.kt rename to app/src/main/java/com/wassupluke/wasseswidgets/worker/WorkScheduler.kt index f13af77..a9d2b2d 100644 --- a/app/src/main/java/com/wassupluke/simpleweather/worker/WorkScheduler.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/worker/WorkScheduler.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.worker +package com.wassupluke.wasseswidgets.worker import android.content.Context import androidx.work.* diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7ddf37a..01886ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - Simple Weather + Wasses Widgets Displays current temperature Location @@ -25,11 +25,10 @@ Set Widget font size - Alarm widget - Alarm widget tap action - Simple Alarm Widget + Alarm widget tap action + Wasses Alarm Widget - Widget tap action - None (opens Simple Weather) + Weather widget tap action + None (opens Wasses Widgets) Selected app not found diff --git a/app/src/test/java/com/wassupluke/simpleweather/data/WeatherDataStoreTest.kt b/app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherDataStoreTest.kt similarity index 97% rename from app/src/test/java/com/wassupluke/simpleweather/data/WeatherDataStoreTest.kt rename to app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherDataStoreTest.kt index ce18898..1f1d486 100644 --- a/app/src/test/java/com/wassupluke/simpleweather/data/WeatherDataStoreTest.kt +++ b/app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherDataStoreTest.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.data +package com.wassupluke.wasseswidgets.data import android.content.Context import androidx.datastore.preferences.core.edit diff --git a/app/src/test/java/com/wassupluke/simpleweather/data/WeatherRepositoryTest.kt b/app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherRepositoryTest.kt similarity index 97% rename from app/src/test/java/com/wassupluke/simpleweather/data/WeatherRepositoryTest.kt rename to app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherRepositoryTest.kt index b9aa41b..9ec2ba0 100644 --- a/app/src/test/java/com/wassupluke/simpleweather/data/WeatherRepositoryTest.kt +++ b/app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherRepositoryTest.kt @@ -1,9 +1,9 @@ -package com.wassupluke.simpleweather.data +package com.wassupluke.wasseswidgets.data import android.content.Context import androidx.datastore.preferences.core.edit import androidx.test.core.app.ApplicationProvider -import com.wassupluke.simpleweather.data.api.* +import com.wassupluke.wasseswidgets.data.api.* import io.mockk.coEvery import io.mockk.mockk import kotlinx.coroutines.flow.first diff --git a/app/src/test/java/com/wassupluke/simpleweather/data/api/OpenMeteoServiceTest.kt b/app/src/test/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoServiceTest.kt similarity index 97% rename from app/src/test/java/com/wassupluke/simpleweather/data/api/OpenMeteoServiceTest.kt rename to app/src/test/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoServiceTest.kt index 293db5f..4746e8a 100644 --- a/app/src/test/java/com/wassupluke/simpleweather/data/api/OpenMeteoServiceTest.kt +++ b/app/src/test/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoServiceTest.kt @@ -1,4 +1,4 @@ -package com.wassupluke.simpleweather.data.api +package com.wassupluke.wasseswidgets.data.api import kotlinx.serialization.json.Json import org.junit.Assert.assertEquals diff --git a/app/src/test/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModelTest.kt b/app/src/test/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModelTest.kt similarity index 96% rename from app/src/test/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModelTest.kt rename to app/src/test/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModelTest.kt index 70b93ea..8d8f232 100644 --- a/app/src/test/java/com/wassupluke/simpleweather/ui/settings/SettingsViewModelTest.kt +++ b/app/src/test/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModelTest.kt @@ -1,12 +1,12 @@ -package com.wassupluke.simpleweather.ui.settings +package com.wassupluke.wasseswidgets.ui.settings import android.app.Application import android.os.Build import androidx.datastore.preferences.core.edit import androidx.test.core.app.ApplicationProvider -import com.wassupluke.simpleweather.data.WeatherDataStore -import com.wassupluke.simpleweather.data.WeatherRepository -import com.wassupluke.simpleweather.data.dataStore +import com.wassupluke.wasseswidgets.data.WeatherDataStore +import com.wassupluke.wasseswidgets.data.WeatherRepository +import com.wassupluke.wasseswidgets.data.dataStore import io.mockk.mockk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi From 06e6bfeb5e20ce98b5acdae0d1cdb0bdcbfd1bf5 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Tue, 31 Mar 2026 21:42:29 -0500 Subject: [PATCH 13/26] feat: rename tap-action labels, make Fahrenheit default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DEFAULT_TEMP_UNIT: C → F - "Widget tap action" → "Weather widget tap action" - "Alarm widget" section header → "Alarm widget tap action" - Remove unused title_alarm_widget_tap_action string - Update alarm_widget_description and label_widget_tap_none for new app name --- .../java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt b/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt index f52bf2b..98878e3 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt @@ -38,7 +38,7 @@ fun parseColorSafe(colorString: String): Int? { val Context.dataStore: DataStore by preferencesDataStore(name = "weather_settings") object WeatherDataStore { - const val DEFAULT_TEMP_UNIT = "C" + const val DEFAULT_TEMP_UNIT = "F" const val DEFAULT_INTERVAL_MINUTES = 60 val USE_DEVICE_LOCATION = booleanPreferencesKey("use_device_location") From d3680de29fdaceaf6557f062cf6b561454a87c2f Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Tue, 31 Mar 2026 21:42:46 -0500 Subject: [PATCH 14/26] fix: read current alarm on widget placement (APPWIDGET_UPDATE) AlarmWidgetReceiver now calls updateAlarmText on APPWIDGET_UPDATE so the widget shows the correct next alarm immediately when first placed, rather than waiting for the next alarm-change broadcast. --- .../wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt index 00de669..f3a5997 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt @@ -1,6 +1,7 @@ package com.wassupluke.wasseswidgets.widget import android.app.AlarmManager +import android.appwidget.AppWidgetManager import android.content.Context import android.content.Intent import androidx.datastore.preferences.core.edit @@ -24,7 +25,8 @@ class AlarmWidgetReceiver : GlanceAppWidgetReceiver() { AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED, Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_TIME_CHANGED, - Intent.ACTION_TIMEZONE_CHANGED -> updateAlarmText(context) + Intent.ACTION_TIMEZONE_CHANGED, + AppWidgetManager.ACTION_APPWIDGET_UPDATE -> updateAlarmText(context) } } From b9deae00b9acf8d6801bc5b3d2cc5de98eabc938 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Tue, 31 Mar 2026 22:30:09 -0500 Subject: [PATCH 15/26] refactor: rename string keys to _ convention; add widget picker labels - All strings.xml keys follow settings_*, widget_temperature_*, widget_alarm_* prefixes - Add widget_temperature_label / widget_alarm_label shown in widget picker - Add android:label to both receivers so picker shows "Temperature" / "Next Alarm" - Update widget_info XMLs and all R.string references in SettingsScreen.kt - Add docs/adding-a-widget.md with conventions for future widgets --- app/src/main/AndroidManifest.xml | 6 +- .../ui/settings/SettingsScreen.kt | 52 ++++---- app/src/main/res/values/strings.xml | 68 ++++++---- app/src/main/res/xml/alarm_widget_info.xml | 2 +- app/src/main/res/xml/weather_widget_info.xml | 2 +- docs/adding-a-widget.md | 123 ++++++++++++++++++ 6 files changed, 196 insertions(+), 57 deletions(-) create mode 100644 docs/adding-a-widget.md diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a51e4df..d3def14 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,7 +31,8 @@ + android:exported="true" + android:label="@string/widget_temperature_label"> @@ -42,7 +43,8 @@ + android:exported="true" + android:label="@string/widget_alarm_label"> diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsScreen.kt b/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsScreen.kt index 0b15d2c..4b5fb5a 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsScreen.kt @@ -120,7 +120,7 @@ internal fun SettingsScreenContent( ) { Spacer(Modifier.height(8.dp)) - Text(stringResource(R.string.title_location), style = MaterialTheme.typography.titleSmall) + Text(stringResource(R.string.settings_location_title), style = MaterialTheme.typography.titleSmall) Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier @@ -130,7 +130,7 @@ internal fun SettingsScreenContent( else onDisableDeviceLocation() } ) { - Text(stringResource(R.string.label_use_device_location), modifier = Modifier.weight(1f)) + Text(stringResource(R.string.settings_location_device_label), modifier = Modifier.weight(1f)) Switch( checked = uiState.useDeviceLocation, onCheckedChange = { use -> @@ -143,7 +143,7 @@ internal fun SettingsScreenContent( OutlinedTextField( value = locationInput, onValueChange = { locationInput = it }, - label = { Text(stringResource(R.string.hint_location_input)) }, + label = { Text(stringResource(R.string.settings_location_hint)) }, modifier = Modifier.fillMaxWidth(), singleLine = true, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), @@ -167,7 +167,7 @@ internal fun SettingsScreenContent( HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - Text(stringResource(R.string.title_temperature_unit), style = MaterialTheme.typography.titleSmall) + Text(stringResource(R.string.settings_temperature_unit_title), style = MaterialTheme.typography.titleSmall) SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) { listOf("C", "F").forEachIndexed { index, unit -> SegmentedButton( @@ -181,17 +181,17 @@ internal fun SettingsScreenContent( HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - Text(stringResource(R.string.title_update_interval), style = MaterialTheme.typography.titleSmall) + Text(stringResource(R.string.settings_update_interval_title), style = MaterialTheme.typography.titleSmall) val intervalOptions = listOf( - 15 to stringResource(R.string.interval_15min), - 30 to stringResource(R.string.interval_30min), - 60 to stringResource(R.string.interval_1hr), - 180 to stringResource(R.string.interval_3hr), - 360 to stringResource(R.string.interval_6hr) + 15 to stringResource(R.string.settings_interval_15min), + 30 to stringResource(R.string.settings_interval_30min), + 60 to stringResource(R.string.settings_interval_1hr), + 180 to stringResource(R.string.settings_interval_3hr), + 360 to stringResource(R.string.settings_interval_6hr) ) var intervalExpanded by remember { mutableStateOf(false) } - val selectedLabel = intervalOptions.firstOrNull { it.first == uiState.updateIntervalMinutes }?.second ?: stringResource(R.string.interval_1hr) + val selectedLabel = intervalOptions.firstOrNull { it.first == uiState.updateIntervalMinutes }?.second ?: stringResource(R.string.settings_interval_1hr) ExposedDropdownMenuBox( expanded = intervalExpanded, @@ -201,7 +201,7 @@ internal fun SettingsScreenContent( value = selectedLabel, onValueChange = {}, readOnly = true, - label = { Text(stringResource(R.string.label_interval)) }, + label = { Text(stringResource(R.string.settings_interval_label)) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = intervalExpanded) }, modifier = Modifier .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable) @@ -232,7 +232,7 @@ internal fun SettingsScreenContent( .fillMaxWidth() .clickable { onSetWidgetDynamicColor(!uiState.widgetDynamicColor) } ) { - Text(stringResource(R.string.label_dynamic_color), modifier = Modifier.weight(1f)) + Text(stringResource(R.string.settings_dynamic_color_label), modifier = Modifier.weight(1f)) Switch( checked = uiState.widgetDynamicColor, onCheckedChange = { onSetWidgetDynamicColor(it) } @@ -241,7 +241,7 @@ internal fun SettingsScreenContent( } if (!uiState.widgetDynamicColor) { - Text(stringResource(R.string.title_widget_text_color), style = MaterialTheme.typography.titleSmall) + Text(stringResource(R.string.settings_text_color_title), style = MaterialTheme.typography.titleSmall) var colorInput by remember { mutableStateOf(uiState.widgetTextColor) } LaunchedEffect(uiState.widgetTextColor) { colorInput = uiState.widgetTextColor } @@ -260,8 +260,8 @@ internal fun SettingsScreenContent( OutlinedTextField( value = colorInput, onValueChange = { colorInput = it }, - label = { Text(stringResource(R.string.label_text_color)) }, - placeholder = { Text(stringResource(R.string.hint_color_input)) }, + label = { Text(stringResource(R.string.settings_text_color_label)) }, + placeholder = { Text(stringResource(R.string.settings_color_hint)) }, modifier = Modifier.fillMaxWidth(), singleLine = true, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), @@ -285,7 +285,7 @@ internal fun SettingsScreenContent( if (previewColor == null && uiState.widgetTextColor.isNotEmpty()) { Text( - text = stringResource(R.string.error_invalid_color), + text = stringResource(R.string.settings_color_error), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.error ) @@ -294,7 +294,7 @@ internal fun SettingsScreenContent( HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - Text(stringResource(R.string.title_font_size), style = MaterialTheme.typography.titleSmall) + Text(stringResource(R.string.settings_font_size_title), style = MaterialTheme.typography.titleSmall) Slider( value = uiState.fontSize.toFloat(), onValueChange = { onSetFontSize(it.toInt()) }, @@ -311,7 +311,7 @@ internal fun SettingsScreenContent( HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) Text( - stringResource(R.string.title_widget_tap_action), + stringResource(R.string.settings_weather_tap_title), style = MaterialTheme.typography.titleSmall ) @@ -356,14 +356,14 @@ internal fun SettingsScreenContent( } else if (uiState.widgetTapPackage.isNotEmpty()) { Spacer(Modifier.size(40.dp).padding(end = 8.dp)) Text( - text = stringResource(R.string.label_selected_app_not_found), + text = stringResource(R.string.settings_tap_app_missing_label), modifier = Modifier.weight(1f), color = MaterialTheme.colorScheme.error ) } else { Spacer(Modifier.size(40.dp).padding(end = 8.dp)) Text( - text = stringResource(R.string.label_widget_tap_none), + text = stringResource(R.string.settings_tap_none_label), modifier = Modifier.weight(1f), color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -391,7 +391,7 @@ internal fun SettingsScreenContent( ) { Spacer(Modifier.size(40.dp).padding(end = 12.dp)) Text( - text = stringResource(R.string.label_widget_tap_none), + text = stringResource(R.string.settings_tap_none_label), modifier = Modifier.weight(1f) ) if (uiState.widgetTapPackage.isEmpty()) { @@ -439,7 +439,7 @@ internal fun SettingsScreenContent( HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - Text(stringResource(R.string.title_alarm_widget), style = MaterialTheme.typography.titleSmall) + Text(stringResource(R.string.settings_alarm_tap_title), style = MaterialTheme.typography.titleSmall) val selectedAlarmAppInfo = remember(uiState.alarmWidgetTapPackage) { if (uiState.alarmWidgetTapPackage.isEmpty()) null @@ -482,14 +482,14 @@ internal fun SettingsScreenContent( } else if (uiState.alarmWidgetTapPackage.isNotEmpty()) { Spacer(Modifier.size(40.dp).padding(end = 8.dp)) Text( - text = stringResource(R.string.label_selected_app_not_found), + text = stringResource(R.string.settings_tap_app_missing_label), modifier = Modifier.weight(1f), color = MaterialTheme.colorScheme.error ) } else { Spacer(Modifier.size(40.dp).padding(end = 8.dp)) Text( - text = stringResource(R.string.label_widget_tap_none), + text = stringResource(R.string.settings_tap_none_label), modifier = Modifier.weight(1f), color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -517,7 +517,7 @@ internal fun SettingsScreenContent( ) { Spacer(Modifier.size(40.dp).padding(end = 12.dp)) Text( - text = stringResource(R.string.label_widget_tap_none), + text = stringResource(R.string.settings_tap_none_label), modifier = Modifier.weight(1f) ) if (uiState.alarmWidgetTapPackage.isEmpty()) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 01886ba..a328f04 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,34 +1,48 @@ Wasses Widgets - Displays current temperature - - Location - Use device location - City, zip code, or lat,lon - - Temperature Unit - Update Interval - Widget Text Color - Dynamic color - Text color - #RRGGBB, #AARRGGBB, or name like red - Invalid color — enter a hex value or name like \"red\" - - Interval - 15 min - 30 min - 1 hr - 3 hr - 6 hr - Set + + Temperature + Shows the current temperature on your home screen + Next Alarm + Shows your next scheduled alarm on your home screen + + + Location + Use device location + City, zip code, or lat,lon + + + Temperature Unit + + + Update Interval + Interval + 15 min + 30 min + 1 hr + 3 hr + 6 hr - Widget font size - Alarm widget tap action - Wasses Alarm Widget + + Dynamic color + Widget Text Color + Text color + #RRGGBB, #AARRGGBB, or name like red + Invalid color — enter a hex value or name like \"red\" + Widget font size - Weather widget tap action - None (opens Wasses Widgets) - Selected app not found + + Weather widget tap action + + + Alarm widget tap action + + + None (opens Wasses Widgets) + Selected app not found + + + Set diff --git a/app/src/main/res/xml/alarm_widget_info.xml b/app/src/main/res/xml/alarm_widget_info.xml index e445b6f..ea916cc 100644 --- a/app/src/main/res/xml/alarm_widget_info.xml +++ b/app/src/main/res/xml/alarm_widget_info.xml @@ -6,5 +6,5 @@ android:targetCellWidth="2" android:targetCellHeight="1" android:updatePeriodMillis="0" - android:description="@string/alarm_widget_description" + android:description="@string/widget_alarm_description" android:resizeMode="horizontal|vertical" /> diff --git a/app/src/main/res/xml/weather_widget_info.xml b/app/src/main/res/xml/weather_widget_info.xml index 0425344..418c54d 100644 --- a/app/src/main/res/xml/weather_widget_info.xml +++ b/app/src/main/res/xml/weather_widget_info.xml @@ -6,5 +6,5 @@ android:targetCellWidth="2" android:targetCellHeight="1" android:updatePeriodMillis="0" - android:description="@string/widget_description" + android:description="@string/widget_temperature_description" android:resizeMode="horizontal|vertical" /> diff --git a/docs/adding-a-widget.md b/docs/adding-a-widget.md new file mode 100644 index 0000000..f5f832a --- /dev/null +++ b/docs/adding-a-widget.md @@ -0,0 +1,123 @@ +# Adding a New Widget + +## String Resources (`res/values/strings.xml`) + +Follow the `_` convention. Each widget gets a label (shown in the picker) and a description (shown beneath it). + +```xml + +Human Name +Shows X on your home screen + + + widget tap action +``` + +Shared strings (`settings_tap_none_label`, `settings_tap_app_missing_label`, appearance keys) are reused — don't duplicate them. + +## Files to Create + +| File | Purpose | +|-|-| +| `widget/Widget.kt` | Glance widget UI | +| `widget/WidgetReceiver.kt` | `GlanceAppWidgetReceiver` + update trigger | +| `res/xml/_widget_info.xml` | Widget provider metadata | + +### `_widget_info.xml` + +```xml + + +``` + +### `Widget.kt` + +Mirror `WeatherWidget.kt` or `AlarmWidget.kt`. All widgets share these DataStore keys: +- `WIDGET_TEXT_COLOR` + `WIDGET_DYNAMIC_COLOR` + `resolveDynamicColor()` — color +- `FONT_SIZE` / `DEFAULT_FONT_SIZE` — font size +- Widget-specific tap key: `_WIDGET_TAP_PACKAGE` + +### `WidgetReceiver.kt` + +Extend `GlanceAppWidgetReceiver`. Override `onReceive` and call your update function for both `AppWidgetManager.ACTION_APPWIDGET_UPDATE` (first placement) and any domain-specific system broadcasts. + +Use `goAsync()` for any async work: + +```kotlin +private fun updateContent(context: Context) { + val pendingResult = goAsync() + CoroutineScope(Dispatchers.IO).launch { + try { + // read data, write to DataStore, call Widget().updateAll(context) + } finally { + pendingResult.finish() + } + } +} +``` + +## Files to Modify + +### `AndroidManifest.xml` + +```xml + + + + + + + +``` + +Add any required permissions (e.g. `RECEIVE_BOOT_COMPLETED`) if new broadcasts are needed. + +### `WeatherDataStore.kt` + +Add a tap-package key and any widget-specific cache keys: + +```kotlin +val _WIDGET_TAP_PACKAGE = stringPreferencesKey("_widget_tap_package") +val _DATA = stringPreferencesKey("_data") // if caching display text +``` + +### `SettingsViewModel.kt` + +Add to `SettingsUiState`: +```kotlin +val WidgetTapPackage: String = "" +``` + +Add to `prefsToUiState`: +```kotlin +WidgetTapPackage = prefs[WeatherDataStore._WIDGET_TAP_PACKAGE] ?: "" +``` + +Add method: +```kotlin +fun setWidgetTapPackage(pkg: String) { + viewModelScope.launch(dispatcher) { + context.dataStore.edit { it[WeatherDataStore._WIDGET_TAP_PACKAGE] = pkg } + Widget().updateAll(context) + } +} +``` + +Also add `Widget().updateAll(context)` to `setFontSize`, `setWidgetTextColor`, and `setWidgetDynamicColor` so shared appearance settings propagate to the new widget. + +### `SettingsScreen.kt` + +Add `onSetWidgetTapPackage: (String) -> Unit` parameter to `SettingsScreenContent`. Add a new section (after the existing alarm widget section) using `settings__tap_title` and reusing the existing app-picker pattern. From b8fecb9a88bdd41dd2679d0e47f5dfe23c952196 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 22:13:26 -0500 Subject: [PATCH 16/26] fix: avoid double goAsync() crash on APPWIDGET_UPDATE GlanceAppWidgetReceiver.super.onReceive() already calls goAsync() for APPWIDGET_UPDATE, leaving mPendingResult null. A second goAsync() call returns null and crashes in the finally block. Skip goAsync() for APPWIDGET_UPDATE; use it only for standalone alarm broadcasts. --- .../wasseswidgets/widget/AlarmWidgetReceiver.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt index f3a5997..c27933f 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt @@ -26,12 +26,14 @@ class AlarmWidgetReceiver : GlanceAppWidgetReceiver() { Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_TIME_CHANGED, Intent.ACTION_TIMEZONE_CHANGED, - AppWidgetManager.ACTION_APPWIDGET_UPDATE -> updateAlarmText(context) + AppWidgetManager.ACTION_APPWIDGET_UPDATE -> updateAlarmText(context, intent.action ?: "") } } - private fun updateAlarmText(context: Context) { - val pendingResult = goAsync() + private fun updateAlarmText(context: Context, action: String) { + // APPWIDGET_UPDATE: super already called goAsync() internally — calling it again returns null. + // For all other broadcasts, goAsync() is available and needed to keep the process alive. + val pendingResult = if (action != AppWidgetManager.ACTION_APPWIDGET_UPDATE) goAsync() else null CoroutineScope(Dispatchers.IO).launch { try { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager @@ -44,7 +46,7 @@ class AlarmWidgetReceiver : GlanceAppWidgetReceiver() { context.dataStore.edit { it[WeatherDataStore.ALARM_TEXT] = alarmText } AlarmWidget().updateAll(context) } finally { - pendingResult.finish() + pendingResult?.finish() } } } From daa14a30e76304074b55f1f17c59a406f1bf8ffe Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 23:02:14 -0500 Subject: [PATCH 17/26] fix upcoming alarm not showing when widget is first placed, add alarm icon --- .../wasseswidgets/widget/AlarmWidget.kt | 25 +++++++++++++------ .../wasseswidgets/widget/WeatherWidget.kt | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt index bc536ec..14abddb 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.sp import androidx.datastore.preferences.core.emptyPreferences +import androidx.compose.ui.unit.dp import androidx.glance.* import androidx.glance.action.Action import androidx.glance.action.actionStartActivity @@ -18,6 +19,7 @@ import androidx.glance.layout.* import androidx.glance.GlanceTheme import androidx.glance.text.* import androidx.glance.unit.ColorProvider +import com.wassupluke.wasseswidgets.R import com.wassupluke.wasseswidgets.data.WeatherDataStore import com.wassupluke.wasseswidgets.data.dataStore import com.wassupluke.wasseswidgets.data.parseColorSafe @@ -83,13 +85,22 @@ private fun AlarmWidgetContent( .clickable(tapAction), contentAlignment = Alignment.Center ) { - Text( - text = alarmText, - style = TextStyle( - fontSize = fontSize.sp, - fontWeight = FontWeight.Normal, - color = textColorProvider + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + provider = ImageProvider(R.drawable.ic_alarm), + contentDescription = null, + modifier = GlanceModifier.size(fontSize.dp), + colorFilter = ColorFilter.tint(textColorProvider) ) - ) + Spacer(GlanceModifier.width(4.dp)) + Text( + text = alarmText, + style = TextStyle( + fontSize = fontSize.sp, + fontWeight = FontWeight.Normal, + color = textColorProvider + ) + ) + } } } diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt index d4107f3..d0f9ab1 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt @@ -32,7 +32,7 @@ class WeatherWidget : GlanceAppWidget() { GlanceTheme { val prefs by context.dataStore.data.collectAsState(initial = emptyPreferences()) val tempCelsius = prefs[WeatherDataStore.LAST_TEMP_CELSIUS] - val unit = prefs[WeatherDataStore.TEMP_UNIT] ?: "C" + val unit = prefs[WeatherDataStore.TEMP_UNIT] ?: WeatherDataStore.DEFAULT_TEMP_UNIT val colorString = prefs[WeatherDataStore.WIDGET_TEXT_COLOR] ?: "white" val dynamicColor = prefs.resolveDynamicColor() From ee0aaef1de71e6f92adcfac74e6c7966a5048972 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 23:11:43 -0500 Subject: [PATCH 18/26] shrink widget touch target to the text area, abstract widget basics to WidgetRoot.kt --- .../wasseswidgets/widget/WidgetRoot.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 app/src/main/java/com/wassupluke/wasseswidgets/widget/WidgetRoot.kt diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WidgetRoot.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WidgetRoot.kt new file mode 100644 index 0000000..bc08708 --- /dev/null +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WidgetRoot.kt @@ -0,0 +1,25 @@ +package com.wassupluke.wasseswidgets.widget + +import androidx.compose.runtime.Composable +import androidx.glance.GlanceModifier +import androidx.glance.action.Action +import androidx.glance.action.clickable +import androidx.glance.layout.Alignment +import androidx.glance.layout.Box +import androidx.glance.layout.fillMaxSize + +/** + * Standard widget scaffold: centers content and bounds the tap target to the content area, + * so empty widget surface space does not intercept touches. + */ +@Composable +fun WidgetRoot(tapAction: Action, content: @Composable () -> Unit) { + Box( + modifier = GlanceModifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Box(modifier = GlanceModifier.clickable(tapAction)) { + content() + } + } +} From fe0c35cb6e1c9401a1f73ae163fd674ff584ba9e Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 23:11:53 -0500 Subject: [PATCH 19/26] shrink widget touch target to the text area, abstract widget basics to WidgetRoot.kt --- .../wassupluke/wasseswidgets/widget/AlarmWidget.kt | 14 ++++++-------- .../wasseswidgets/widget/WeatherWidget.kt | 9 ++------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt index 14abddb..f89dde8 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt @@ -12,10 +12,13 @@ import androidx.compose.ui.unit.dp import androidx.glance.* import androidx.glance.action.Action import androidx.glance.action.actionStartActivity -import androidx.glance.action.clickable import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.provideContent -import androidx.glance.layout.* +import androidx.glance.layout.Alignment +import androidx.glance.layout.Row +import androidx.glance.layout.Spacer +import androidx.glance.layout.size +import androidx.glance.layout.width import androidx.glance.GlanceTheme import androidx.glance.text.* import androidx.glance.unit.ColorProvider @@ -79,12 +82,7 @@ private fun AlarmWidgetContent( tapAction: Action, fontSize: Int ) { - Box( - modifier = GlanceModifier - .fillMaxSize() - .clickable(tapAction), - contentAlignment = Alignment.Center - ) { + WidgetRoot(tapAction = tapAction) { Row(verticalAlignment = Alignment.CenterVertically) { Image( provider = ImageProvider(R.drawable.ic_alarm), diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt index d0f9ab1..dd27266 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt @@ -14,7 +14,7 @@ import androidx.glance.action.actionStartActivity import androidx.glance.action.clickable import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.provideContent -import androidx.glance.layout.* +import androidx.glance.layout.Alignment import androidx.glance.GlanceTheme import androidx.glance.text.* import androidx.glance.unit.ColorProvider @@ -90,12 +90,7 @@ private fun WeatherWidgetContent( tapAction: Action, fontSize: Int ) { - Box( - modifier = GlanceModifier - .fillMaxSize() - .clickable(tapAction), - contentAlignment = Alignment.Center - ) { + WidgetRoot(tapAction = tapAction) { Text( text = displayTemp, style = TextStyle( From 0018f55c76713f541c82acd96de1e82eb4e8efc1 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 23:12:08 -0500 Subject: [PATCH 20/26] initial commit --- app/src/main/res/drawable/ic_alarm.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 app/src/main/res/drawable/ic_alarm.xml diff --git a/app/src/main/res/drawable/ic_alarm.xml b/app/src/main/res/drawable/ic_alarm.xml new file mode 100644 index 0000000..3335520 --- /dev/null +++ b/app/src/main/res/drawable/ic_alarm.xml @@ -0,0 +1,10 @@ + + + + From 0cdb99e1bfc315b1628322d38929cb107784bdc6 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 23:15:44 -0500 Subject: [PATCH 21/26] remove unused imports --- .../java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt index dd27266..168606e 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt @@ -11,10 +11,8 @@ import androidx.datastore.preferences.core.emptyPreferences import androidx.glance.* import androidx.glance.action.Action import androidx.glance.action.actionStartActivity -import androidx.glance.action.clickable import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.provideContent -import androidx.glance.layout.Alignment import androidx.glance.GlanceTheme import androidx.glance.text.* import androidx.glance.unit.ColorProvider From 5e19c25678b4639c32f9b8c076c8e35abe72a1ce Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 23:19:40 -0500 Subject: [PATCH 22/26] fix: use ColorProvider(Int) to avoid RestrictTo library group violation ColorProvider(Color) is restricted to the Glance library group. Pass the @ColorInt from parseColorSafe() directly to ColorProvider(Int) instead of converting to Compose Color first. --- .../wassupluke/wasseswidgets/widget/AlarmWidget.kt | 12 ++---------- .../wassupluke/wasseswidgets/widget/WeatherWidget.kt | 12 ++---------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt index f89dde8..afa5bf9 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt @@ -5,7 +5,6 @@ import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.sp import androidx.datastore.preferences.core.emptyPreferences import androidx.compose.ui.unit.dp @@ -43,15 +42,8 @@ class AlarmWidget : GlanceAppWidget() { val textColorProvider: ColorProvider = if (dynamicColor) { GlanceTheme.colors.primary } else { - val resolved = parseColorSafe(colorString)?.let { argb -> - Color( - red = android.graphics.Color.red(argb) / 255f, - green = android.graphics.Color.green(argb) / 255f, - blue = android.graphics.Color.blue(argb) / 255f, - alpha = android.graphics.Color.alpha(argb) / 255f - ) - } ?: Color.White - ColorProvider(resolved) + val argb = parseColorSafe(colorString) ?: android.graphics.Color.WHITE + ColorProvider(argb) } val tapPackage = prefs[WeatherDataStore.ALARM_WIDGET_TAP_PACKAGE] diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt index 168606e..df3319a 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt @@ -5,7 +5,6 @@ import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.sp import androidx.datastore.preferences.core.emptyPreferences import androidx.glance.* @@ -44,15 +43,8 @@ class WeatherWidget : GlanceAppWidget() { val textColorProvider: ColorProvider = if (dynamicColor) { GlanceTheme.colors.primary } else { - val resolved = parseColorSafe(colorString)?.let { argb -> - Color( - red = android.graphics.Color.red(argb) / 255f, - green = android.graphics.Color.green(argb) / 255f, - blue = android.graphics.Color.blue(argb) / 255f, - alpha = android.graphics.Color.alpha(argb) / 255f - ) - } ?: Color.White - ColorProvider(resolved) + val argb = parseColorSafe(colorString) ?: android.graphics.Color.WHITE + ColorProvider(argb) } val tapPackage = prefs[WeatherDataStore.WIDGET_TAP_PACKAGE] From 4442207540fc405b5a3cbe4deb209734b2e18d19 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 23:27:10 -0500 Subject: [PATCH 23/26] fix: suppress RestrictedApi for ColorProvider(Color) in both widgets ColorProvider(Color) is restricted to the Glance library group with no public equivalent. Annotate both widget classes with @SuppressLint and use Color(argb) to correctly convert the @ColorInt before passing it in. The previous attempt used ColorProvider(Int) which takes a @ColorRes, not a @ColorInt, and would have produced wrong colors at runtime. --- .../java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt | 4 +++- .../java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt index afa5bf9..6b93687 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt @@ -5,6 +5,7 @@ import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.sp import androidx.datastore.preferences.core.emptyPreferences import androidx.compose.ui.unit.dp @@ -28,6 +29,7 @@ import com.wassupluke.wasseswidgets.data.parseColorSafe import com.wassupluke.wasseswidgets.data.resolveDynamicColor import com.wassupluke.wasseswidgets.ui.MainActivity +@SuppressLint("RestrictedApi") class AlarmWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { @@ -43,7 +45,7 @@ class AlarmWidget : GlanceAppWidget() { GlanceTheme.colors.primary } else { val argb = parseColorSafe(colorString) ?: android.graphics.Color.WHITE - ColorProvider(argb) + ColorProvider(Color(argb)) } val tapPackage = prefs[WeatherDataStore.ALARM_WIDGET_TAP_PACKAGE] diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt index df3319a..8b5f7ca 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt @@ -5,6 +5,7 @@ import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.sp import androidx.datastore.preferences.core.emptyPreferences import androidx.glance.* @@ -22,6 +23,7 @@ import com.wassupluke.wasseswidgets.data.resolveDynamicColor import com.wassupluke.wasseswidgets.ui.MainActivity import kotlin.math.roundToInt +@SuppressLint("RestrictedApi") class WeatherWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { @@ -44,7 +46,7 @@ class WeatherWidget : GlanceAppWidget() { GlanceTheme.colors.primary } else { val argb = parseColorSafe(colorString) ?: android.graphics.Color.WHITE - ColorProvider(argb) + ColorProvider(Color(argb)) } val tapPackage = prefs[WeatherDataStore.WIDGET_TAP_PACKAGE] From 95e67b0db99a3d6c1fa5ae34534c36341677c7f5 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 23:31:14 -0500 Subject: [PATCH 24/26] fix: use string resource for alarm none text; clamp icon size - Replace hardcoded "No alarm" in AlarmWidget and AlarmWidgetReceiver with R.string.widget_alarm_none for localization consistency - Clamp alarm icon size to 32dp max so large font values don't blow up the icon disproportionately --- .../java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt | 4 ++-- .../wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt | 3 ++- app/src/main/res/values/strings.xml | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt index 6b93687..937dea9 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt @@ -36,7 +36,7 @@ class AlarmWidget : GlanceAppWidget() { provideContent { GlanceTheme { val prefs by context.dataStore.data.collectAsState(initial = emptyPreferences()) - val alarmText = prefs[WeatherDataStore.ALARM_TEXT] ?: "No alarm" + val alarmText = prefs[WeatherDataStore.ALARM_TEXT] ?: context.getString(R.string.widget_alarm_none) val colorString = prefs[WeatherDataStore.WIDGET_TEXT_COLOR] ?: "white" val dynamicColor = prefs.resolveDynamicColor() val fontSize = prefs[WeatherDataStore.FONT_SIZE] ?: WeatherDataStore.DEFAULT_FONT_SIZE @@ -81,7 +81,7 @@ private fun AlarmWidgetContent( Image( provider = ImageProvider(R.drawable.ic_alarm), contentDescription = null, - modifier = GlanceModifier.size(fontSize.dp), + modifier = GlanceModifier.size(fontSize.coerceAtMost(32).dp), colorFilter = ColorFilter.tint(textColorProvider) ) Spacer(GlanceModifier.width(4.dp)) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt index c27933f..f0ce5d3 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt @@ -8,6 +8,7 @@ import androidx.datastore.preferences.core.edit import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidgetReceiver import androidx.glance.appwidget.updateAll +import com.wassupluke.wasseswidgets.R import com.wassupluke.wasseswidgets.data.WeatherDataStore import com.wassupluke.wasseswidgets.data.dataStore import kotlinx.coroutines.CoroutineScope @@ -39,7 +40,7 @@ class AlarmWidgetReceiver : GlanceAppWidgetReceiver() { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val nextAlarm = alarmManager.nextAlarmClock val alarmText = if (nextAlarm == null) { - "No alarm" + context.getString(R.string.widget_alarm_none) } else { DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(nextAlarm.triggerTime)) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a328f04..d5de352 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,6 +7,7 @@ Shows the current temperature on your home screen Next Alarm Shows your next scheduled alarm on your home screen + No alarm Location From bdcbaf0d6811b9cb80db588e9e786fb89fb2aea1 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 23:33:23 -0500 Subject: [PATCH 25/26] revert: restore icon size to fontSize.dp (no clamping) --- .../java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt index 937dea9..8c7b32a 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt +++ b/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt @@ -81,7 +81,7 @@ private fun AlarmWidgetContent( Image( provider = ImageProvider(R.drawable.ic_alarm), contentDescription = null, - modifier = GlanceModifier.size(fontSize.coerceAtMost(32).dp), + modifier = GlanceModifier.size(fontSize.dp), colorFilter = ColorFilter.tint(textColorProvider) ) Spacer(GlanceModifier.width(4.dp)) From 64926678e933204be1961341958f081a74d24854 Mon Sep 17 00:00:00 2001 From: Luke Wass Date: Wed, 1 Apr 2026 23:40:07 -0500 Subject: [PATCH 26/26] refactor: rename package from wasseswidgets to widgets Update applicationId, namespace, all package declarations, imports, directory structure, and display name from "Wasses Widgets" to "Widgets". --- app/build.gradle.kts | 4 ++-- .../data/WeatherDataStore.kt | 2 +- .../data/WeatherRepository.kt | 6 +++--- .../data/api/NetworkModule.kt | 2 +- .../data/api/OpenMeteoService.kt | 2 +- .../data/api/WeatherApiModels.kt | 2 +- .../ui/MainActivity.kt | 16 ++++++++-------- .../ui/settings/SettingsScreen.kt | 8 ++++---- .../ui/settings/SettingsViewModel.kt | 16 ++++++++-------- .../ui/theme/SimpleWeatherTheme.kt | 2 +- .../widget/AlarmWidget.kt | 14 +++++++------- .../widget/AlarmWidgetReceiver.kt | 8 ++++---- .../widget/WeatherWidget.kt | 12 ++++++------ .../widget/WeatherWidgetReceiver.kt | 2 +- .../widget/WidgetRoot.kt | 2 +- .../worker/WeatherFetchWorker.kt | 10 +++++----- .../worker/WorkScheduler.kt | 2 +- app/src/main/res/values/strings.xml | 4 ++-- .../data/WeatherDataStoreTest.kt | 2 +- .../data/WeatherRepositoryTest.kt | 4 ++-- .../data/api/OpenMeteoServiceTest.kt | 2 +- .../ui/settings/SettingsViewModelTest.kt | 8 ++++---- 22 files changed, 65 insertions(+), 65 deletions(-) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/data/WeatherDataStore.kt (98%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/data/WeatherRepository.kt (93%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/data/api/NetworkModule.kt (95%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/data/api/OpenMeteoService.kt (93%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/data/api/WeatherApiModels.kt (92%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/ui/MainActivity.kt (85%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/ui/settings/SettingsScreen.kt (99%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/ui/settings/SettingsViewModel.kt (93%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/ui/theme/SimpleWeatherTheme.kt (96%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/widget/AlarmWidget.kt (90%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/widget/AlarmWidgetReceiver.kt (91%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/widget/WeatherWidget.kt (91%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/widget/WeatherWidgetReceiver.kt (84%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/widget/WidgetRoot.kt (94%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/worker/WeatherFetchWorker.kt (76%) rename app/src/main/java/com/wassupluke/{wasseswidgets => widgets}/worker/WorkScheduler.kt (95%) rename app/src/test/java/com/wassupluke/{wasseswidgets => widgets}/data/WeatherDataStoreTest.kt (97%) rename app/src/test/java/com/wassupluke/{wasseswidgets => widgets}/data/WeatherRepositoryTest.kt (97%) rename app/src/test/java/com/wassupluke/{wasseswidgets => widgets}/data/api/OpenMeteoServiceTest.kt (97%) rename app/src/test/java/com/wassupluke/{wasseswidgets => widgets}/ui/settings/SettingsViewModelTest.kt (96%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9b0d705..567f0ec 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,11 +13,11 @@ val gitVersionName = providers.exec { }.standardOutput.asText.map { it.trim().removePrefix("v").ifEmpty { "0.0.0" } } android { - namespace = "com.wassupluke.wasseswidgets" + namespace = "com.wassupluke.widgets" compileSdk = 36 defaultConfig { - applicationId = "com.wassupluke.wasseswidgets" + applicationId = "com.wassupluke.widgets" minSdk = 26 targetSdk = 36 versionCode = gitVersionCode.get() diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt b/app/src/main/java/com/wassupluke/widgets/data/WeatherDataStore.kt similarity index 98% rename from app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt rename to app/src/main/java/com/wassupluke/widgets/data/WeatherDataStore.kt index 98878e3..a33ba4d 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherDataStore.kt +++ b/app/src/main/java/com/wassupluke/widgets/data/WeatherDataStore.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.data +package com.wassupluke.widgets.data import android.content.Context import android.graphics.Color as AndroidColor diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherRepository.kt b/app/src/main/java/com/wassupluke/widgets/data/WeatherRepository.kt similarity index 93% rename from app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherRepository.kt rename to app/src/main/java/com/wassupluke/widgets/data/WeatherRepository.kt index b5570e9..0725ad6 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/data/WeatherRepository.kt +++ b/app/src/main/java/com/wassupluke/widgets/data/WeatherRepository.kt @@ -1,10 +1,10 @@ -package com.wassupluke.wasseswidgets.data +package com.wassupluke.widgets.data import android.content.Context import android.location.Geocoder import androidx.datastore.preferences.core.edit -import com.wassupluke.wasseswidgets.data.api.NetworkModule -import com.wassupluke.wasseswidgets.data.api.OpenMeteoService +import com.wassupluke.widgets.data.api.NetworkModule +import com.wassupluke.widgets.data.api.OpenMeteoService import java.util.Locale class WeatherRepository( diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/data/api/NetworkModule.kt b/app/src/main/java/com/wassupluke/widgets/data/api/NetworkModule.kt similarity index 95% rename from app/src/main/java/com/wassupluke/wasseswidgets/data/api/NetworkModule.kt rename to app/src/main/java/com/wassupluke/widgets/data/api/NetworkModule.kt index 3b7e6ff..25f48f6 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/data/api/NetworkModule.kt +++ b/app/src/main/java/com/wassupluke/widgets/data/api/NetworkModule.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.data.api +package com.wassupluke.widgets.data.api import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import kotlinx.serialization.json.Json diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoService.kt b/app/src/main/java/com/wassupluke/widgets/data/api/OpenMeteoService.kt similarity index 93% rename from app/src/main/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoService.kt rename to app/src/main/java/com/wassupluke/widgets/data/api/OpenMeteoService.kt index 8b0d181..333ea25 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoService.kt +++ b/app/src/main/java/com/wassupluke/widgets/data/api/OpenMeteoService.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.data.api +package com.wassupluke.widgets.data.api import retrofit2.http.GET import retrofit2.http.Query diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/data/api/WeatherApiModels.kt b/app/src/main/java/com/wassupluke/widgets/data/api/WeatherApiModels.kt similarity index 92% rename from app/src/main/java/com/wassupluke/wasseswidgets/data/api/WeatherApiModels.kt rename to app/src/main/java/com/wassupluke/widgets/data/api/WeatherApiModels.kt index 738bb02..da45c64 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/data/api/WeatherApiModels.kt +++ b/app/src/main/java/com/wassupluke/widgets/data/api/WeatherApiModels.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.data.api +package com.wassupluke.widgets.data.api import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/ui/MainActivity.kt b/app/src/main/java/com/wassupluke/widgets/ui/MainActivity.kt similarity index 85% rename from app/src/main/java/com/wassupluke/wasseswidgets/ui/MainActivity.kt rename to app/src/main/java/com/wassupluke/widgets/ui/MainActivity.kt index 1d7d416..e25a34f 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/ui/MainActivity.kt +++ b/app/src/main/java/com/wassupluke/widgets/ui/MainActivity.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.ui +package com.wassupluke.widgets.ui import android.Manifest import android.annotation.SuppressLint @@ -13,13 +13,13 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.google.android.gms.location.LocationServices -import com.wassupluke.wasseswidgets.data.WeatherDataStore -import com.wassupluke.wasseswidgets.data.WeatherRepository -import com.wassupluke.wasseswidgets.data.dataStore -import com.wassupluke.wasseswidgets.ui.settings.SettingsScreen -import com.wassupluke.wasseswidgets.ui.settings.SettingsViewModel -import com.wassupluke.wasseswidgets.ui.theme.SimpleWeatherTheme -import com.wassupluke.wasseswidgets.worker.WorkScheduler +import com.wassupluke.widgets.data.WeatherDataStore +import com.wassupluke.widgets.data.WeatherRepository +import com.wassupluke.widgets.data.dataStore +import com.wassupluke.widgets.ui.settings.SettingsScreen +import com.wassupluke.widgets.ui.settings.SettingsViewModel +import com.wassupluke.widgets.ui.theme.SimpleWeatherTheme +import com.wassupluke.widgets.worker.WorkScheduler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsScreen.kt b/app/src/main/java/com/wassupluke/widgets/ui/settings/SettingsScreen.kt similarity index 99% rename from app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsScreen.kt rename to app/src/main/java/com/wassupluke/widgets/ui/settings/SettingsScreen.kt index 4b5fb5a..ecd6ed7 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/wassupluke/widgets/ui/settings/SettingsScreen.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.ui.settings +package com.wassupluke.widgets.ui.settings import android.Manifest import android.content.Intent @@ -35,9 +35,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.wassupluke.wasseswidgets.R -import com.wassupluke.wasseswidgets.data.parseColorSafe -import com.wassupluke.wasseswidgets.ui.theme.SimpleWeatherTheme +import com.wassupluke.widgets.R +import com.wassupluke.widgets.data.parseColorSafe +import com.wassupluke.widgets.ui.theme.SimpleWeatherTheme import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/wassupluke/widgets/ui/settings/SettingsViewModel.kt similarity index 93% rename from app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModel.kt rename to app/src/main/java/com/wassupluke/widgets/ui/settings/SettingsViewModel.kt index a0c4aea..2b90793 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/wassupluke/widgets/ui/settings/SettingsViewModel.kt @@ -1,17 +1,17 @@ -package com.wassupluke.wasseswidgets.ui.settings +package com.wassupluke.widgets.ui.settings import android.app.Application import androidx.datastore.preferences.core.edit import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import com.wassupluke.wasseswidgets.data.WeatherDataStore -import com.wassupluke.wasseswidgets.data.WeatherRepository -import com.wassupluke.wasseswidgets.data.dataStore -import com.wassupluke.wasseswidgets.data.resolveDynamicColor +import com.wassupluke.widgets.data.WeatherDataStore +import com.wassupluke.widgets.data.WeatherRepository +import com.wassupluke.widgets.data.dataStore +import com.wassupluke.widgets.data.resolveDynamicColor import androidx.glance.appwidget.updateAll -import com.wassupluke.wasseswidgets.widget.AlarmWidget -import com.wassupluke.wasseswidgets.widget.WeatherWidget -import com.wassupluke.wasseswidgets.worker.WorkScheduler +import com.wassupluke.widgets.widget.AlarmWidget +import com.wassupluke.widgets.widget.WeatherWidget +import com.wassupluke.widgets.worker.WorkScheduler import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/ui/theme/SimpleWeatherTheme.kt b/app/src/main/java/com/wassupluke/widgets/ui/theme/SimpleWeatherTheme.kt similarity index 96% rename from app/src/main/java/com/wassupluke/wasseswidgets/ui/theme/SimpleWeatherTheme.kt rename to app/src/main/java/com/wassupluke/widgets/ui/theme/SimpleWeatherTheme.kt index 38abdaf..9569f95 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/ui/theme/SimpleWeatherTheme.kt +++ b/app/src/main/java/com/wassupluke/widgets/ui/theme/SimpleWeatherTheme.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.ui.theme +package com.wassupluke.widgets.ui.theme import android.app.Activity import android.os.Build diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt b/app/src/main/java/com/wassupluke/widgets/widget/AlarmWidget.kt similarity index 90% rename from app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt rename to app/src/main/java/com/wassupluke/widgets/widget/AlarmWidget.kt index 8c7b32a..7e6a3ac 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidget.kt +++ b/app/src/main/java/com/wassupluke/widgets/widget/AlarmWidget.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.widget +package com.wassupluke.widgets.widget import android.annotation.SuppressLint import android.content.Context @@ -22,12 +22,12 @@ import androidx.glance.layout.width import androidx.glance.GlanceTheme import androidx.glance.text.* import androidx.glance.unit.ColorProvider -import com.wassupluke.wasseswidgets.R -import com.wassupluke.wasseswidgets.data.WeatherDataStore -import com.wassupluke.wasseswidgets.data.dataStore -import com.wassupluke.wasseswidgets.data.parseColorSafe -import com.wassupluke.wasseswidgets.data.resolveDynamicColor -import com.wassupluke.wasseswidgets.ui.MainActivity +import com.wassupluke.widgets.R +import com.wassupluke.widgets.data.WeatherDataStore +import com.wassupluke.widgets.data.dataStore +import com.wassupluke.widgets.data.parseColorSafe +import com.wassupluke.widgets.data.resolveDynamicColor +import com.wassupluke.widgets.ui.MainActivity @SuppressLint("RestrictedApi") class AlarmWidget : GlanceAppWidget() { diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt b/app/src/main/java/com/wassupluke/widgets/widget/AlarmWidgetReceiver.kt similarity index 91% rename from app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt rename to app/src/main/java/com/wassupluke/widgets/widget/AlarmWidgetReceiver.kt index f0ce5d3..ed7f302 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/AlarmWidgetReceiver.kt +++ b/app/src/main/java/com/wassupluke/widgets/widget/AlarmWidgetReceiver.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.widget +package com.wassupluke.widgets.widget import android.app.AlarmManager import android.appwidget.AppWidgetManager @@ -8,9 +8,9 @@ import androidx.datastore.preferences.core.edit import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidgetReceiver import androidx.glance.appwidget.updateAll -import com.wassupluke.wasseswidgets.R -import com.wassupluke.wasseswidgets.data.WeatherDataStore -import com.wassupluke.wasseswidgets.data.dataStore +import com.wassupluke.widgets.R +import com.wassupluke.widgets.data.WeatherDataStore +import com.wassupluke.widgets.data.dataStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt b/app/src/main/java/com/wassupluke/widgets/widget/WeatherWidget.kt similarity index 91% rename from app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt rename to app/src/main/java/com/wassupluke/widgets/widget/WeatherWidget.kt index 8b5f7ca..595515d 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidget.kt +++ b/app/src/main/java/com/wassupluke/widgets/widget/WeatherWidget.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.widget +package com.wassupluke.widgets.widget import android.annotation.SuppressLint import android.content.Context @@ -16,11 +16,11 @@ import androidx.glance.appwidget.provideContent import androidx.glance.GlanceTheme import androidx.glance.text.* import androidx.glance.unit.ColorProvider -import com.wassupluke.wasseswidgets.data.WeatherDataStore -import com.wassupluke.wasseswidgets.data.dataStore -import com.wassupluke.wasseswidgets.data.parseColorSafe -import com.wassupluke.wasseswidgets.data.resolveDynamicColor -import com.wassupluke.wasseswidgets.ui.MainActivity +import com.wassupluke.widgets.data.WeatherDataStore +import com.wassupluke.widgets.data.dataStore +import com.wassupluke.widgets.data.parseColorSafe +import com.wassupluke.widgets.data.resolveDynamicColor +import com.wassupluke.widgets.ui.MainActivity import kotlin.math.roundToInt @SuppressLint("RestrictedApi") diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidgetReceiver.kt b/app/src/main/java/com/wassupluke/widgets/widget/WeatherWidgetReceiver.kt similarity index 84% rename from app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidgetReceiver.kt rename to app/src/main/java/com/wassupluke/widgets/widget/WeatherWidgetReceiver.kt index 493f2f0..48c0d6d 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WeatherWidgetReceiver.kt +++ b/app/src/main/java/com/wassupluke/widgets/widget/WeatherWidgetReceiver.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.widget +package com.wassupluke.widgets.widget import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidgetReceiver diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WidgetRoot.kt b/app/src/main/java/com/wassupluke/widgets/widget/WidgetRoot.kt similarity index 94% rename from app/src/main/java/com/wassupluke/wasseswidgets/widget/WidgetRoot.kt rename to app/src/main/java/com/wassupluke/widgets/widget/WidgetRoot.kt index bc08708..e3825e5 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/widget/WidgetRoot.kt +++ b/app/src/main/java/com/wassupluke/widgets/widget/WidgetRoot.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.widget +package com.wassupluke.widgets.widget import androidx.compose.runtime.Composable import androidx.glance.GlanceModifier diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/worker/WeatherFetchWorker.kt b/app/src/main/java/com/wassupluke/widgets/worker/WeatherFetchWorker.kt similarity index 76% rename from app/src/main/java/com/wassupluke/wasseswidgets/worker/WeatherFetchWorker.kt rename to app/src/main/java/com/wassupluke/widgets/worker/WeatherFetchWorker.kt index c630fb4..9fb6195 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/worker/WeatherFetchWorker.kt +++ b/app/src/main/java/com/wassupluke/widgets/worker/WeatherFetchWorker.kt @@ -1,13 +1,13 @@ -package com.wassupluke.wasseswidgets.worker +package com.wassupluke.widgets.worker import android.content.Context import androidx.glance.appwidget.updateAll import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import com.wassupluke.wasseswidgets.data.WeatherDataStore -import com.wassupluke.wasseswidgets.data.WeatherRepository -import com.wassupluke.wasseswidgets.data.dataStore -import com.wassupluke.wasseswidgets.widget.WeatherWidget +import com.wassupluke.widgets.data.WeatherDataStore +import com.wassupluke.widgets.data.WeatherRepository +import com.wassupluke.widgets.data.dataStore +import com.wassupluke.widgets.widget.WeatherWidget import kotlinx.coroutines.flow.first class WeatherFetchWorker( diff --git a/app/src/main/java/com/wassupluke/wasseswidgets/worker/WorkScheduler.kt b/app/src/main/java/com/wassupluke/widgets/worker/WorkScheduler.kt similarity index 95% rename from app/src/main/java/com/wassupluke/wasseswidgets/worker/WorkScheduler.kt rename to app/src/main/java/com/wassupluke/widgets/worker/WorkScheduler.kt index a9d2b2d..f0a22b2 100644 --- a/app/src/main/java/com/wassupluke/wasseswidgets/worker/WorkScheduler.kt +++ b/app/src/main/java/com/wassupluke/widgets/worker/WorkScheduler.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.worker +package com.wassupluke.widgets.worker import android.content.Context import androidx.work.* diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d5de352..7cd93fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - Wasses Widgets + Widgets Temperature @@ -41,7 +41,7 @@ Alarm widget tap action - None (opens Wasses Widgets) + None (opens Widgets) Selected app not found diff --git a/app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherDataStoreTest.kt b/app/src/test/java/com/wassupluke/widgets/data/WeatherDataStoreTest.kt similarity index 97% rename from app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherDataStoreTest.kt rename to app/src/test/java/com/wassupluke/widgets/data/WeatherDataStoreTest.kt index 1f1d486..88939a3 100644 --- a/app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherDataStoreTest.kt +++ b/app/src/test/java/com/wassupluke/widgets/data/WeatherDataStoreTest.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.data +package com.wassupluke.widgets.data import android.content.Context import androidx.datastore.preferences.core.edit diff --git a/app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherRepositoryTest.kt b/app/src/test/java/com/wassupluke/widgets/data/WeatherRepositoryTest.kt similarity index 97% rename from app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherRepositoryTest.kt rename to app/src/test/java/com/wassupluke/widgets/data/WeatherRepositoryTest.kt index 9ec2ba0..8fbd929 100644 --- a/app/src/test/java/com/wassupluke/wasseswidgets/data/WeatherRepositoryTest.kt +++ b/app/src/test/java/com/wassupluke/widgets/data/WeatherRepositoryTest.kt @@ -1,9 +1,9 @@ -package com.wassupluke.wasseswidgets.data +package com.wassupluke.widgets.data import android.content.Context import androidx.datastore.preferences.core.edit import androidx.test.core.app.ApplicationProvider -import com.wassupluke.wasseswidgets.data.api.* +import com.wassupluke.widgets.data.api.* import io.mockk.coEvery import io.mockk.mockk import kotlinx.coroutines.flow.first diff --git a/app/src/test/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoServiceTest.kt b/app/src/test/java/com/wassupluke/widgets/data/api/OpenMeteoServiceTest.kt similarity index 97% rename from app/src/test/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoServiceTest.kt rename to app/src/test/java/com/wassupluke/widgets/data/api/OpenMeteoServiceTest.kt index 4746e8a..31c6123 100644 --- a/app/src/test/java/com/wassupluke/wasseswidgets/data/api/OpenMeteoServiceTest.kt +++ b/app/src/test/java/com/wassupluke/widgets/data/api/OpenMeteoServiceTest.kt @@ -1,4 +1,4 @@ -package com.wassupluke.wasseswidgets.data.api +package com.wassupluke.widgets.data.api import kotlinx.serialization.json.Json import org.junit.Assert.assertEquals diff --git a/app/src/test/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModelTest.kt b/app/src/test/java/com/wassupluke/widgets/ui/settings/SettingsViewModelTest.kt similarity index 96% rename from app/src/test/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModelTest.kt rename to app/src/test/java/com/wassupluke/widgets/ui/settings/SettingsViewModelTest.kt index 8d8f232..1669a84 100644 --- a/app/src/test/java/com/wassupluke/wasseswidgets/ui/settings/SettingsViewModelTest.kt +++ b/app/src/test/java/com/wassupluke/widgets/ui/settings/SettingsViewModelTest.kt @@ -1,12 +1,12 @@ -package com.wassupluke.wasseswidgets.ui.settings +package com.wassupluke.widgets.ui.settings import android.app.Application import android.os.Build import androidx.datastore.preferences.core.edit import androidx.test.core.app.ApplicationProvider -import com.wassupluke.wasseswidgets.data.WeatherDataStore -import com.wassupluke.wasseswidgets.data.WeatherRepository -import com.wassupluke.wasseswidgets.data.dataStore +import com.wassupluke.widgets.data.WeatherDataStore +import com.wassupluke.widgets.data.WeatherRepository +import com.wassupluke.widgets.data.dataStore import io.mockk.mockk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi