Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ reviews:
between native and Dart (HomeWidgetPreferences) must stay in sync.
- path: "**/*_test.dart"
instructions: >-
Ensure tests assert real behavior; home_widget and SharedPreferences
Ensure tests assert real behavior; WidgetStore and SharedPreferences
mocks should follow the existing patterns in test/.

chat:
Expand Down
5 changes: 0 additions & 5 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ updates:
commit-message:
prefix: "chore"
include: "scope"
# Ignore home_widget updates (intentionally pinned)
ignore:
- dependency-name: "home_widget"
# Ignore all versions - we're pinned to 0.8.0 for functional reasons
# See docs/HOME_WIDGET_VERSION_ISSUE.md
# Group all minor/patch updates into a single PR
groups:
dev-dependencies:
Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This file provides context for AI assistants working on this project.
| Aspect | Value |
|--------|-------|
| Framework | Flutter 3.44.0 (pinned — see "Before Coding") |
| Android SDK | minSdk 30 (Android 11), target 36 — do NOT lower minSdk, see Gotcha #9 |
| Weather API | Open-Meteo (free, no key) |
| Charting | Native SVG (SvgChartGenerator.kt + AndroidSVG) |
| Widget package | Native AppWidgetProvider + method-channel KV store (`widget_store.dart`) |
Expand Down Expand Up @@ -47,7 +48,7 @@ lib/
│ ├── app_*.arb # Other languages
│ └── app_localizations.dart # Generated
├── services/
│ ├── location_service.dart # Geolocator wrapper with fallback
│ ├── location_service.dart # Native location (LocationBridge) with fallback
│ ├── widget_service.dart # Triggers native widget refresh + resize flag
│ ├── widget_store.dart # Method-channel KV bridge to HomeWidgetPreferences (replaces home_widget)
│ └── native_svg_service.dart # Method channel to native (weather fetch, SVG gen, cache)
Expand Down Expand Up @@ -280,3 +281,4 @@ adb logcat | grep -i "Error inflating"
6. **Implicit broadcasts** - Android 8.0+ requires runtime receiver registration (not manifest)
7. **Event staleness** - Widget checks `last_weather_update` timestamp (15 min threshold)
8. **Edge-to-edge warning** - Play Console may warn about deprecated APIs (setStatusBarColor etc.) - this is Flutter engine code, not app code; tracked in flutter/flutter#160328
9. **minSdk is pinned to 30 (`app/build.gradle.kts`), NOT Flutter's default 24** - hard floor is 29: the widget's `WidgetTheme` parent `android:Theme.DeviceDefault.DayNight` requires API 29; on API 24-28 the launcher can't inflate the widget (blank/broken widget → Google Play "Broken Functionality" rejection, fixed 2026-06). 30 also gives `LocationListener` default callbacks (so `LocationProvider` needs no `onStatusChanged`/`onProviderEnabled`/`onProviderDisabled` stubs). Run `cd android && ./gradlew :app:lintDebug` and check for `NewApi` errors before shipping any resource/theme change. If you must support <29, give `WidgetTheme` an API-24-safe parent and add a `values-v29/styles.xml` DayNight override instead of lowering minSdk blindly; below API 30, restore the `LocationListener` stubs.
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ lib/
│ └── home_screen.dart # Main app screen (both chart panels)
├── services/
│ ├── location_service.dart # GPS + city search + reverse geocoding
│ ├── widget_service.dart # Home widget updates via home_widget
│ ├── widget_service.dart # Triggers native widget refresh (WidgetStore)
│ ├── native_svg_service.dart # Method channel to Kotlin (SVG / weather / cache)
│ ├── units_service.dart # Temperature unit and 12/24h logic
│ └── material_you_service.dart # Material You color pass-through
Expand Down Expand Up @@ -184,7 +184,7 @@ back in their preferred language.
### Android Widget

The home screen widgets use:
- `HomeWidgetProvider` from the home_widget package
- `AppWidgetProvider` (native; two providers — 48h and 7-day weekly)
- `RemoteViews` for native Android widget rendering
- SVG chart generated in Kotlin (`SvgChartGenerator.kt`) and rasterised via AndroidSVG
- AlarmManager (~15 min inexact), WorkManager (~30 min with network constraint),
Expand Down Expand Up @@ -248,15 +248,15 @@ Supported locales are auto-detected from ARB files.

| Package | Purpose |
|---------|---------|
| home_widget | Android/iOS widget support |
| geolocator | GPS location |
| geocoding | Reverse geocoding (city names) |
| http | API requests |
| path_provider | File storage |
| shared_preferences | Settings storage |
| http | API requests (weather, city search) |
| intl | Locale-aware formatting |
| flutter_localizations | i18n framework |

Material You theming uses native Android color extraction (`MaterialYouColorExtractor.kt`).
Location (GPS + reverse geocoding), the widget KV bridge, persistent storage, and
Material You theming are all **native** (over the `org.bortnik.meteogram/svg` method
channel) — no `home_widget`, `geolocator`, `geocoding`, `path_provider`, or
`shared_preferences` packages. This keeps the project free of any Kotlin-Gradle-Plugin
plugin, as required by AGP-9 built-in Kotlin.

## License

Expand All @@ -273,5 +273,4 @@ Contributions welcome! Please read the existing code style and test your changes
## Acknowledgments

- Weather data: [Open-Meteo](https://open-meteo.com/)
- Widget support: [home_widget](https://pub.dev/packages/home_widget)
- SVG rendering: [AndroidSVG](https://bigbadaboom.github.io/androidsvg/)
8 changes: 1 addition & 7 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ We aim to respond to security reports within **48 hours** and provide a fix with
### Dependency Management

- **Automated scanning**: Dependabot monitors dependencies weekly
- **Pinned versions**: Critical dependencies (like `home_widget`) are pinned for stability
- **Pinned toolchain**: The Flutter SDK and Android Gradle Plugin are pinned for build stability
- **Regular updates**: Dependencies are reviewed and updated monthly
- **Vulnerability tracking**: All dependencies checked against [GitHub Advisory Database](https://github.com/advisories)

Expand Down Expand Up @@ -58,12 +58,6 @@ This app uses these third-party services:

## Known Security Considerations

### home_widget Version Pin

This project intentionally uses `home_widget: 0.8.0` (not latest 0.9.0) due to functional issues with widget resizing. See [docs/HOME_WIDGET_VERSION_ISSUE.md](docs/HOME_WIDGET_VERSION_ISSUE.md) for details.

**Security impact**: `JobIntentService` (used in 0.8.0) is deprecated but still functional and secure. We monitor for security advisories and will migrate when 0.9.0+ fixes the resize issue.

### Permissions

The app requests these Android permissions:
Expand Down
7 changes: 6 additions & 1 deletion android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ android {

defaultConfig {
applicationId = "org.bortnik.meteogram"
minSdk = flutter.minSdkVersion
// minSdk 30 (Android 11). The hard floor is 29: the home-screen widget's theme
// parent android:Theme.DeviceDefault.DayNight requires API 29, so on API 24-28
// the launcher couldn't inflate the widget (broken widget). API 30 additionally
// provides LocationListener default callbacks (see LocationProvider). Overrides
// Flutter's default minSdk (24); do not lower below 29 without re-checking WidgetTheme.
minSdk = 30
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
Expand All @@ -32,13 +30,8 @@ object LocationProvider {

fun isLocationServiceEnabled(context: Context): Boolean {
val lm = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager ?: return false
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
lm.isLocationEnabled
} else {
@Suppress("DEPRECATION")
(lm.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
}
// isLocationEnabled is API 28+; minSdk is 29, so it's always available.
return lm.isLocationEnabled
}

/** "granted" if fine or coarse location is held, else "denied". */
Expand Down Expand Up @@ -76,7 +69,6 @@ object LocationProvider {
* once on the main thread with `[lat, lon]`, or null on timeout/failure. Call
* this on the main thread.
*/
@Suppress("DEPRECATION") // onStatusChanged override is needed for minSdk 24 runtime safety.
fun getCurrentPosition(context: Context, timeoutMs: Long, callback: (DoubleArray?) -> Unit) {
if (!hasPermission(context)) { callback(null); return }
val lm = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
Expand All @@ -98,13 +90,8 @@ object LocationProvider {
handler.removeCallbacksAndMessages(null)
callback(doubleArrayOf(location.latitude, location.longitude))
}

// onStatusChanged/onProviderEnabled/onProviderDisabled gained default
// implementations only in API 30; override them so the class is safe on
// API 24 (where the framework still invokes them on the interface).
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
// onStatusChanged/onProviderEnabled/onProviderDisabled use the
// LocationListener default impls (API 30+), so no stubs are needed.
}

try {
Expand Down
211 changes: 0 additions & 211 deletions docs/EXPEDITED_WORKMANAGER_PLAN.md

This file was deleted.

Loading
Loading