feat(map): migrate to flutter_map 8.2 with OSM-compliant tiles
This commit is contained in:
@ -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(
|
||||
|
||||
@ -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,
|
||||
|
||||
56
pubspec.lock
56
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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user