Skip to content

Commit

Permalink
refactor: location picker
Browse files Browse the repository at this point in the history
  • Loading branch information
shenlong-tanwen committed Jan 7, 2024
1 parent 491fff6 commit 206116c
Show file tree
Hide file tree
Showing 14 changed files with 420 additions and 265 deletions.
Binary file modified mobile/assets/location-pin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions mobile/lib/extensions/maplibrecontroller_extensions.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:math';

import 'package:flutter/services.dart';
import 'package:immich_mobile/modules/map/models/map_marker.dart';
import 'package:immich_mobile/modules/map/utils/map_utils.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
Expand Down Expand Up @@ -37,6 +38,24 @@ extension MapMarkers on MaplibreMapController {
);
}

Future<Symbol?> addMarkerAtLatLng(LatLng centre) async {
// no marker is displayed if asset-path is incorrect
try {
final ByteData bytes = await rootBundle.load("assets/location-pin.png");
await addImage("mapMarker", bytes.buffer.asUint8List());
return addSymbol(
SymbolOptions(
geometry: centre,
iconImage: "mapMarker",
iconSize: 0.15,
iconAnchor: "bottom",
),
);
} finally {
// no-op
}
}

Future<LatLngBounds> getBoundsFromPoint(
Point<double> point,
double distance,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 3 additions & 6 deletions mobile/lib/modules/map/models/map_marker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,9 @@ class MapMarker {
);
}

static MapMarker? fromDto(MapMarkerResponseDto dto) {
if (dto.lat < -90 || dto.lat > 90 || dto.lon < -180 || dto.lon > 180) {
return null;
}
return MapMarker(latLng: LatLng(dto.lat, dto.lon), assetRemoteId: dto.id);
}
MapMarker.fromDto(MapMarkerResponseDto dto)
: latLng = LatLng(dto.lat, dto.lon),
assetRemoteId = dto.id;

@override
String toString() =>
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/modules/map/providers/map_state.provider.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 16 additions & 14 deletions mobile/lib/modules/map/services/map.service.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:immich_mobile/mixins/error_logger.mixin.dart';
import 'package:immich_mobile/modules/map/models/map_marker.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:logging/logging.dart';

class MapSerivce {
class MapSerivce with ErrorLoggerMixin {
final ApiService _apiService;
final _log = Logger("MapService");
@override
final logger = Logger("MapService");

MapSerivce(this._apiService);

Expand All @@ -14,18 +16,18 @@ class MapSerivce {
DateTime? fileCreatedAfter,
DateTime? fileCreatedBefore,
}) async {
try {
final markers = await _apiService.assetApi.getMapMarkers(
isFavorite: isFavorite,
isArchived: withArchived,
fileCreatedAfter: fileCreatedAfter,
fileCreatedBefore: fileCreatedBefore,
);
return logError(
() async {
final markers = await _apiService.assetApi.getMapMarkers(
isFavorite: isFavorite,
isArchived: withArchived,
fileCreatedAfter: fileCreatedAfter,
fileCreatedBefore: fileCreatedBefore,
);

return markers?.map(MapMarker.fromDto).nonNulls ?? [];
} catch (error, stack) {
_log.severe("Cannot get map markers ${error.toString()}", error, stack);
return [];
}
return markers?.map(MapMarker.fromDto) ?? [];
},
defaultValue: [],
);
}
}
159 changes: 159 additions & 0 deletions mobile/lib/modules/map/views/map_location_picker_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
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';

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

return MapThemeOveride(
mapBuilder: (style) => Builder(
builder: (ctx) => Scaffold(
backgroundColor: ctx.themeData.cardColor,
appBar: _AppBar(onClose: onClose),
extendBodyBehindAppBar: true,
body: Column(
children: [
style.widgetWhen(
onData: (style) => Expanded(
child: 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),
),
),
),
),
_BottomBar(
selectedLatLng: selectedLatLng,
onUseLocation: () => onClose(selectedLatLng.value),
),
],
),
),
),
);
}
}

class _AppBar extends StatelessWidget implements PreferredSizeWidget {
final Function() onClose;

const _AppBar({required this.onClose});

@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: MediaQuery.paddingOf(context).top + 25),
child: Expanded(
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;

const _BottomBar({
required this.selectedLatLng,
required this.onUseLocation,
});

@override
Widget build(BuildContext context) {
return SizedBox(
height: 150,
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)}",
),
),
],
),
Center(
child: ElevatedButton(
onPressed: onUseLocation,
child: const Text("map_location_picker_page_use_location").tr(),
),
),
],
),
);
}
}
61 changes: 0 additions & 61 deletions mobile/lib/modules/map/widgets/map_location_picker.dart

This file was deleted.

2 changes: 2 additions & 0 deletions mobile/lib/modules/map/widgets/map_theme_override.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/map/providers/map_state.provider.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';

/// Overrides the theme below the widget tree to use the theme data based on the
/// map settings instead of the one from the app settings
class MapThemeOveride extends StatefulHookConsumerWidget {
final ThemeMode? themeMode;
final Widget Function(AsyncValue<String> style) mapBuilder;
Expand Down
Loading

0 comments on commit 206116c

Please sign in to comment.