mirror of
https://github.com/immich-app/immich.git
synced 2024-12-24 10:37:28 +02:00
af32183728
* refactor: autoroutex pushroute * refactor: autoroutex popRoute * refactor: autoroutex navigate and replace * chore: add doc comments for extension methods * refactor: Add LoggerMixin and refactor Album activities to use mixin * refactor: Activity page * chore: activity user from user constructor * fix: update current asset after build method * refactor: tests with similar structure as lib * chore: remove avoid-declaring-call-method rule from dcm analysis * test: fix proper expect order * test: activity_statistics_provider_test * test: activity_provider_test * test: use proper matchers * test: activity_text_field_test & dismissible_activity_test added * test: add http mock to return transparent image * test: download isar core libs during test * test: add widget tags to widget test cases * test: activity_tile_test * build: currentAlbumProvider to generator * movie add / remove like to activity input tile * test: activities_page_test.dart * chore: better error logs * chore: dismissibleactivity as statelesswidget --------- Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
258 lines
8.3 KiB
Dart
258 lines
8.3 KiB
Dart
import 'package:auto_route/auto_route.dart';
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:flutter_map/plugin_api.dart';
|
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
import 'package:immich_mobile/extensions/string_extensions.dart';
|
|
import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart';
|
|
import 'package:immich_mobile/routing/router.dart';
|
|
import 'package:latlong2/latlong.dart';
|
|
|
|
Future<LatLng?> showLocationPicker({
|
|
required BuildContext context,
|
|
LatLng? initialLatLng,
|
|
}) {
|
|
return showDialog<LatLng?>(
|
|
context: context,
|
|
useRootNavigator: false,
|
|
builder: (ctx) => _LocationPicker(
|
|
initialLatLng: initialLatLng,
|
|
),
|
|
);
|
|
}
|
|
|
|
enum _LocationPickerMode { map, manual }
|
|
|
|
bool _validateLat(String value) {
|
|
final l = double.tryParse(value);
|
|
return l != null && l > -90 && l < 90;
|
|
}
|
|
|
|
bool _validateLong(String value) {
|
|
final l = double.tryParse(value);
|
|
return l != null && l > -180 && l < 180;
|
|
}
|
|
|
|
class _LocationPicker extends HookWidget {
|
|
final LatLng? initialLatLng;
|
|
|
|
const _LocationPicker({
|
|
this.initialLatLng,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final latitude = useState(initialLatLng?.latitude ?? 0.0);
|
|
final longitude = useState(initialLatLng?.longitude ?? 0.0);
|
|
final latlng = LatLng(latitude.value, longitude.value);
|
|
final pickerMode = useState(_LocationPickerMode.map);
|
|
final latitudeController = useTextEditingController();
|
|
final isValidLatitude = useState(true);
|
|
final latitiudeFocusNode = useFocusNode();
|
|
final longitudeController = useTextEditingController();
|
|
final longitudeFocusNode = useFocusNode();
|
|
final isValidLongitude = useState(true);
|
|
|
|
void validateInputs() {
|
|
isValidLatitude.value = _validateLat(latitudeController.text);
|
|
if (isValidLatitude.value) {
|
|
latitude.value = latitudeController.text.toDouble();
|
|
}
|
|
isValidLongitude.value = _validateLong(longitudeController.text);
|
|
if (isValidLongitude.value) {
|
|
longitude.value = longitudeController.text.toDouble();
|
|
}
|
|
}
|
|
|
|
void validateAndPop() {
|
|
if (pickerMode.value == _LocationPickerMode.manual) {
|
|
validateInputs();
|
|
}
|
|
if (isValidLatitude.value && isValidLongitude.value) {
|
|
return context.pop(latlng);
|
|
}
|
|
}
|
|
|
|
List<Widget> buildMapPickerMode() {
|
|
return [
|
|
TextButton.icon(
|
|
icon: Text(
|
|
"${latitude.value.toStringAsFixed(4)}, ${longitude.value.toStringAsFixed(4)}",
|
|
),
|
|
label: const Icon(Icons.edit_outlined, size: 16),
|
|
onPressed: () {
|
|
latitudeController.text = latitude.value.toStringAsFixed(4);
|
|
longitudeController.text = longitude.value.toStringAsFixed(4);
|
|
pickerMode.value = _LocationPickerMode.manual;
|
|
},
|
|
),
|
|
const SizedBox(
|
|
height: 12,
|
|
),
|
|
MapThumbnail(
|
|
coords: latlng,
|
|
height: 200,
|
|
width: 200,
|
|
zoom: 6,
|
|
showAttribution: false,
|
|
onTap: (p0, p1) async {
|
|
final newLatLng = await context.pushRoute<LatLng?>(
|
|
MapLocationPickerRoute(initialLatLng: latlng),
|
|
);
|
|
if (newLatLng != null) {
|
|
latitude.value = newLatLng.latitude;
|
|
longitude.value = newLatLng.longitude;
|
|
}
|
|
},
|
|
markers: [
|
|
Marker(
|
|
anchorPos: AnchorPos.align(AnchorAlign.top),
|
|
point: LatLng(
|
|
latitude.value,
|
|
longitude.value,
|
|
),
|
|
builder: (ctx) => const Image(
|
|
image: AssetImage('assets/location-pin.png'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
];
|
|
}
|
|
|
|
List<Widget> buildManualPickerMode() {
|
|
return [
|
|
TextButton.icon(
|
|
icon: const Text("location_picker_choose_on_map").tr(),
|
|
label: const Icon(Icons.map_outlined, size: 16),
|
|
onPressed: () {
|
|
validateInputs();
|
|
if (isValidLatitude.value && isValidLongitude.value) {
|
|
pickerMode.value = _LocationPickerMode.map;
|
|
}
|
|
},
|
|
),
|
|
const SizedBox(
|
|
height: 12,
|
|
),
|
|
TextField(
|
|
controller: latitudeController,
|
|
focusNode: latitiudeFocusNode,
|
|
textInputAction: TextInputAction.done,
|
|
autofocus: false,
|
|
decoration: InputDecoration(
|
|
labelText: 'location_picker_latitude'.tr(),
|
|
labelStyle: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: context.primaryColor,
|
|
),
|
|
floatingLabelBehavior: FloatingLabelBehavior.auto,
|
|
border: const OutlineInputBorder(),
|
|
hintText: 'location_picker_latitude_hint'.tr(),
|
|
hintStyle: const TextStyle(
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 14,
|
|
),
|
|
errorText: isValidLatitude.value
|
|
? null
|
|
: "location_picker_latitude_error".tr(),
|
|
),
|
|
onEditingComplete: () {
|
|
isValidLatitude.value = _validateLat(latitudeController.text);
|
|
if (isValidLatitude.value) {
|
|
latitude.value = latitudeController.text.toDouble();
|
|
longitudeFocusNode.requestFocus();
|
|
}
|
|
},
|
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
inputFormatters: [LengthLimitingTextInputFormatter(8)],
|
|
onTapOutside: (_) => latitiudeFocusNode.unfocus(),
|
|
),
|
|
const SizedBox(
|
|
height: 24,
|
|
),
|
|
TextField(
|
|
controller: longitudeController,
|
|
focusNode: longitudeFocusNode,
|
|
textInputAction: TextInputAction.done,
|
|
autofocus: false,
|
|
decoration: InputDecoration(
|
|
labelText: 'location_picker_longitude'.tr(),
|
|
labelStyle: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: context.primaryColor,
|
|
),
|
|
floatingLabelBehavior: FloatingLabelBehavior.auto,
|
|
border: const OutlineInputBorder(),
|
|
hintText: 'location_picker_longitude_hint'.tr(),
|
|
hintStyle: const TextStyle(
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 14,
|
|
),
|
|
errorText: isValidLongitude.value
|
|
? null
|
|
: "location_picker_longitude_error".tr(),
|
|
),
|
|
onEditingComplete: () {
|
|
isValidLongitude.value = _validateLong(longitudeController.text);
|
|
if (isValidLongitude.value) {
|
|
longitude.value = longitudeController.text.toDouble();
|
|
longitudeFocusNode.unfocus();
|
|
}
|
|
},
|
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
inputFormatters: [LengthLimitingTextInputFormatter(8)],
|
|
onTapOutside: (_) => longitudeFocusNode.unfocus(),
|
|
),
|
|
];
|
|
}
|
|
|
|
return AlertDialog(
|
|
contentPadding: const EdgeInsets.all(30),
|
|
alignment: Alignment.center,
|
|
content: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Text(
|
|
"edit_location_dialog_title",
|
|
textAlign: TextAlign.center,
|
|
).tr(),
|
|
const SizedBox(
|
|
height: 12,
|
|
),
|
|
if (pickerMode.value == _LocationPickerMode.manual)
|
|
...buildManualPickerMode(),
|
|
if (pickerMode.value == _LocationPickerMode.map)
|
|
...buildMapPickerMode(),
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => context.pop(),
|
|
child: Text(
|
|
"action_common_cancel",
|
|
style: context.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
color: context.colorScheme.error,
|
|
),
|
|
).tr(),
|
|
),
|
|
TextButton(
|
|
onPressed: validateAndPop,
|
|
child: Text(
|
|
"action_common_update",
|
|
style: context.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
color: context.primaryColor,
|
|
),
|
|
).tr(),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|