From 6f2c43c1f66289d0cf709bc8dc7851fc4d34063b Mon Sep 17 00:00:00 2001 From: xarleyn Date: Thu, 20 Aug 2026 23:48:20 +0300 Subject: [PATCH 1/4] Upgrade the Android toolchain to Flutter 3.47.1. CI was already on current stable and warning that Gradle 8.14, AGP 8.11.1, and Kotlin 2.2.20 would soon be unsupported. Pin Flutter 3.47.1 and match its Gradle 9.3.1 / AGP 9.1.0 / Kotlin 2.4.0 template so those checks pass. Co-authored-by: Cursor --- .github/workflows/ci.yml | 1 + .github/workflows/release.yml | 1 + CHANGELOG.md | 6 + README.md | 8 +- analysis_options.yaml | 6 + android/app/build.gradle.kts | 11 +- android/build.gradle.kts | 16 +++ android/gradle.properties | 4 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/jcenter-compat.gradle | 19 +++ android/settings.gradle.kts | 6 +- lib/l10n/generated/app_localizations_en.dart | 1 + lib/l10n/generated/app_localizations_ru.dart | 1 + lib/main.dart | 1 + lib/screens/achievements_screen.dart | 1 + lib/screens/analytics_screen.dart | 2 + lib/screens/debug_diagnostics_screen.dart | 13 +- lib/screens/debug_log_screen.dart | 7 +- lib/screens/device_comparison_screen.dart | 1 + lib/screens/ducting_forecast_screen.dart | 1 + lib/screens/map_screen.dart | 128 ++++++++---------- lib/screens/repeater_health_screen.dart | 2 + lib/screens/session_history_screen.dart | 1 + lib/screens/settings/settings_page.dart | 6 +- lib/screens/settings/settings_screen.dart | 17 +-- .../widgets/settings_section_header.dart | 6 +- lib/screens/signal_trend_screen.dart | 1 + lib/services/achievement_service.dart | 2 + lib/services/aggregation_service.dart | 1 + lib/services/carpeater_service.dart | 1 + lib/services/database_service.dart | 3 + lib/services/ducting_service.dart | 2 + lib/services/location_service.dart | 45 +++--- lib/services/lora_companion_service.dart | 12 +- lib/services/meshcore_protocol.dart | 3 +- lib/services/persistent_debug_logger.dart | 1 + lib/services/settings_service.dart | 1 + lib/services/sound_service.dart | 1 + lib/services/tile_download_service.dart | 1 + lib/services/upload_service.dart | 2 + lib/services/widget_service.dart | 1 + lib/utils/geohash_utils.dart | 1 + pubspec.lock | 30 ++-- pubspec.yaml | 2 +- test/session_map_view_test.dart | 5 +- 45 files changed, 226 insertions(+), 157 deletions(-) create mode 100644 android/jcenter-compat.gradle diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa1ae41..74d45fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable + flutter-version: "3.47.1" cache: true - name: Install dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9f0c97..3398602 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,6 +36,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable + flutter-version: "3.47.1" cache: true - name: Read version from pubspec diff --git a/CHANGELOG.md b/CHANGELOG.md index e78e3a1..3d18f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,6 +121,12 @@ service error. ### Changed +- Android builds now follow the Flutter 3.47 toolchain: Gradle 9.3.1, Android + Gradle Plugin 9.1.0, and Kotlin 2.4.0. CI and release workflows pin Flutter + 3.47.1 so the Gradle/AGP/Kotlin warnings no longer appear. Gradle 9 no longer + provides `jcenter()`, so the Android build maps leftover plugin calls to + Maven Central. Android library plugins are compiled against API 36 so they + satisfy AGP 9's compile SDK requirement. - Bluetooth device selection no longer waits for the scan timeout. Previously used and paired companion radios appear immediately, and newly found devices are added to the list while scanning continues. diff --git a/README.md b/README.md index 13f2e61..fadbdee 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,9 @@ companion radio over USB or Bluetooth, and displays coverage on a Flutter map. ### Prerequisites -- Flutter SDK (3.10.0 or higher) +- Flutter SDK 3.47.1 or compatible (Dart 3.13) - Android Studio or VS Code with Flutter extensions -- Android SDK with API level 21+ +- Android SDK with API level 24+ - A MeshCore companion radio device (for testing) ### Installation @@ -111,8 +111,8 @@ lib/ ## Requirements -- Flutter stable with a Dart SDK compatible with `pubspec.yaml`. -- Android SDK and an Android device or emulator. +- Flutter 3.47.1 (Dart 3.13) or a compatible stable SDK. +- Android SDK (compile/target API 36, min API 24) and an Android device or emulator. - A physical MeshCore companion radio for LoRa workflows. ## Development diff --git a/analysis_options.yaml b/analysis_options.yaml index 36f78c3..e0e6be7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -6,3 +6,9 @@ analyzer: - .toolchain/** - build/** - flutter/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 34afe52..5c2f88d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -3,7 +3,6 @@ import java.io.FileInputStream plugins { id("com.android.application") - id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } @@ -57,10 +56,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17.toString() - } - defaultConfig { applicationId = "mintylinux.meshcore.wardrive" minSdk = flutter.minSdkVersion @@ -91,6 +86,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + flutter { source = "../.." } diff --git a/android/build.gradle.kts b/android/build.gradle.kts index dbee657..0a720ec 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -2,6 +2,7 @@ allprojects { repositories { google() mavenCentral() + maven { url = uri("https://jitpack.io") } } } @@ -19,6 +20,21 @@ subprojects { project.evaluationDependsOn(":app") } +subprojects { + // AGP 9 requires each Android library to compile against at least the SDK + // of its dependencies. Several Flutter plugins still declare API 33/34. + fun bumpLibraryCompileSdk() { + extensions + .findByType() + ?.compileSdk = 36 + } + if (state.executed) { + bumpLibraryCompileSdk() + } else { + afterEvaluate { bumpLibraryCompileSdk() } + } +} + tasks.register("clean") { delete(rootProject.layout.buildDirectory) } diff --git a/android/gradle.properties b/android/gradle.properties index fbee1d8..e96108c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,2 +1,6 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index e4ef43f..a20f2c4 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip diff --git a/android/jcenter-compat.gradle b/android/jcenter-compat.gradle new file mode 100644 index 0000000..c357921 --- /dev/null +++ b/android/jcenter-compat.gradle @@ -0,0 +1,19 @@ +// Gradle 9 removed RepositoryHandler.jcenter(). Some Flutter plugins still call it +// during evaluation (notably usb_serial 0.5.2). Map those calls to mavenCentral(). +def aliasJcenter = { repos -> + repos.metaClass.jcenter = { Object[] args -> + repos.mavenCentral() + } +} + +gradle.beforeProject { project -> + aliasJcenter(project.buildscript.repositories) + aliasJcenter(project.repositories) +} + +gradle.projectsLoaded { + gradle.rootProject.allprojects { project -> + aliasJcenter(project.buildscript.repositories) + aliasJcenter(project.repositories) + } +} diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index ca7fe06..745c7bd 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,8 +19,10 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.11.1" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("com.android.application") version "9.1.0" apply false + id("org.jetbrains.kotlin.android") version "2.4.0" apply false } include(":app") + +apply(from = "jcenter-compat.gradle") diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index c93a535..ad4d492 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index 4409870..3609f1b 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -1,5 +1,6 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; + import 'app_localizations.dart'; // ignore_for_file: type=lint diff --git a/lib/main.dart b/lib/main.dart index 78abb7f..103e6c5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; + import 'l10n/app_locale.dart'; import 'l10n/generated/app_localizations.dart'; import 'screens/map_screen.dart'; diff --git a/lib/screens/achievements_screen.dart b/lib/screens/achievements_screen.dart index 75d323d..29dc0c1 100644 --- a/lib/screens/achievements_screen.dart +++ b/lib/screens/achievements_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; + import '../l10n/achievement_l10n.dart'; import '../l10n/generated/app_localizations.dart'; import '../services/achievement_service.dart'; diff --git a/lib/screens/analytics_screen.dart b/lib/screens/analytics_screen.dart index e00dc02..6cece03 100644 --- a/lib/screens/analytics_screen.dart +++ b/lib/screens/analytics_screen.dart @@ -1,10 +1,12 @@ import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; import 'package:geohash_plus/geohash_plus.dart' as geohash; import 'package:share_plus/share_plus.dart'; + import '../models/models.dart'; import '../services/aggregation_service.dart'; import '../services/database_service.dart'; diff --git a/lib/screens/debug_diagnostics_screen.dart b/lib/screens/debug_diagnostics_screen.dart index e10a894..b30c97b 100644 --- a/lib/screens/debug_diagnostics_screen.dart +++ b/lib/screens/debug_diagnostics_screen.dart @@ -1,9 +1,12 @@ import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:share_plus/share_plus.dart'; + import '../l10n/generated/app_localizations.dart'; import '../services/location_service.dart'; + import 'package:path_provider/path_provider.dart'; /// Debug diagnostics screen for troubleshooting Samsung device issues @@ -105,9 +108,8 @@ class _DebugDiagnosticsScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - AppLocalizations.of( - context, - ).debugDiagnosticsErrorDeleting('$e'), + AppLocalizations.of(context) + .debugDiagnosticsErrorDeleting('$e'), ), ), ); @@ -348,9 +350,8 @@ class _LogViewerScreen extends StatelessWidget { onPressed: () { Share.share( content, - subject: AppLocalizations.of( - context, - ).debugDiagnosticsShareSubjectWithFile(fileName), + subject: AppLocalizations.of(context) + .debugDiagnosticsShareSubjectWithFile(fileName), ); }, ), diff --git a/lib/screens/debug_log_screen.dart b/lib/screens/debug_log_screen.dart index 1bae0d2..214bcba 100644 --- a/lib/screens/debug_log_screen.dart +++ b/lib/screens/debug_log_screen.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:file_picker/file_picker.dart'; + import '../l10n/generated/app_localizations.dart'; import '../services/debug_log_service.dart'; @@ -74,9 +76,8 @@ class _DebugLogScreenState extends State { try { final logs = _logService.logs; if (logs.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(l10n.debugLogNoLogsToExport))); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(l10n.debugLogNoLogsToExport))); return; } diff --git a/lib/screens/device_comparison_screen.dart b/lib/screens/device_comparison_screen.dart index 06d6f68..78a77b4 100644 --- a/lib/screens/device_comparison_screen.dart +++ b/lib/screens/device_comparison_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; + import '../l10n/generated/app_localizations.dart'; import '../services/database_service.dart'; diff --git a/lib/screens/ducting_forecast_screen.dart b/lib/screens/ducting_forecast_screen.dart index d1774cd..22de54a 100644 --- a/lib/screens/ducting_forecast_screen.dart +++ b/lib/screens/ducting_forecast_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; + import '../l10n/generated/app_localizations.dart'; /// Tropospheric ducting forecast viewer using dxinfocentre.com forecast maps. diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 7a1bd4b..3ed65e9 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -2,12 +2,14 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math' as math; + import 'package:flutter/material.dart'; import 'package:flutter_compass/flutter_compass.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; + import '../models/models.dart'; import '../models/location_quality_settings.dart'; import '../models/impossible_zone.dart'; @@ -32,6 +34,7 @@ import '../widgets/compass_calibration.dart'; import '../widgets/tracking_play_button.dart'; import '../widgets/bluetooth_device_picker_dialog.dart'; import '../services/widget_service.dart'; + import 'package:geohash_plus/geohash_plus.dart' as geohash; import 'package:usb_serial/usb_serial.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; @@ -47,7 +50,9 @@ import 'package:dio_cache_interceptor_file_store/dio_cache_interceptor_file_stor import 'package:flutter_map_heatmap/flutter_map_heatmap.dart'; import 'package:geolocator/geolocator.dart'; import 'package:permission_handler/permission_handler.dart'; + import 'dart:typed_data'; + import 'debug_log_screen.dart'; import 'debug_diagnostics_screen.dart'; import 'session_history_screen.dart'; @@ -1309,9 +1314,8 @@ class _MapScreenState extends State { bytes: utf8.encode(content), ); _showSnackBar( - AppLocalizations.of( - context, - ).mapExportedSamples(samples.length, format.toUpperCase()), + AppLocalizations.of(context) + .mapExportedSamples(samples.length, format.toUpperCase()), ); } else if (choice == 'share') { final directory = await getExternalStorageDirectory(); @@ -1663,9 +1667,8 @@ $placemarks Text(AppLocalizations.of(ctx).mapLon(lon.toStringAsFixed(6))), Text( AppLocalizations.of(ctx).mapAddedOn( - DateFormat.yMMMd( - Localizations.localeOf(ctx).toString(), - ).format(createdAt), + DateFormat.yMMMd(Localizations.localeOf(ctx).toString()) + .format(createdAt), ), style: const TextStyle(fontSize: 12, color: Colors.grey), ), @@ -1857,9 +1860,9 @@ $placemarks : sample.pingSuccess == false ? 'fail' : 'gps', - DateFormat.MMMd( - Localizations.localeOf(ctx).toString(), - ).add_Hm().format(sample.timestamp), + DateFormat.MMMd(Localizations.localeOf(ctx).toString()) + .add_Hm() + .format(sample.timestamp), ), ), actions: [ @@ -1893,9 +1896,8 @@ $placemarks builder: (ctx) => AlertDialog( title: Text(AppLocalizations.of(ctx).mapDeleteCoverageCell), content: Text( - AppLocalizations.of( - ctx, - ).mapDeleteCoverageCellBody(total, coverage.id), + AppLocalizations.of(ctx) + .mapDeleteCoverageCellBody(total, coverage.id), ), actions: [ TextButton( @@ -2949,9 +2951,8 @@ $placemarks width: 28, height: 28, child: Semantics( - label: AppLocalizations.of( - context, - ).mapApproxRadioPositionUncertainty(uncertaintyText), + label: AppLocalizations.of(context) + .mapApproxRadioPositionUncertainty(uncertaintyText), button: true, child: GestureDetector( onTap: () => _showSnackBar( @@ -3360,9 +3361,8 @@ $placemarks vertical: 1, ), decoration: BoxDecoration( - color: _getDuctingColor( - _currentDuctingRisk, - ).withValues(alpha: 0.15), + color: _getDuctingColor(_currentDuctingRisk) + .withValues(alpha: 0.15), borderRadius: BorderRadius.circular(8), border: Border.all( color: _getDuctingColor(_currentDuctingRisk), @@ -3639,9 +3639,8 @@ $placemarks AppLocalizations.of(context).mapUsbDeviceFallback, ), subtitle: Text( - AppLocalizations.of( - context, - ).mapVidPid('${device.vid}', '${device.pid}'), + AppLocalizations.of(context) + .mapVidPid('${device.vid}', '${device.pid}'), ), onTap: () => Navigator.pop(context, device), ); @@ -3871,9 +3870,9 @@ $placemarks _applySessionMapView(SessionMapView.session(session)); _showSnackBar( AppLocalizations.of(context).mapShowingSessionFrom( - DateFormat.MMMd( - Localizations.localeOf(context).toString(), - ).add_Hm().format(session.startTime), + DateFormat.MMMd(Localizations.localeOf(context).toString()) + .add_Hm() + .format(session.startTime), ), ); }, @@ -4214,9 +4213,8 @@ $placemarks vertical: 2, ), decoration: BoxDecoration( - color: _getDuctingColor( - sample.ductingRisk!, - ).withValues(alpha: 0.15), + color: _getDuctingColor(sample.ductingRisk!) + .withValues(alpha: 0.15), borderRadius: BorderRadius.circular(4), ), child: Text( @@ -4303,14 +4301,12 @@ $placemarks ), const SizedBox(height: 8), Text( - AppLocalizations.of( - context, - ).mapLat(repeater.position.latitude.toStringAsFixed(6)), + AppLocalizations.of(context) + .mapLat(repeater.position.latitude.toStringAsFixed(6)), ), Text( - AppLocalizations.of( - context, - ).mapLon(repeater.position.longitude.toStringAsFixed(6)), + AppLocalizations.of(context) + .mapLon(repeater.position.longitude.toStringAsFixed(6)), ), if (repeater.rssi != null) const SizedBox(height: 8), if (repeater.rssi != null) @@ -4497,9 +4493,8 @@ $placemarks leading: const Icon(Icons.cell_tower, color: Colors.purple), title: Text( repeater.name ?? - AppLocalizations.of( - context, - ).mapRepeaterFallback(repeater.id), + AppLocalizations.of(context) + .mapRepeaterFallback(repeater.id), ), subtitle: Text( '${repeater.position.latitude.toStringAsFixed(4)}, ' @@ -4571,9 +4566,8 @@ $placemarks ), if (totalBatches > 1) Text( - AppLocalizations.of( - context, - ).mapUploadBatch(currentBatch, totalBatches), + AppLocalizations.of(context) + .mapUploadBatch(currentBatch, totalBatches), style: const TextStyle(fontSize: 12), ), ], @@ -4653,9 +4647,8 @@ $placemarks children: [ if (results.length > 1) Text( - AppLocalizations.of( - context, - ).mapUploadedToSites(successCount, results.length), + AppLocalizations.of(context) + .mapUploadedToSites(successCount, results.length), ), const SizedBox(height: 8), ...results.entries.map((entry) { @@ -4834,18 +4827,16 @@ $placemarks AppLocalizations.of(ctx).mapDeleteSite, ), content: Text( - AppLocalizations.of( - ctx, - ).mapDeleteSiteConfirm(endpoint.name), + AppLocalizations.of(ctx) + .mapDeleteSiteConfirm(endpoint.name), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: Text( - AppLocalizations.of( - ctx, - ).settingsCancel, + AppLocalizations.of(ctx) + .settingsCancel, ), ), TextButton( @@ -4995,9 +4986,8 @@ $placemarks ), const SizedBox(height: 8), Text( - AppLocalizations.of( - context, - ).mapTilesEstimate(tileCount, estimatedMB), + AppLocalizations.of(context) + .mapTilesEstimate(tileCount, estimatedMB), style: const TextStyle(fontWeight: FontWeight.bold), ), if (tileCount > 5000) @@ -5075,9 +5065,8 @@ $placemarks if (context.mounted) Navigator.pop(context); if (!downloadCancelled) { _showSnackBar( - AppLocalizations.of( - context, - ).mapDownloadedTiles(succeeded, totalTiles), + AppLocalizations.of(context) + .mapDownloadedTiles(succeeded, totalTiles), ); } }); @@ -5093,9 +5082,8 @@ $placemarks LinearProgressIndicator(value: progress), const SizedBox(height: 12), Text( - AppLocalizations.of( - context, - ).mapTilesProgress(completed, totalTiles), + AppLocalizations.of(context) + .mapTilesProgress(completed, totalTiles), ), ], ), @@ -5106,9 +5094,8 @@ $placemarks downloader.cancel(); Navigator.pop(context); _showSnackBar( - AppLocalizations.of( - context, - ).mapDownloadCancelled(completed), + AppLocalizations.of(context) + .mapDownloadCancelled(completed), ); }, child: Text(AppLocalizations.of(context).settingsCancel), @@ -5233,9 +5220,8 @@ $placemarks ), title: Text( name ?? - AppLocalizations.of( - context, - ).mapRepeaterFallback(displayId), + AppLocalizations.of(context) + .mapRepeaterFallback(displayId), ), subtitle: Text( displayId, @@ -5252,9 +5238,8 @@ $placemarks await _settingsService.setIncludeOnlyRepeaters(id); _loadSamples(); _showSnackBar( - AppLocalizations.of( - context, - ).mapShowingCoverageFrom(displayId), + AppLocalizations.of(context) + .mapShowingCoverageFrom(displayId), ); }, ); @@ -5412,9 +5397,8 @@ $placemarks _showCommunityCoverage = true; }); _showSnackBar( - AppLocalizations.of( - context, - ).mapDownloadedCoverageCells(coverage.length), + AppLocalizations.of(context) + .mapDownloadedCoverageCells(coverage.length), ); } else { // Try loading from cache @@ -5524,9 +5508,9 @@ $placemarks final parsedLastUpdate = DateTime.tryParse(lastUpdate); final lastUpdateDisplay = parsedLastUpdate == null ? lastUpdate - : DateFormat.yMMMd( - Localizations.localeOf(context).toString(), - ).add_Hm().format(parsedLastUpdate.toLocal()); + : DateFormat.yMMMd(Localizations.localeOf(context).toString()) + .add_Hm() + .format(parsedLastUpdate.toLocal()); showDialog( context: context, diff --git a/lib/screens/repeater_health_screen.dart b/lib/screens/repeater_health_screen.dart index 41b84c2..b5ae4ad 100644 --- a/lib/screens/repeater_health_screen.dart +++ b/lib/screens/repeater_health_screen.dart @@ -1,7 +1,9 @@ import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:intl/intl.dart'; + import '../l10n/generated/app_localizations.dart'; import '../models/models.dart'; diff --git a/lib/screens/session_history_screen.dart b/lib/screens/session_history_screen.dart index 9c02ea8..0658d88 100644 --- a/lib/screens/session_history_screen.dart +++ b/lib/screens/session_history_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; + import '../l10n/generated/app_localizations.dart'; import '../models/models.dart'; import '../services/database_service.dart'; diff --git a/lib/screens/settings/settings_page.dart b/lib/screens/settings/settings_page.dart index 3fb8e2a..c57f78d 100644 --- a/lib/screens/settings/settings_page.dart +++ b/lib/screens/settings/settings_page.dart @@ -1,7 +1,9 @@ part of '../map_screen.dart'; -typedef _SettingsCategoryBuilder = - List Function(BuildContext context, StateSetter setPageState); +typedef _SettingsCategoryBuilder = List Function( + BuildContext context, + StateSetter setPageState, +); class _SettingsCategory { const _SettingsCategory({ diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index d3e824f..658c603 100644 --- a/lib/screens/settings/settings_screen.dart +++ b/lib/screens/settings/settings_screen.dart @@ -6,12 +6,11 @@ export 'widgets/settings_section_header.dart'; export 'widgets/settings_text_input_dialog.dart'; export 'widgets/upload_endpoint_selection_dialog.dart'; -typedef SettingsContentBuilder = - Widget Function( - BuildContext context, - StateSetter setPageState, - ScrollController scrollController, - ); +typedef SettingsContentBuilder = Widget Function( + BuildContext context, + StateSetter setPageState, + ScrollController scrollController, +); class SettingsScreen extends StatefulWidget { const SettingsScreen({ @@ -166,10 +165,8 @@ class SettingsCategoryTile extends StatelessWidget { ), ), title: Text(title), - titleTextStyle: Theme.of(context).textTheme.titleMedium?.copyWith( - color: colors.onSurface, - fontWeight: FontWeight.w500, - ), + titleTextStyle: Theme.of(context).textTheme.titleMedium + ?.copyWith(color: colors.onSurface, fontWeight: FontWeight.w500), trailing: Icon(Icons.chevron_right, color: colors.onSurfaceVariant), onTap: onTap, ); diff --git a/lib/screens/settings/widgets/settings_section_header.dart b/lib/screens/settings/widgets/settings_section_header.dart index 9661d45..732f2fe 100644 --- a/lib/screens/settings/widgets/settings_section_header.dart +++ b/lib/screens/settings/widgets/settings_section_header.dart @@ -22,10 +22,8 @@ class SettingsSectionHeader extends StatelessWidget { const SizedBox(width: 10), Text( title, - style: Theme.of(context).textTheme.titleSmall?.copyWith( - color: colors.primary, - fontWeight: FontWeight.w700, - ), + style: Theme.of(context).textTheme.titleSmall + ?.copyWith(color: colors.primary, fontWeight: FontWeight.w700), ), ], ), diff --git a/lib/screens/signal_trend_screen.dart b/lib/screens/signal_trend_screen.dart index 4af009e..dba267e 100644 --- a/lib/screens/signal_trend_screen.dart +++ b/lib/screens/signal_trend_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:intl/intl.dart'; + import '../l10n/generated/app_localizations.dart'; import '../models/models.dart'; diff --git a/lib/services/achievement_service.dart b/lib/services/achievement_service.dart index f0deeda..b834620 100644 --- a/lib/services/achievement_service.dart +++ b/lib/services/achievement_service.dart @@ -1,5 +1,7 @@ import 'dart:async'; + import 'package:shared_preferences/shared_preferences.dart'; + import 'database_service.dart'; import 'settings_service.dart'; diff --git a/lib/services/aggregation_service.dart b/lib/services/aggregation_service.dart index eace601..e3ad5b7 100644 --- a/lib/services/aggregation_service.dart +++ b/lib/services/aggregation_service.dart @@ -1,4 +1,5 @@ import 'dart:ui'; + import '../models/models.dart'; import '../utils/geohash_utils.dart'; import '../utils/color_blind_palette.dart'; diff --git a/lib/services/carpeater_service.dart b/lib/services/carpeater_service.dart index 9b4f823..a978ae7 100644 --- a/lib/services/carpeater_service.dart +++ b/lib/services/carpeater_service.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:typed_data'; + import 'meshcore_protocol.dart'; import 'lora_companion_service.dart'; import 'settings_service.dart'; diff --git a/lib/services/database_service.dart b/lib/services/database_service.dart index 710383d..b3de8b8 100644 --- a/lib/services/database_service.dart +++ b/lib/services/database_service.dart @@ -1,9 +1,12 @@ import 'dart:math'; + import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; + import '../models/impossible_zone.dart'; import '../models/models.dart'; + import 'dart:io'; class DatabaseService { diff --git a/lib/services/ducting_service.dart b/lib/services/ducting_service.dart index 67632b4..0ff9759 100644 --- a/lib/services/ducting_service.dart +++ b/lib/services/ducting_service.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:math'; + import 'package:http/http.dart' as http; + import 'database_service.dart'; /// Atmospheric ducting risk levels diff --git a/lib/services/location_service.dart b/lib/services/location_service.dart index 09b5f9d..02daf14 100644 --- a/lib/services/location_service.dart +++ b/lib/services/location_service.dart @@ -1,18 +1,22 @@ import 'dart:async'; import 'dart:io' show Platform; import 'dart:math'; + import 'package:battery_plus/battery_plus.dart'; import 'package:geolocator/geolocator.dart'; import 'package:latlong2/latlong.dart'; import 'package:permission_handler/permission_handler.dart' hide ServiceStatus; + import '../models/models.dart'; import '../models/location_quality_settings.dart'; import 'database_service.dart'; import 'lora_companion_service.dart'; import 'location_quality_filter.dart'; import '../utils/geohash_utils.dart'; + import 'package:flutter/widgets.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; + import '../l10n/app_locale.dart'; import '../l10n/generated/app_localizations.dart'; import 'persistent_debug_logger.dart'; @@ -483,28 +487,27 @@ class LocationService { _lastPositionEventAt = DateTime.now(); _positionStreamSubscription = - Geolocator.getPositionStream( - locationSettings: locationSettings, - ).listen( - (position) { - if (generation != _positionStreamGeneration) return; - _lastPositionEventAt = DateTime.now(); - _handleNewPosition( - position, - source: LocationPositionSource.fused, + Geolocator.getPositionStream(locationSettings: locationSettings) + .listen( + (position) { + if (generation != _positionStreamGeneration) return; + _lastPositionEventAt = DateTime.now(); + _handleNewPosition( + position, + source: LocationPositionSource.fused, + ); + }, + onError: (Object error) { + if (generation != _positionStreamGeneration) return; + _logger.logError('Location Stream', error.toString()); + _schedulePositionStreamRestart('stream error: $error'); + }, + onDone: () { + if (generation != _positionStreamGeneration) return; + _positionStreamSubscription = null; + _schedulePositionStreamRestart('stream closed'); + }, ); - }, - onError: (Object error) { - if (generation != _positionStreamGeneration) return; - _logger.logError('Location Stream', error.toString()); - _schedulePositionStreamRestart('stream error: $error'); - }, - onDone: () { - if (generation != _positionStreamGeneration) return; - _positionStreamSubscription = null; - _schedulePositionStreamRestart('stream closed'); - }, - ); await _logger.logLocationEvent( 'Position stream started ($reason; fused provider, 0m filter)', ); diff --git a/lib/services/lora_companion_service.dart b/lib/services/lora_companion_service.dart index 90e971c..21b6edb 100644 --- a/lib/services/lora_companion_service.dart +++ b/lib/services/lora_companion_service.dart @@ -2,11 +2,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; + import 'package:pointycastle/export.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:usb_serial/usb_serial.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:latlong2/latlong.dart'; + import 'debug_log_service.dart'; import 'meshcore_protocol.dart'; import '../models/models.dart'; @@ -809,18 +811,16 @@ class LoRaCompanionService { // Extract SNR int? snr; - final snrMatch = RegExp( - r'[Ss][Nn][Rr][:\s=]*(-?\d+(?:\.\d+)?)', - ).firstMatch(line); + final snrMatch = RegExp(r'[Ss][Nn][Rr][:\s=]*(-?\d+(?:\.\d+)?)') + .firstMatch(line); if (snrMatch != null) { snr = double.tryParse(snrMatch.group(1)!)?.toInt(); } // Extract RSSI int? rssi; - final rssiMatch = RegExp( - r'[Rr][Ss][Ss][Ii][:\s=]*(-?\d+)', - ).firstMatch(line); + final rssiMatch = RegExp(r'[Rr][Ss][Ss][Ii][:\s=]*(-?\d+)') + .firstMatch(line); if (rssiMatch != null) { rssi = int.tryParse(rssiMatch.group(1)!); } diff --git a/lib/services/meshcore_protocol.dart b/lib/services/meshcore_protocol.dart index 52792d8..f0ebda7 100644 --- a/lib/services/meshcore_protocol.dart +++ b/lib/services/meshcore_protocol.dart @@ -710,8 +710,7 @@ class MeshCoreProtocol { return { 'snr': snr, 'rssi': rssi, - 'sender': - null, // Not extracting sender from encrypted payload, use repeater instead + 'sender': null, // Not extracting sender from encrypted payload, use repeater instead 'repeater': repeater, 'repeaterKey': repeaterKey, }; diff --git a/lib/services/persistent_debug_logger.dart b/lib/services/persistent_debug_logger.dart index ed94fe8..80f3483 100644 --- a/lib/services/persistent_debug_logger.dart +++ b/lib/services/persistent_debug_logger.dart @@ -1,4 +1,5 @@ import 'dart:io'; + import 'package:path_provider/path_provider.dart'; import 'package:intl/intl.dart'; diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 5859854..663dcb6 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -1,4 +1,5 @@ import 'dart:convert'; + import 'package:shared_preferences/shared_preferences.dart'; import '../l10n/app_locale.dart'; diff --git a/lib/services/sound_service.dart b/lib/services/sound_service.dart index 9d73717..dce4725 100644 --- a/lib/services/sound_service.dart +++ b/lib/services/sound_service.dart @@ -1,4 +1,5 @@ import 'package:flutter/services.dart'; + import 'settings_service.dart'; /// Android ToneGenerator tone constants diff --git a/lib/services/tile_download_service.dart b/lib/services/tile_download_service.dart index 786605f..185ddf0 100644 --- a/lib/services/tile_download_service.dart +++ b/lib/services/tile_download_service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'dart:convert' show utf8; + import 'package:http/http.dart' as http; import 'package:latlong2/latlong.dart'; import 'package:crypto/crypto.dart' show md5; diff --git a/lib/services/upload_service.dart b/lib/services/upload_service.dart index 3b8bb27..b43ffc4 100644 --- a/lib/services/upload_service.dart +++ b/lib/services/upload_service.dart @@ -1,8 +1,10 @@ import 'dart:convert'; import 'dart:io'; + import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; + import 'database_service.dart'; import '../models/models.dart'; import '../constants/app_version.dart'; diff --git a/lib/services/widget_service.dart b/lib/services/widget_service.dart index e013258..f81b710 100644 --- a/lib/services/widget_service.dart +++ b/lib/services/widget_service.dart @@ -1,5 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:home_widget/home_widget.dart'; + import '../l10n/app_locale.dart'; import '../l10n/generated/app_localizations.dart'; import 'settings_service.dart'; diff --git a/lib/utils/geohash_utils.dart b/lib/utils/geohash_utils.dart index a7cb2f6..e2d6cbd 100644 --- a/lib/utils/geohash_utils.dart +++ b/lib/utils/geohash_utils.dart @@ -1,4 +1,5 @@ import 'dart:math'; + import 'package:geohash_plus/geohash_plus.dart' as geohash; import 'package:latlong2/latlong.dart'; diff --git a/pubspec.lock b/pubspec.lock index 142673b..ee44c92 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -505,10 +505,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.20.3" js: dependency: transitive description: @@ -585,26 +585,26 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.20" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.19.0" mgrs_dart: dependency: transitive description: @@ -990,10 +990,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.12" typed_data: dependency: transitive description: @@ -1102,10 +1102,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.2" vm_service: dependency: transitive description: @@ -1179,5 +1179,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.0 <4.0.0" + dart: ">=3.13.0 <4.0.0" flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index e032845..83e5e4f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.42+45 environment: - sdk: ^3.10.0 + sdk: ^3.13.0 dependencies: flutter: diff --git a/test/session_map_view_test.dart b/test/session_map_view_test.dart index 1d4641e..ddad314 100644 --- a/test/session_map_view_test.dart +++ b/test/session_map_view_test.dart @@ -134,9 +134,8 @@ void main() { endTime: DateTime.utc(2026, 8, 19, 12), sampleCount: 4, ); - final next = SessionMapView.session( - current, - ).afterStopWithSamples(finalized); + final next = SessionMapView.session(current) + .afterStopWithSamples(finalized); expect(next.session?.endTime, finalized.endTime); expect(next.session?.sampleCount, 4); }); From b46afa15eb7929a63b576f3ef3fec15a6274eb4e Mon Sep 17 00:00:00 2001 From: xarleyn Date: Thu, 20 Aug 2026 23:54:02 +0300 Subject: [PATCH 2/4] CI: bump Actions off deprecated Node 20 runtimes. checkout, setup-java, and upload-artifact were still on Node 20 majors; move them to current Node 24 releases. Co-authored-by: Cursor --- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74d45fd..f71c712 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: "17" @@ -49,7 +49,7 @@ jobs: run: flutter build apk --debug - name: Upload debug APK - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: app-debug path: build/app/outputs/flutter-apk/app-debug.apk diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3398602..8c36de4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,10 +24,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: "17" From 5524aab679e104f56a7aa9c377ab5101132d8d99 Mon Sep 17 00:00:00 2001 From: xarleyn Date: Fri, 21 Aug 2026 00:23:54 +0300 Subject: [PATCH 3/4] Update Dart plugins within the Flutter 3.47 / API 36 toolchain. Keep packages that need Android 17 or AGP 9 built-in Kotlin on last compatible versions, and adapt BLE connect plus share APIs. Co-authored-by: Cursor --- CHANGELOG.md | 5 + lib/screens/analytics_screen.dart | 3 +- lib/screens/debug_diagnostics_screen.dart | 20 ++- lib/screens/map_screen.dart | 46 +++-- lib/services/lora_companion_service.dart | 5 +- pubspec.lock | 198 +++++++++++----------- pubspec.yaml | 58 ++++--- 7 files changed, 181 insertions(+), 154 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d18f2b..d71e728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,6 +121,11 @@ service error. ### Changed +- Dart and Android plugins are updated within the Flutter 3.47 / API 36 + toolchain. BLE connect now uses FlutterBluePlus's nonprofit license. The map + stack stays on `flutter_map` 7 because `flutter_map_heatmap` still does not + support v8. Packages that need Android 17 (API 37) or AGP 9 built-in Kotlin + stay on the last versions that compile against API 36. - Android builds now follow the Flutter 3.47 toolchain: Gradle 9.3.1, Android Gradle Plugin 9.1.0, and Kotlin 2.4.0. CI and release workflows pin Flutter 3.47.1 so the Gradle/AGP/Kotlin warnings no longer appear. Gradle 9 no longer diff --git a/lib/screens/analytics_screen.dart b/lib/screens/analytics_screen.dart index 6cece03..2d26885 100644 --- a/lib/screens/analytics_screen.dart +++ b/lib/screens/analytics_screen.dart @@ -280,7 +280,8 @@ class _CoverageScoreTab extends StatelessWidget { ), const SizedBox(height: 16), ElevatedButton.icon( - onPressed: () => Share.share(shareText), + onPressed: () => + SharePlus.instance.share(ShareParams(text: shareText)), icon: const Icon(Icons.share, size: 18), label: Text(l10n.analyticsShareScore), ), diff --git a/lib/screens/debug_diagnostics_screen.dart b/lib/screens/debug_diagnostics_screen.dart index b30c97b..854832e 100644 --- a/lib/screens/debug_diagnostics_screen.dart +++ b/lib/screens/debug_diagnostics_screen.dart @@ -60,10 +60,12 @@ class _DebugDiagnosticsScreenState extends State { Future _shareLogFile(File file) async { final l10n = AppLocalizations.of(context); try { - await Share.shareXFiles( - [XFile(file.path)], - subject: l10n.debugDiagnosticsShareSubject, - text: l10n.debugDiagnosticsShareText, + await SharePlus.instance.share( + ShareParams( + files: [XFile(file.path)], + subject: l10n.debugDiagnosticsShareSubject, + text: l10n.debugDiagnosticsShareText, + ), ); } catch (e) { if (mounted) { @@ -348,10 +350,12 @@ class _LogViewerScreen extends StatelessWidget { IconButton( icon: const Icon(Icons.share), onPressed: () { - Share.share( - content, - subject: AppLocalizations.of(context) - .debugDiagnosticsShareSubjectWithFile(fileName), + SharePlus.instance.share( + ShareParams( + text: content, + subject: AppLocalizations.of(context) + .debugDiagnosticsShareSubjectWithFile(fileName), + ), ); }, ), diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 3ed65e9..34693e1 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1322,10 +1322,13 @@ class _MapScreenState extends State { final file = File('${directory!.path}/$fileName'); await file.writeAsString(content); - await Share.shareXFiles( - [XFile(file.path)], - subject: AppLocalizations.of(context).mapExportShareSubject, - text: AppLocalizations.of(context).mapExportShareText(samples.length), + await SharePlus.instance.share( + ShareParams( + files: [XFile(file.path)], + subject: AppLocalizations.of(context).mapExportShareSubject, + text: AppLocalizations.of(context) + .mapExportShareText(samples.length), + ), ); _showSnackBar(AppLocalizations.of(context).mapExportShared); } @@ -1525,9 +1528,12 @@ $placemarks final dir = await getApplicationDocumentsDirectory(); final file = File('${dir.path}/$fileName'); await file.writeAsString(jsonString); - await Share.shareXFiles([ - XFile(file.path), - ], text: AppLocalizations.of(context).mapSettingsShareText); + await SharePlus.instance.share( + ShareParams( + files: [XFile(file.path)], + text: AppLocalizations.of(context).mapSettingsShareText, + ), + ); } } catch (e) { _showSnackBar(AppLocalizations.of(context).mapExportFailed('$e')); @@ -1541,9 +1547,10 @@ $placemarks allowedExtensions: ['json'], ); - if (result == null || result.files.single.path == null) return; + if (result == null || result.files.isEmpty) return; + final pickedFile = result.files.single; - final file = File(result.files.single.path!); + final file = File(pickedFile.path!); final jsonString = await file.readAsString(); // Show confirmation dialog @@ -2203,7 +2210,7 @@ $placemarks imageBytes, quality: 100, fileName: fileName, - androidRelativePath: "Pictures/MeshCore", + androidRelativePath: 'MeshCore', skipIfExists: false, ); @@ -2231,9 +2238,12 @@ $placemarks final tempDir = await getTemporaryDirectory(); final file = File('${tempDir.path}/meshcore_screenshot.png'); await file.writeAsBytes(imageBytes); - await Share.shareXFiles([ - XFile(file.path), - ], text: AppLocalizations.of(context).mapScreenshotShareText); + await SharePlus.instance.share( + ShareParams( + files: [XFile(file.path)], + text: AppLocalizations.of(context).mapScreenshotShareText, + ), + ); }, child: Text(AppLocalizations.of(context).mapYes), ), @@ -5160,10 +5170,12 @@ $placemarks ); await file.writeAsBytes(imageBytes); - await Share.shareXFiles( - [XFile(file.path)], - subject: AppLocalizations.of(context).mapCoverageShareSubject, - text: statsText, + await SharePlus.instance.share( + ShareParams( + files: [XFile(file.path)], + subject: AppLocalizations.of(context).mapCoverageShareSubject, + text: statsText, + ), ); } catch (e) { setState(() { diff --git a/lib/services/lora_companion_service.dart b/lib/services/lora_companion_service.dart index 21b6edb..0cb38d6 100644 --- a/lib/services/lora_companion_service.dart +++ b/lib/services/lora_companion_service.dart @@ -471,7 +471,10 @@ class LoRaCompanionService { /// Connect to LoRa device via Bluetooth Future connectBluetooth(BluetoothDevice device) async { try { - await device.connect(timeout: const Duration(seconds: 15)); + await device.connect( + license: License.nonprofit, + timeout: const Duration(seconds: 15), + ); _bluetoothDevice = device; try { diff --git a/pubspec.lock b/pubspec.lock index ee44c92..e4ed0a7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" url: "https://pub.dev" source: hosted - version: "0.3.5+1" + version: "0.3.5+4" crypto: dependency: "direct main" description: @@ -125,10 +125,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" dart_earcut: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + sha256: a48d5da28e89bd02196e80d81ed8d7954923d00a0f4a68cc20b575038f023383 url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.15" dio: dependency: transitive description: @@ -197,10 +197,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" file: dependency: transitive description: @@ -229,10 +229,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08" + sha256: b938f77d042cbcd822936a7a359a7235bad8bd72070de1f827efc2cc297ac888 url: "https://pub.dev" source: hosted - version: "0.69.2" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -242,50 +242,58 @@ packages: dependency: "direct main" description: name: flutter_blue_plus - sha256: "69a8c87c11fc792e8cf0f997d275484fbdb5143ac9f0ac4d424429700cb4e0ed" + sha256: "5389f2b85305d0e03b025de7a92ec86137440b5108744f1017645ff67c5f2dcc" url: "https://pub.dev" source: hosted - version: "1.36.8" + version: "2.3.12" flutter_blue_plus_android: dependency: transitive description: name: flutter_blue_plus_android - sha256: "6f7fe7e69659c30af164a53730707edc16aa4d959e4c208f547b893d940f853d" + sha256: "5f1db477d442974c516196718e2ab66e3601e9a2de271bddde9368c1a85e6e64" url: "https://pub.dev" source: hosted - version: "7.0.4" + version: "9.0.3" flutter_blue_plus_darwin: dependency: transitive description: name: flutter_blue_plus_darwin - sha256: "682982862c1d964f4d54a3fb5fccc9e59a066422b93b7e22079aeecd9c0d38f8" + sha256: ce55b40e751da0d9a74bf14db3924d2553b7e67ca0270ea9260f2d46a44ead3a url: "https://pub.dev" source: hosted - version: "7.0.3" + version: "9.0.4" flutter_blue_plus_linux: dependency: transitive description: name: flutter_blue_plus_linux - sha256: "56b0c45edd0a2eec8f85bd97a26ac3cd09447e10d0094fed55587bf0592e3347" + sha256: "79387947c27d04fce505916d168a1f8b7a89846d22d11a659970aba316459622" url: "https://pub.dev" source: hosted - version: "7.0.3" + version: "9.0.3" flutter_blue_plus_platform_interface: dependency: transitive description: name: flutter_blue_plus_platform_interface - sha256: "84fbd180c50a40c92482f273a92069960805ce324e3673ad29c41d2faaa7c5c2" + sha256: "9378ed463673ab51e7ab72cf4bad3633b134182ca184ddcc598d6f7474ada993" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "9.0.3" flutter_blue_plus_web: dependency: transitive description: name: flutter_blue_plus_web - sha256: a1aceee753d171d24c0e0cdadb37895b5e9124862721f25f60bb758e20b72c99 + sha256: "62670fd0072e9424661170c3439eb2784679e0cb8420907ae2fe979aab8eed71" url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "9.0.3" + flutter_blue_plus_winrt: + dependency: transitive + description: + name: flutter_blue_plus_winrt + sha256: "0000b2d818e6f79ad07764206fdd8afb69426fd44453c6254c35828fd16aa09f" + url: "https://pub.dev" + source: hosted + version: "0.0.20" flutter_compass: dependency: "direct main" description: @@ -306,10 +314,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" url: "https://pub.dev" source: hosted - version: "0.13.1" + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -351,58 +359,58 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" url: "https://pub.dev" source: hosted - version: "2.0.33" + version: "2.0.35" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: "7686b1d6a29985dcbb808c59518226e603e3bfa7c0ddfd1a0d00e4cda77c868e" url: "https://pub.dev" source: hosted - version: "9.2.4" - flutter_secure_storage_linux: + version: "10.3.1" + flutter_secure_storage_darwin: dependency: transitive description: - name: flutter_secure_storage_linux - sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + name: flutter_secure_storage_darwin + sha256: "82329fa5cdf343773b1b6897dea959105a29f092454259edff92f9f6637e8149" url: "https://pub.dev" source: hosted - version: "1.2.3" - flutter_secure_storage_macos: + version: "0.3.2" + flutter_secure_storage_linux: dependency: transitive description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + name: flutter_secure_storage_linux + sha256: "76fa9c841b3b1619fc5b5bc36efc7d158fa2356f223b6caeb1d0c80a54168546" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.0.2" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "788060052712555182aba55ecb5f8b6e5cb9cfe8f776c83249a61fe3ce877db4" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.3" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -441,10 +449,10 @@ packages: dependency: transitive description: name: geolocator_apple - sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 + sha256: "853803d6bb1713c094e935b4a5ae5f19c0308acf81da13fa9ff84fb4c70c0b73" url: "https://pub.dev" source: hosted - version: "2.3.13" + version: "2.3.14" geolocator_platform_interface: dependency: transitive description: @@ -457,10 +465,10 @@ packages: dependency: transitive description: name: geolocator_web - sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172 + sha256: "19e485a0f8d6a88abcf9c53cba3a4105e14b7435ed8ac1c108c067b938fe8429" url: "https://pub.dev" source: hosted - version: "4.1.3" + version: "4.1.4" geolocator_windows: dependency: transitive description: @@ -473,10 +481,10 @@ packages: dependency: "direct main" description: name: home_widget - sha256: "7430f7549d42cef2e729bd3c779de748b93f1eb78b1abfe6bca8fffd1cfce3e9" + sha256: fece84c7464099873c3ace38394ad7053509a4b0e36e57a1105a6aa31f47f23c url: "https://pub.dev" source: hosted - version: "0.7.0+1" + version: "0.9.3" http: dependency: "direct main" description: @@ -509,14 +517,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.3" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" json_annotation: dependency: transitive description: @@ -625,10 +625,10 @@ packages: dependency: transitive description: name: package_info_plus - sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.0.1" package_info_plus_platform_interface: dependency: transitive description: @@ -649,10 +649,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.6" path_provider_android: dependency: transitive description: @@ -713,34 +713,34 @@ packages: dependency: transitive description: name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8 url: "https://pub.dev" source: hosted - version: "9.4.7" + version: "9.6.1" permission_handler_html: dependency: transitive description: name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac" url: "https://pub.dev" source: hosted - version: "0.1.3+5" + version: "0.1.4+1" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23 url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "0.2.2" petitparser: dependency: transitive description: @@ -769,10 +769,10 @@ packages: dependency: "direct main" description: name: pointycastle - sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" url: "https://pub.dev" source: hosted - version: "3.9.1" + version: "4.0.0" polylabel: dependency: transitive description: @@ -797,14 +797,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" saver_gallery: dependency: "direct main" description: @@ -825,26 +817,26 @@ packages: dependency: "direct main" description: name: share_plus - sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da + sha256: "223873d106614442ea6f20db5a038685cc5b32a2fba81cdecaefbbae0523f7fa" url: "https://pub.dev" source: hosted - version: "10.1.4" + version: "12.0.2" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "6.1.0" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.5" shared_preferences_android: dependency: transitive description: @@ -910,42 +902,42 @@ packages: dependency: "direct main" description: name: sqflite - sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" sqflite_android: dependency: transitive description: name: sqflite_android - sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b url: "https://pub.dev" source: hosted - version: "2.4.2+2" + version: "2.4.3" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + sha256: "5bf6a55c166e73bf651ba7ec3ed486e577620e3dc8f3a9c6a258a8031b624590" url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.5.11" sqflite_darwin: dependency: transitive description: name: sqflite_darwin - sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + sha256: c86ca18b8f666bbf903924687fe21cc16fc385d086005067e26619ca530bef9f url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3+1" sqflite_platform_interface: dependency: transitive description: name: sqflite_platform_interface - sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" stack_trace: dependency: transitive description: @@ -974,10 +966,10 @@ packages: dependency: transitive description: name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + sha256: "3a7b5d17422dd0f8d5c6c14feaa5a1c65638b9455f871a96f08437562c046931" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.4.1+2" term_glyph: dependency: transitive description: @@ -1070,10 +1062,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" url_launcher_windows: dependency: transitive description: @@ -1118,18 +1110,18 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" + sha256: ddf3db70eaa10c37558ff817519b85d527dbd21034fd5d8e1c2e85f31588f1c1 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.2" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" + sha256: "0618d1799f0b28bcf98255b4ee8313e6fc4d38589dc4ee5fe5840d57d1aff6da" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.6.0" web: dependency: transitive description: @@ -1180,4 +1172,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.13.0 <4.0.0" - flutter: ">=3.35.0" + flutter: ">=3.44.0" diff --git a/pubspec.yaml b/pubspec.yaml index 83e5e4f..d69c763 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,53 +27,63 @@ dependencies: flutter_localizations: sdk: flutter - cupertino_icons: ^1.0.8 + cupertino_icons: ^1.0.9 # Map and location + # flutter_map 8 and flutter_map_cache 2 need a heatmap plugin that still + # only supports flutter_map 7. flutter_map: ^7.0.2 latlong2: ^0.9.1 - flutter_map_cache: ^1.5.0 - dio_cache_interceptor: ^3.5.0 + flutter_map_cache: ^1.5.2 + dio_cache_interceptor: ^3.5.1 dio_cache_interceptor_file_store: ^1.2.3 flutter_map_heatmap: ^0.0.8 - fl_chart: ^0.69.2 - + fl_chart: ^1.2.0 + # Location tracking - geolocator: ^13.0.2 + # 14 pulls package_info_plus 10, which skips kotlin-android on AGP 9. + geolocator: ^13.0.4 flutter_compass: ^0.8.1 - permission_handler: ^11.3.1 - wakelock_plus: ^1.2.8 - flutter_foreground_task: ^8.11.0 + # 13.x needs Android 17 (API 37). + permission_handler: ^11.4.0 + wakelock_plus: '>=1.2.8 <1.6.0' + # 9+ expects AGP 9 built-in Kotlin. + flutter_foreground_task: ^8.17.0 geohash_plus: ^1.0.3 # Networking and device communication http: ^1.2.2 url_launcher: ^6.3.1 usb_serial: ^0.5.2 - flutter_blue_plus: ^1.32.12 + flutter_blue_plus: ^2.3.12 # Persistence and security - sqflite: ^2.3.3+2 - path_provider: ^2.1.5 + sqflite: ^2.4.3 + path_provider: ^2.1.6 path: ^1.9.0 - flutter_secure_storage: ^9.2.2 - shared_preferences: ^2.3.3 - pointycastle: ^3.9.1 + # 11 needs API 37. + flutter_secure_storage: ^10.0.0 + shared_preferences: ^2.5.5 + pointycastle: ^4.0.0 # Utilities and export intl: any - file_picker: ^8.0.0+1 - share_plus: ^10.1.2 - + # 10+ skips kotlin-android on AGP 9. + file_picker: ^8.3.7 + # 13.2+ skips kotlin-android on AGP 9. + share_plus: '>=11.0.0 <13.2.0' + # Screenshot capture screenshot: ^3.0.0 - saver_gallery: ^3.0.6 - + # 5 expects AGP 9 built-in Kotlin. + saver_gallery: ^3.0.10 + # Home screen widget - home_widget: ^0.7.0 - + home_widget: ^0.9.3 + # Device battery monitoring - battery_plus: ^6.0.3 + # 7+ skips kotlin-android on AGP 9. + battery_plus: ^6.2.3 # Crypto (for tile cache hashing) @@ -83,7 +93,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 - flutter_launcher_icons: ^0.13.1 + flutter_launcher_icons: ^0.14.4 flutter: generate: true From 07622ac6b88c246a7bf6ac51add922da05b524f5 Mon Sep 17 00:00:00 2001 From: xarleyn Date: Sat, 22 Aug 2026 12:58:11 +0300 Subject: [PATCH 4/4] docs: add Russian fork changelog draft --- docs/README.md | 1 + docs/changelog-fork-ru.md | 64 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 docs/changelog-fork-ru.md diff --git a/docs/README.md b/docs/README.md index 13c7333..4af450a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,6 +20,7 @@ - [Debugging ping responses](development/debugging-pings.md) - [Map provider investigation](development/map-providers.md) - [Android release builds, versioning, signing, and GitHub Actions](development/releasing.md) +- [Fork changelog draft (RU)](changelog-fork-ru.md) - [Version history](../CHANGELOG.md) Screenshot source files are stored in `assets/screenshots/`. diff --git a/docs/changelog-fork-ru.md b/docs/changelog-fork-ru.md new file mode 100644 index 0000000..784f570 --- /dev/null +++ b/docs/changelog-fork-ru.md @@ -0,0 +1,64 @@ + + +MeshCore Wardrive 1.0.42-xa1: +- Форк wardrive-приложения MeshCore. База — апстрим v1.0.42 от mintylinux, всё перечисленное ниже — изменения форка относительно неё. +- Полная русская локализация: переведены все экраны и диалоги, уведомления, виджет на рабочем столе, достижения и диагностика; термины подогнаны под радио-жаргон (пинги, репитеры, отправка). В настройках появился выбор языка: Системная / English / Русский. + +🖼️ [ФОТО: выбор языка в настройках и русский интерфейс] + +- Настройки полностью переработаны в стиле Android: главный экран с карточками категорий и отдельные страницы для каждой из них — карта, позиционирование, оповещения, Carpeater, приложение/устройство, обнаружение, статистика, данные, диагностика, онлайн-сервисы. Добавлены быстрые ярлыки для прокрутки к нужной секции; диалоги, bottom-шиты, выбор файлов и вложенные экраны возвращают обратно в свою категорию, а не сваливают на корень настроек. + +🖼️ [ФОТО: главный экран настроек с категориями] + +- Долгое нажатие на карту открывает меню выбора того, что добавить: + 0) запланированный репитер — отметка возможного места будущего репитера; + 1) зону приватности — область, данные из которой исключаются из отправок и экспорта; + 2) зону исключения GPS — область, где вы физически не можете находиться. GPS/Wi-Fi-фиксы внутри неё отбрасываются — ни сэмпла, ни пинга, — а карта держит последнюю достоверную позицию. Сами зоны на карте не рисуются. Добавлять зоны исключения можно как из настроек, так и этим долгим нажатием. + +🖼️ [ФОТО: меню долгого нажатия на карте] + +- Маркер вашей позиции: помимо классического синего круга доступна «стрелка направления», следующая за компасом телефона (если компаса нет — по GPS-курсу в движении). В режиме стрелки кнопка компаса включает режим «курс вверх» — карта вращается по вашему движению; ручное вращение карты выключает слежение за курсом. +- Калибровка компаса «восьмёркой»: при устойчиво ненадёжном магнитометре в режиме стрелки через короткую задержку появляется компактный баннер с предложением калибровки («Позже» прячет его на сутки), повторная калибровка доступна из настроек или долгим нажатием на кнопку компаса. Фикс: баннер больше не перекрывается системной навигационной панелью Android. + +🖼️ [ФОТО: стрелка направления и режим «курс вверх»] + +- LOD-отрисовка карты: на дальнем зуме ячейки покрытия, связи и точки сэмплов группируются по geohash — карта работает заметно быстрее. По желанию отключается в настройках отображения карты (по умолчанию включено). +- Фикс общего покрытия сообщества: тайлы выровнены по точным границам geohash и используют ту же точность LOD, что и локальное покрытие, поэтому сетки совпадают ячейка-в-ячейку; общее покрытие не зависит от фильтра сессии и скрывается только собственным переключателем; тап по ячейке показывает те же агрегированные данные, что и оверлей. +- До первого GPS-фикса карта открывается по среднему сохранённых измерений (масштаб подстраивается под разброс точек) вместо стандартного вида; первый фикс, как и раньше, центрует карту на вас. +- Долгое нажатие на кнопку Play начинает новую запись на чистой карте, не удаляя сохранённые сэмплы; короткое нажатие по-прежнему показывает всё покрытие. Пустую сессию (без GPS-точек) при остановке предлагается сохранить или удалить; после удаления сессии на карте показывается последняя сохранённая. +- Фикс UI: кнопка Play больше не использует Material-tooltip, «глотавший» жест долгого нажатия; подсказка «Показать на карте» в истории сессий перекрашена в цвет темы и читаема на тёмных карточках. + +🖼️ [ФОТО: запись на чистой карте поверх старых данных] + +- beaconDB Wi-Fi-позиционирование (опционально): приложение сканирует ближайшие точки доступа каждые 30 секунд и при валидной Wi-Fi-оценке приоритизирует её над fused/GPS. Исключаются _nomap, скрытые, рандомизированные и устаревшие сети, IP- и сотовый фолбэк отключены; активная Wi-Fi-позиция показывается голубым маркером. При включении приложение даёт ссылку на параметры разработчика для отключения троттлинга сканирования Wi-Fi и удерживает Wi-Fi-lock во время записи. + +🖼️ [ФОТО: настройка beaconDB и голубой Wi-Fi-маркер] + +- Настраиваемые пороги качества позиции: точность, неправдоподобное движение и зоны исключения GPS собраны в отдельном подменю, скорость вводится в привычных км/ч, возврат к встроенным значениям — одним нажатием. +- Доработка геолокации: используется fused-провайдер Android (GPS, сотовые сети и Wi-Fi работают вместе); mock-позиции исключаются из карты и записываемых данных; «сторожевой таймер» перезапускает зависшие потоки позиции, поиск позиции возобновляется сам после повторного включения служб геолокации; слишком неточные фиксы и «самолётные» прыжки отбрасываются без блокировки честных поездок по высокогорным дорогам. Ошибки старта трекинга теперь конкретные: разрешение, настройки или сервис. +- Старт записи запрашивает «неограниченный» фоновый трекинг: точную геолокацию, доступ «Разрешить всегда» и исключение из оптимизации батареи. +- Режим энергосбережения можно полностью выключить; авто-удвоение интервала пинга при ≤20% батареи осталось, включено по умолчанию и меняется прямо во время записи. +- Темы интерфейса и карты теперь выбираются независимо; существующие установки сохраняют прежний вид карты. +- Радиопротокол синхронизирован с MeshCore v13: корректные старт приложения и согласование устройства, поиск контактов и обработка advert'ов, актуальные коды пакетов, исправленный разбор raw/control/login-кадров. BLE-подключение использует официальные UART UUID и запрашивает MTU 512. +- Поддержка полных ID узлов (full_node_id): полные публичные ключи сохраняются в сэмплах и уходят в отправки, короткие ID остаются для отображения и поиска репитеров. Связи «ячейка—репитер» матчатся и по полным, и по коротким ID без учёта регистра, в деталях покрытия показываются двухбайтовые префиксы, кольца предсказания покрытия корректно сопоставляют репитеров по полному ID. +- «Оценка позиции по радио»: если успешный пинг услышали минимум три репитера с известными координатами, на карте появляется временная серая оценка позиции вашего радио с кругом неопределённости — визуально отличима от синей GPS-позиции; скрывается в настройках карты, не отключая расчёт. + +🖼️ [ФОТО: серая оценка позиции с кругом неопределённости] + +- Ответы на радиопинг показываются сразу после первого ответившего репитера, с реальным временем ответа; остальные ответы продолжают собираться для оценки позиции, не задерживая отклик, а параллельные циклы обнаружения исключены. +- Полный сбор ответов обнаружения стал настраиваемым: «Быстрый» завершает цикл через 3 секунды после первого ответа, «Полный» слушает до таймаута обнаружения. +- Фикс декодирования имён репитеров: кириллица и эмодзи в именах теперь отображаются корректно. +- Новая настройка «Не гасить экран»: дисплей не засыпает, пока приложение открыто, не вмешиваясь в существующий wakelock трекинга. +- Выбор сайта отправки перед загрузкой: перед отправкой данных показывается диалог с галочками эндпоинтов. +- Региональные эндпоинты по умолчанию: на свежих установках для русской локали используется meshcoretel.ru, для остальных — meshcoretel.io (интегрированы обновления форка meshcoretel). +- Оффлайн-режим: баннер по всему приложению распознаёт отсутствие или ограничение доступа в интернет короткой периодической проверкой; запись GPS, связь с радио и локальное хранение продолжают работать. Проверка обновлений быстро завершается по таймауту с краткой ошибкой вместо вечного ожидания. +- Bluetooth-скан: устройства появляются в списке сразу по мере обнаружения, не дожидаясь таймаута сканирования; ранее использованные и сопряжённые радио видны сразу. +- Фикс Quick Settings: плавающая панель больше не падает при сохранённых значениях, отсутствующих в компактных меню (таймаут 5/25 с, «Частый» пинг 50 м) — списки приведены к соответствию с настройками, неизвестные сохранённые значения принимаются. +- Фикс падений настроек: текстовые поля держат контроллеры живыми до завершения закрытия диалога — больше нет крашей и каскадных ошибок разметки после сохранения порогов качества позиции и других редактируемых параметров. +- Фикс пингов: сохраняется по сэмплу на каждый уникальный ответ репитера с латентностью каждого ответа; дубликаты ответов одного узла по-прежнему схлопываются. +- Фикс подключения: на время полного цикла USB/Bluetooth-подключения кнопка отключается — нельзя запустить параллельные сканы и запросы соединения. +- Звуковой сигнал при потере связи: если LoRa-устройство отключилось неожиданно (выдернут USB, оборван Bluetooth-линк), приложение проигрывает характерный двойной гудок с двойной вибрацией; ручное отключение с карты остаётся беззвучным. Переключатель «Сигнал при потере связи» — в настройках, раздел «Звук и оповещения», по умолчанию включён. +- Дефолты свежих установок приведены к разумным: метрические единицы, точность geohash 7, пинги по времени каждые 30 секунд, таймаут обнаружения 10 секунд; дополнительно доступны интервалы пинга 20, 25 и 45 секунд. +- Для сборщиков: GitHub Actions CI (проверка форматирования, анализатор, тесты, debug-сборка на main) и ручной workflow Release APK, публикующий подписанный APK в GitHub Releases; версия Android-сборки автоматизирована из pubspec.yaml. Также готово исследование карт-провайдеров (Яндекс Tiles API / MapKit, hosted-альтернативы, офлайн, лицензионные риски). +- Внутренний рефакторинг: экран карты и настройки разнесены на контроллеры, компоненты и диалоги — поведение не изменилось, но код стало заметно проще поддерживать и расширять.