feat(map): migrate to flutter_map 8.2 with OSM-compliant tiles

This commit is contained in:
2026-02-17 16:41:02 +01:00
parent f709d72f50
commit 4b9b9a2327
5 changed files with 88 additions and 143 deletions

View File

@ -34,12 +34,14 @@ Future<void> main() async {
create: (BuildContext context) => GetIt.I.get<RefreshCubit>(),
),
],
child: MyApp(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(

View File

@ -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<String> getPath() async {
final cacheDirectory = await getTemporaryDirectory();
return cacheDirectory.path;
}
class MapPage extends StatefulWidget {
const MapPage({super.key});
@ -55,14 +42,8 @@ class _MapPageState extends State<MapPage> {
refreshCubit.stream.listen((_) => cubit.reconnect());
return cubit;
},
child: FutureBuilder<String>(
future: getPath(),
builder: (something, tempPath) =>
BlocBuilder<SettingsCubit, SettingsState>(
child: BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, state) {
if (tempPath.data == null) {
return const Center(child: Text('Loading Map...'));
}
return Stack(children: [
FlutterMap(
mapController: _mapController,
@ -72,12 +53,13 @@ class _MapPageState extends State<MapPage> {
),
children: [
TileLayer(
urlTemplate:
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
tileProvider: CachedTileProvider(
maxStale: const Duration(days: 30),
store: HiveCacheStore(tempPath.data!,
hiveBoxName: 'HiveCacheStore')),
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)),
@ -105,7 +87,6 @@ class _MapPageState extends State<MapPage> {
]);
},
),
),
);
}
}
@ -140,7 +121,6 @@ class _UserPathState extends State<UserPath> {
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<UserPath> {
.add(UserPathLoginDataChanged(widget.settings));
print("rebuild");
final _istate = state as MainUserPathState;
final mainState = state as MainUserPathState;
// make markers
final List<Marker> 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<UserPath> {
// Create fancy fade-out and blended line (TODO: make distance based. use flutter_map_math)
List<List<LatLng>> segments = [];
List<Color> 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<UserPath> {
key: ValueKey('${widget.device}_liveLines'),
polylines: [
Polyline(
points: state.livePoints
points: mainState.livePoints
.map((e) => LatLng(e.lat, e.lon))
.toList(),
strokeWidth: 4.0,

View File

@ -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:

View File

@ -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

View File

@ -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);
});
}