1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-29 11:24:37 +02:00
immich/mobile/lib/modules/map/views/map_location_picker_page.dart
shenlong e0864768c2
refactor(mobile): map heatmap color and location picker (#6553)
* refactor(mobile): make location picker scaffold primary

* chore(mobile): update map heatmap colors

* style(mobile): map bottomsheet - only use borders on top

* fix(mobile): location picker show buttons above navigation bar

* fix: crash on iOS due to heatmap invalid color format

* disable rotate

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-02-08 03:07:43 +00:00

183 lines
5.8 KiB
Dart

import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart';
import 'package:immich_mobile/modules/map/widgets/map_theme_override.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:immich_mobile/modules/map/utils/map_utils.dart';
@RoutePage<LatLng?>()
class MapLocationPickerPage extends HookConsumerWidget {
final LatLng initialLatLng;
const MapLocationPickerPage({
super.key,
this.initialLatLng = const LatLng(0, 0),
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedLatLng = useValueNotifier<LatLng>(initialLatLng);
final controller = useRef<MaplibreMapController?>(null);
final marker = useRef<Symbol?>(null);
Future<void> onStyleLoaded() async {
marker.value = await controller.value?.addMarkerAtLatLng(initialLatLng);
}
Future<void> onMapClick(Point<num> point, LatLng centre) async {
selectedLatLng.value = centre;
controller.value?.animateCamera(CameraUpdate.newLatLng(centre));
if (marker.value != null) {
await controller.value
?.updateSymbol(marker.value!, SymbolOptions(geometry: centre));
}
}
void onClose([LatLng? selected]) {
context.popRoute(selected);
}
Future<void> getCurrentLocation() async {
var (currentLocation, _) =
await MapUtils.checkPermAndGetLocation(context);
if (currentLocation == null) {
return;
}
var currentLatLng =
LatLng(currentLocation.latitude, currentLocation.longitude);
selectedLatLng.value = currentLatLng;
controller.value?.animateCamera(CameraUpdate.newLatLng(currentLatLng));
}
return MapThemeOveride(
mapBuilder: (style) => Builder(
builder: (ctx) => Scaffold(
backgroundColor: ctx.themeData.cardColor,
appBar: _AppBar(onClose: onClose),
extendBodyBehindAppBar: true,
primary: true,
body: style.widgetWhen(
onData: (style) => Container(
clipBehavior: Clip.antiAliasWithSaveLayer,
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(40),
bottomRight: Radius.circular(40),
),
),
child: MaplibreMap(
initialCameraPosition:
CameraPosition(target: initialLatLng, zoom: 12),
styleString: style,
onMapCreated: (mapController) =>
controller.value = mapController,
onStyleLoadedCallback: onStyleLoaded,
onMapClick: onMapClick,
dragEnabled: false,
tiltGesturesEnabled: false,
myLocationEnabled: false,
attributionButtonMargins: const Point(20, 15),
),
),
),
bottomNavigationBar: _BottomBar(
selectedLatLng: selectedLatLng,
onUseLocation: () => onClose(selectedLatLng.value),
onGetCurrentLocation: getCurrentLocation,
),
),
),
);
}
}
class _AppBar extends StatelessWidget implements PreferredSizeWidget {
final Function() onClose;
const _AppBar({required this.onClose});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 25),
child: Align(
alignment: Alignment.centerLeft,
child: ElevatedButton(
onPressed: onClose,
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
),
child: const Icon(Icons.arrow_back_ios_new_rounded),
),
),
);
}
@override
Size get preferredSize => const Size.fromHeight(100);
}
class _BottomBar extends StatelessWidget {
final ValueNotifier<LatLng> selectedLatLng;
final Function() onUseLocation;
final Function() onGetCurrentLocation;
const _BottomBar({
required this.selectedLatLng,
required this.onUseLocation,
required this.onGetCurrentLocation,
});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 150 + context.padding.bottom,
child: Padding(
padding: EdgeInsets.only(bottom: context.padding.bottom),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.public, size: 18),
const SizedBox(width: 15),
ValueListenableBuilder(
valueListenable: selectedLatLng,
builder: (_, value, __) => Text(
"${value.latitude.toStringAsFixed(4)}, ${value.longitude.toStringAsFixed(4)}",
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: onUseLocation,
child:
const Text("map_location_picker_page_use_location").tr(),
),
ElevatedButton(
onPressed: onGetCurrentLocation,
child: const Icon(Icons.my_location),
),
],
),
],
),
),
);
}
}