From 4b9b9a232770b2d47e040dc4835b3909405f4fa2 Mon Sep 17 00:00:00 2001 From: Yandrik Date: Tue, 17 Feb 2026 16:41:02 +0100 Subject: [PATCH] feat(map): migrate to flutter_map 8.2 with OSM-compliant tiles --- lib/main.dart | 4 +- lib/map_page.dart | 121 ++++++++++++++++++------------------------ pubspec.lock | 56 +++---------------- pubspec.yaml | 4 +- test/widget_test.dart | 46 ++++++++-------- 5 files changed, 88 insertions(+), 143 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 308723a..2c1d373 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -34,12 +34,14 @@ Future main() async { create: (BuildContext context) => GetIt.I.get(), ), ], - child: MyApp(), + child: const MyApp(), ), ); } class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override Widget build(BuildContext context) { return MaterialApp( diff --git a/lib/map_page.dart b/lib/map_page.dart index a114c9d..c2d0c33 100644 --- a/lib/map_page.dart +++ b/lib/map_page.dart @@ -1,14 +1,7 @@ -import 'package:bloc/bloc.dart'; -import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart'; -import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_cache/flutter_map_cache.dart'; import 'package:flutter_map_compass/flutter_map_compass.dart'; -import 'package:flutter_map_math/flutter_geo_math.dart'; import 'package:get_it/get_it.dart'; import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; @@ -18,15 +11,9 @@ import 'package:ot_viewer_app/settings_page.dart'; import 'package:ot_viewer_app/user_path_bloc.dart'; import 'package:ot_viewer_app/util.dart'; import 'package:ot_viewer_app/web_socket_cubit.dart'; -import 'package:path_provider/path_provider.dart'; import 'owntracks_api.dart'; import 'dart:math' as math; -Future getPath() async { - final cacheDirectory = await getTemporaryDirectory(); - return cacheDirectory.path; -} - class MapPage extends StatefulWidget { const MapPage({super.key}); @@ -55,56 +42,50 @@ class _MapPageState extends State { refreshCubit.stream.listen((_) => cubit.reconnect()); return cubit; }, - child: FutureBuilder( - future: getPath(), - builder: (something, tempPath) => - BlocBuilder( - builder: (context, state) { - if (tempPath.data == null) { - return const Center(child: Text('Loading Map...')); - } - return Stack(children: [ - FlutterMap( + child: BlocBuilder( + builder: (context, state) { + return Stack(children: [ + FlutterMap( + mapController: _mapController, + options: const MapOptions( + initialCenter: LatLng(48.3285, 9.8942), + initialZoom: 13.0, + ), + children: [ + TileLayer( + urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'ot_viewer_app', + tileProvider: NetworkTileProvider( + headers: { + 'User-Agent': 'ot_viewer_app/1.0', + }, + ), + ), + ...state.activeDevices.map((id) => + UserPath(key: ValueKey(id), device: id, settings: state)), + const MapCompass.cupertino( + rotationDuration: Duration(milliseconds: 600)), + // CurrentLocationLayer(), TODO: add permission + RichAttributionWidget( + attributions: [ + TextSourceAttribution( + 'OpenStreetMap contributors', + onTap: () => + (Uri.parse('https://openstreetmap.org/copyright')), + ), + ], + ), + ], + ), + Positioned( + bottom: 16, + left: 16, + child: TrackerSelector( mapController: _mapController, - options: const MapOptions( - initialCenter: LatLng(48.3285, 9.8942), - initialZoom: 13.0, - ), - children: [ - TileLayer( - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - tileProvider: CachedTileProvider( - maxStale: const Duration(days: 30), - store: HiveCacheStore(tempPath.data!, - hiveBoxName: 'HiveCacheStore')), - ), - ...state.activeDevices.map((id) => - UserPath(key: ValueKey(id), device: id, settings: state)), - const MapCompass.cupertino( - rotationDuration: Duration(milliseconds: 600)), - // CurrentLocationLayer(), TODO: add permission - RichAttributionWidget( - attributions: [ - TextSourceAttribution( - 'OpenStreetMap contributors', - onTap: () => - (Uri.parse('https://openstreetmap.org/copyright')), - ), - ], - ), - ], ), - Positioned( - bottom: 16, - left: 16, - child: TrackerSelector( - mapController: _mapController, - ), - ), - ]); - }, - ), + ), + ]); + }, ), ); } @@ -140,7 +121,6 @@ class _UserPathState extends State { if (state case LocationUpdateReceived( :final position, - :final deviceId )) { if (userPathBloc.deviceId == state.deviceId) { userPathBloc.add(UserPathLiveSubscriptionUpdate(position)); @@ -161,15 +141,15 @@ class _UserPathState extends State { .add(UserPathLoginDataChanged(widget.settings)); print("rebuild"); - final _istate = state as MainUserPathState; + final mainState = state as MainUserPathState; // make markers final List markers = []; - if (state.livePoints.isNotEmpty) { + if (mainState.livePoints.isNotEmpty) { markers.add(Marker( width: 500, height: 100, - point: state.livePoints.last.asLatLng, + point: mainState.livePoints.last.asLatLng, child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -205,10 +185,11 @@ class _UserPathState extends State { // Create fancy fade-out and blended line (TODO: make distance based. use flutter_map_math) List> segments = []; List colors = []; - if (state.initialPoints.isNotEmpty) { - final allPoints = - state.initialPoints.map((e) => LatLng(e.lat, e.lon)).toList(); - final segmentCount = math.min(100, allPoints.length); + if (mainState.initialPoints.isNotEmpty) { + final allPoints = mainState.initialPoints + .map((e) => LatLng(e.lat, e.lon)) + .toList(); + final int segmentCount = math.min(100, allPoints.length); final pointsPerSegment = (allPoints.length / segmentCount).ceil(); // Split the points into segments and generate colors @@ -259,7 +240,7 @@ class _UserPathState extends State { key: ValueKey('${widget.device}_liveLines'), polylines: [ Polyline( - points: state.livePoints + points: mainState.livePoints .map((e) => LatLng(e.lat, e.lon)) .toList(), strokeWidth: 4.0, diff --git a/pubspec.lock b/pubspec.lock index 7b47a42..9ef4ee5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,38 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" - dio: + dart_polylabel2: dependency: transitive description: - name: dio - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + name: dart_polylabel2 + sha256: "7eeab15ce72894e4bdba6a8765712231fc81be0bd95247de4ad9966abc57adc6" url: "https://pub.dev" source: hosted - version: "5.8.0+1" - dio_cache_interceptor: - dependency: transitive - description: - name: dio_cache_interceptor - sha256: "1346705a2057c265014d7696e3e2318b560bfb00b484dac7f9b01e2ceaebb07d" - url: "https://pub.dev" - source: hosted - version: "3.5.1" - dio_cache_interceptor_hive_store: - dependency: "direct main" - description: - name: dio_cache_interceptor_hive_store - sha256: "449b36541216cb20543228081125ad2995eb9712ec35bd030d85663ea1761895" - url: "https://pub.dev" - source: hosted - version: "3.2.2" - dio_web_adapter: - dependency: transitive - description: - name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" - url: "https://pub.dev" - source: hosted - version: "2.1.1" + version: "1.0.0" duration_picker: dependency: "direct main" description: @@ -234,18 +210,10 @@ packages: dependency: "direct main" description: name: flutter_map - sha256: "2ecb34619a4be19df6f40c2f8dce1591675b4eff7a6857bd8f533706977385da" + sha256: "391e7dc95cc3f5190748210a69d4cfeb5d8f84dcdfa9c3235d0a9d7742ccb3f8" url: "https://pub.dev" source: hosted - version: "7.0.2" - flutter_map_cache: - dependency: "direct main" - description: - name: flutter_map_cache - sha256: "5b30c9b0d36315a22f4ee070737104a6017e7ff990e8addc8128ba81786e03ef" - url: "https://pub.dev" - source: hosted - version: "1.5.2" + version: "8.2.2" flutter_map_compass: dependency: "direct main" description: @@ -292,10 +260,10 @@ packages: dependency: "direct main" description: name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.6.0" http_parser: dependency: transitive description: @@ -512,14 +480,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - polylabel: - dependency: transitive - description: - name: polylabel - sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b" - url: "https://pub.dev" - source: hosted - version: "1.0.1" posix: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 42238be..b3b7a2c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,7 +35,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 - flutter_map: ^7.0.2 + flutter_map: ^8.2.0 http: ^1.2.1 flutter_dotenv: ^5.1.0 shared_preferences: ^2.2.2 @@ -50,8 +50,6 @@ dependencies: fast_immutable_collections: ^10.1.2 flutter_map_compass: ^1.0.1 # flutter_map_location_marker: ^8.0.8 - flutter_map_cache: ^1.5.0 - dio_cache_interceptor_hive_store: ^3.2.2 flutter_map_math: ^0.1.7 intl: ^0.19.0 get_it: ^7.6.7 diff --git a/test/widget_test.dart b/test/widget_test.dart index 4225efc..e37b4f1 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,30 +1,34 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. +import 'dart:io'; -import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; - +import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:ot_viewer_app/main.dart'; +import 'package:ot_viewer_app/refresh_cubit.dart'; +import 'package:ot_viewer_app/settings_page.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + setUpAll(() async { + TestWidgetsFlutterBinding.ensureInitialized(); + final tempDir = await Directory.systemTemp.createTemp('ot_viewer_test'); + HydratedBloc.storage = await HydratedStorage.build( + storageDirectory: tempDir, + ); + }); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); + testWidgets('App shell renders with map and settings tabs', + (WidgetTester tester) async { + await tester.pumpWidget( + MultiBlocProvider( + providers: [ + BlocProvider(create: (context) => SettingsCubit()), + BlocProvider(create: (context) => RefreshCubit()), + ], + child: const MyApp(), + ), + ); - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + expect(find.text('Map'), findsOneWidget); + expect(find.text('Settings'), findsOneWidget); }); }