diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa1ae41..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" @@ -30,6 +30,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable + flutter-version: "3.47.1" cache: true - name: Install dependencies @@ -48,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 d9f0c97..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" @@ -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 44f0a47..a45b3ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,6 +157,17 @@ 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 + 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. - The update check and the "View on GitHub" entry in Settings → About now point at the fork's releases. The check uses the releases list endpoint so prerelease-only repositories are detected, and release tags are matched by 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/docs/README.md b/docs/README.md index 8e19ea2..f2c406a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,6 +21,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-альтернативы, офлайн, лицензионные риски). +- Внутренний рефакторинг: экран карты и настройки разнесены на контроллеры, компоненты и диалоги — поведение не изменилось, но код стало заметно проще поддерживать и расширять. diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index d823369..fb790ea 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 415b9d8..ca955e4 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 dd27ff8..0d84fd1 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 7cdc6df..f6d6e32 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'; @@ -278,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 27eceb1..833fe80 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 @@ -57,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) { @@ -105,9 +110,8 @@ class _DebugDiagnosticsScreenState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - AppLocalizations.of( - context, - ).debugDiagnosticsErrorDeleting('$e'), + AppLocalizations.of(context) + .debugDiagnosticsErrorDeleting('$e'), ), ), ); @@ -346,11 +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/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/dialogs/map_entity_dialogs.dart b/lib/screens/map/dialogs/map_entity_dialogs.dart index 6eb4897..c264b07 100644 --- a/lib/screens/map/dialogs/map_entity_dialogs.dart +++ b/lib/screens/map/dialogs/map_entity_dialogs.dart @@ -338,9 +338,9 @@ class CommunityCellInfoDialog extends StatelessWidget { 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()); final repeatersText = cell.repeaters.isEmpty ? l10n.settingsNone : cell.repeaters.entries diff --git a/lib/screens/map/dialogs/offline_tile_dialogs.dart b/lib/screens/map/dialogs/offline_tile_dialogs.dart index 96702e0..7ef4597 100644 --- a/lib/screens/map/dialogs/offline_tile_dialogs.dart +++ b/lib/screens/map/dialogs/offline_tile_dialogs.dart @@ -30,8 +30,9 @@ class OfflineTileDownloadOutcome { final int succeeded; } -typedef OfflineTileDownload = - Future Function(void Function(int done, int total) onProgress); +typedef OfflineTileDownload = Future Function( + void Function(int done, int total) onProgress, +); class OfflineTileDownloadDialog extends StatefulWidget { const OfflineTileDownloadDialog({ diff --git a/lib/screens/map/dialogs/upload_endpoint_dialog.dart b/lib/screens/map/dialogs/upload_endpoint_dialog.dart index 6eec506..1665b32 100644 --- a/lib/screens/map/dialogs/upload_endpoint_dialog.dart +++ b/lib/screens/map/dialogs/upload_endpoint_dialog.dart @@ -326,10 +326,9 @@ class UploadProgressOutcome { final Object? error; } -typedef UploadOperation = - Future> Function( - void Function(String siteName, int current, int total) onProgress, - ); +typedef UploadOperation = Future> Function( + void Function(String siteName, int current, int total) onProgress, +); class UploadProgressDialog extends StatefulWidget { const UploadProgressDialog({required this.upload, super.key}); diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 2af876b..771025d 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -1,12 +1,14 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; + 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'; @@ -56,6 +58,7 @@ import 'map/widgets/map_action_buttons.dart'; import 'map/widgets/map_control_panel.dart'; import 'map/widgets/map_quick_settings_panel.dart'; import '../services/widget_service.dart'; + import 'package:usb_serial/usb_serial.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:http/http.dart' as http; @@ -69,7 +72,9 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; import 'package:dio_cache_interceptor_file_store/dio_cache_interceptor_file_store.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'; @@ -1186,9 +1191,8 @@ class _MapScreenState extends State { ); if (!mounted) return; _showSnackBar( - AppLocalizations.of( - context, - ).mapExportedSamples(samples.length, format.displayName), + AppLocalizations.of(context) + .mapExportedSamples(samples.length, format.displayName), ); } else if (choice == ExportDestination.share) { final directory = await getExternalStorageDirectory(); @@ -1196,10 +1200,13 @@ class _MapScreenState extends State { await file.writeAsString(content); if (!mounted) return; - 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), + ), ); if (!mounted) return; _showSnackBar(AppLocalizations.of(context).mapExportShared); @@ -1305,9 +1312,12 @@ class _MapScreenState extends State { final file = File('${dir.path}/$fileName'); await file.writeAsString(jsonString); if (!mounted) return; - 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) { if (!mounted) return; @@ -1322,9 +1332,10 @@ class _MapScreenState extends State { 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 @@ -1730,7 +1741,7 @@ class _MapScreenState extends State { imageBytes, quality: 100, fileName: fileName, - androidRelativePath: "Pictures/MeshCore", + androidRelativePath: 'MeshCore', skipIfExists: false, ); @@ -1750,7 +1761,9 @@ class _MapScreenState extends State { final file = File('${tempDir.path}/meshcore_screenshot.png'); await file.writeAsBytes(imageBytes); if (!mounted) return; - await Share.shareXFiles([XFile(file.path)], text: shareText); + await SharePlus.instance.share( + ShareParams(files: [XFile(file.path)], text: shareText), + ); } else { if (!mounted) return; _showSnackBar(AppLocalizations.of(context).mapFailedToSaveScreenshot); @@ -2418,9 +2431,9 @@ class _MapScreenState extends State { _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), ), ); }, @@ -2850,10 +2863,12 @@ class _MapScreenState extends State { await file.writeAsBytes(imageBytes); if (!mounted) return; - 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(() { @@ -2966,9 +2981,8 @@ class _MapScreenState extends State { }); if (!mounted) return; _showSnackBar( - AppLocalizations.of( - context, - ).mapDownloadedCoverageCells(coverage.length), + AppLocalizations.of(context) + .mapDownloadedCoverageCells(coverage.length), ); } else { // Try loading from cache 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 739b69f..924f23e 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, +); enum _SettingsOverviewGroupId { map, sampling, app, data, system } diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index 544b0cb..0e30c76 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({ 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 8ff6a5c..76e4e6c 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 66b4375..9d38664 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 8d368c8..76e544b 100644 --- a/lib/services/database_service.dart +++ b/lib/services/database_service.dart @@ -1,10 +1,14 @@ 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'; + import 'package:flutter/foundation.dart'; class DatabaseService { diff --git a/lib/services/ducting_service.dart b/lib/services/ducting_service.dart index d3b23b7..691c0cb 100644 --- a/lib/services/ducting_service.dart +++ b/lib/services/ducting_service.dart @@ -1,7 +1,10 @@ import 'dart:convert'; import 'dart:math'; + import 'package:http/http.dart' as http; + import 'database_service.dart'; + import 'package:flutter/foundation.dart'; /// Atmospheric ducting risk levels diff --git a/lib/services/location_service.dart b/lib/services/location_service.dart index a31d9a2..d34055c 100644 --- a/lib/services/location_service.dart +++ b/lib/services/location_service.dart @@ -1,10 +1,12 @@ 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'; @@ -12,8 +14,10 @@ import 'lora_companion_service.dart'; import 'location_quality_filter.dart'; import 'bad_fix_monitor.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'; @@ -564,28 +568,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 caac622..0cd7925 100644 --- a/lib/services/lora_companion_service.dart +++ b/lib/services/lora_companion_service.dart @@ -1,12 +1,14 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; + import 'package:flutter/foundation.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:latlong2/latlong.dart'; import 'package:pointycastle/export.dart'; import 'package:usb_serial/usb_serial.dart'; + import 'debug_log_service.dart'; import 'meshcore_protocol.dart'; import 'settings_service.dart'; @@ -652,7 +654,10 @@ class LoRaCompanionService { _connectInFlight = true; try { try { - await device.connect(timeout: const Duration(seconds: 15)); + await device.connect( + license: License.nonprofit, + timeout: const Duration(seconds: 15), + ); _bluetoothDevice = device; try { @@ -1028,18 +1033,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 90c8c14..8bf1830 100644 --- a/lib/services/meshcore_protocol.dart +++ b/lib/services/meshcore_protocol.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:typed_data'; + import 'package:flutter/foundation.dart'; /// MeshCore Companion Radio Binary Protocol @@ -837,8 +838,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/settings_service.dart b/lib/services/settings_service.dart index d5d4298..bec6ee3 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 2b80e77..efdf0be 100644 --- a/lib/services/sound_service.dart +++ b/lib/services/sound_service.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; 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 bd41694..b84eb98 100644 --- a/lib/services/upload_service.dart +++ b/lib/services/upload_service.dart @@ -1,11 +1,14 @@ 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'; + import 'package:flutter/foundation.dart'; class UploadService { 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/lib/utils/sample_export.dart b/lib/utils/sample_export.dart index 79cffa5..26da267 100644 --- a/lib/utils/sample_export.dart +++ b/lib/utils/sample_export.dart @@ -20,9 +20,8 @@ abstract final class SampleExport { static String buildGpx(List samples, {DateTime? generatedAt}) { final sorted = List.from(samples) ..sort((a, b) => a.timestamp.compareTo(b.timestamp)); - final generatedDate = DateFormat( - 'yyyy-MM-dd', - ).format(generatedAt ?? DateTime.now()); + final generatedDate = DateFormat('yyyy-MM-dd') + .format(generatedAt ?? DateTime.now()); final buffer = StringBuffer(); buffer.writeln(''); @@ -56,9 +55,8 @@ abstract final class SampleExport { static String buildKml(List samples, {DateTime? generatedAt}) { final sorted = List.from(samples) ..sort((a, b) => a.timestamp.compareTo(b.timestamp)); - final generatedDate = DateFormat( - 'yyyy-MM-dd', - ).format(generatedAt ?? DateTime.now()); + final generatedDate = DateFormat('yyyy-MM-dd') + .format(generatedAt ?? DateTime.now()); final coords = sorted .map( diff --git a/pubspec.lock b/pubspec.lock index 3cfced0..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: @@ -1179,5 +1171,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.11.0-0 <4.0.0" - flutter: ">=3.35.0" + dart: ">=3.13.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/pubspec.yaml b/pubspec.yaml index e032845..d69c763 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: @@ -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 diff --git a/test/session/session_map_view_test.dart b/test/session/session_map_view_test.dart index 1d4641e..ddad314 100644 --- a/test/session/session_map_view_test.dart +++ b/test/session/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); });