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,56 +42,50 @@ class _MapPageState extends State<MapPage> {
refreshCubit.stream.listen((_) => cubit.reconnect());
return cubit;
},
child: FutureBuilder<String>(
future: getPath(),
builder: (something, tempPath) =>
BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, state) {
if (tempPath.data == null) {
return const Center(child: Text('Loading Map...'));
}
return Stack(children: [
FlutterMap(
child: BlocBuilder<SettingsCubit, SettingsState>(
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<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,