123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- import 'package:collection/collection.dart';
- import 'package:easy_localization/easy_localization.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter_hooks/flutter_hooks.dart';
- import 'package:immich_mobile/extensions/build_context_extensions.dart';
- import 'package:immich_mobile/extensions/duration_extensions.dart';
- import 'package:timezone/timezone.dart' as tz;
- import 'package:timezone/timezone.dart';
- Future<String?> showDateTimePicker({
- required BuildContext context,
- DateTime? initialDateTime,
- String? initialTZ,
- Duration? initialTZOffset,
- }) {
- return showDialog<String?>(
- context: context,
- builder: (context) => _DateTimePicker(
- initialDateTime: initialDateTime,
- initialTZ: initialTZ,
- initialTZOffset: initialTZOffset,
- ),
- );
- }
- String _getFormattedOffset(int offsetInMilli, tz.Location location) {
- return "${location.name} (UTC${Duration(milliseconds: offsetInMilli).formatAsOffset()})";
- }
- class _DateTimePicker extends HookWidget {
- final DateTime? initialDateTime;
- final String? initialTZ;
- final Duration? initialTZOffset;
- const _DateTimePicker({
- this.initialDateTime,
- this.initialTZ,
- this.initialTZOffset,
- });
- _TimeZoneOffset _getInitiationLocation() {
- if (initialTZ != null) {
- try {
- return _TimeZoneOffset.fromLocation(
- tz.timeZoneDatabase.get(initialTZ!),
- );
- } on LocationNotFoundException {
- // no-op
- }
- }
- Duration? tzOffset = initialTZOffset ?? initialDateTime?.timeZoneOffset;
- if (tzOffset != null) {
- final offsetInMilli = tzOffset.inMilliseconds;
- // get all locations with matching offset
- final locations = tz.timeZoneDatabase.locations.values.where(
- (location) => location.currentTimeZone.offset == offsetInMilli,
- );
- // Prefer locations with abbreviation first
- final location = locations.firstWhereOrNull(
- (e) => !e.currentTimeZone.abbreviation.contains("0"),
- ) ??
- locations.firstOrNull;
- if (location != null) {
- return _TimeZoneOffset.fromLocation(location);
- }
- }
- return _TimeZoneOffset.fromLocation(tz.getLocation("UTC"));
- }
- // returns a list of location<name> along with it's offset in duration
- List<_TimeZoneOffset> getAllTimeZones() {
- return tz.timeZoneDatabase.locations.values
- .where((l) => !l.currentTimeZone.abbreviation.contains("0"))
- .map(_TimeZoneOffset.fromLocation)
- .sorted()
- .toList();
- }
- @override
- Widget build(BuildContext context) {
- final date = useState<DateTime>(initialDateTime ?? DateTime.now());
- final tzOffset = useState<_TimeZoneOffset>(_getInitiationLocation());
- final timeZones = useMemoized(() => getAllTimeZones(), const []);
- void pickDate() async {
- final newDate = await showDatePicker(
- context: context,
- initialDate: date.value,
- firstDate: DateTime(1800),
- lastDate: DateTime.now(),
- );
- if (newDate == null) {
- return;
- }
- final newTime = await showTimePicker(
- context: context,
- initialTime: TimeOfDay.fromDateTime(date.value),
- );
- if (newTime == null) {
- return;
- }
- date.value = newDate.copyWith(hour: newTime.hour, minute: newTime.minute);
- }
- void popWithDateTime() {
- final formattedDateTime =
- DateFormat("yyyy-MM-dd'T'HH:mm:ss").format(date.value);
- final dtWithOffset = formattedDateTime +
- Duration(milliseconds: tzOffset.value.offsetInMilliseconds)
- .formatAsOffset();
- context.pop(dtWithOffset);
- }
- return AlertDialog(
- contentPadding: const EdgeInsets.all(30),
- alignment: Alignment.center,
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Text(
- "edit_date_time_dialog_date_time",
- textAlign: TextAlign.center,
- ).tr(),
- TextButton.icon(
- onPressed: pickDate,
- icon: Text(
- DateFormat("dd-MM-yyyy hh:mm a").format(date.value),
- style: context.textTheme.bodyLarge
- ?.copyWith(color: context.primaryColor),
- ),
- label: const Icon(
- Icons.edit_outlined,
- size: 18,
- ),
- ),
- const Text(
- "edit_date_time_dialog_timezone",
- textAlign: TextAlign.center,
- ).tr(),
- DropdownMenu(
- menuHeight: 300,
- width: 280,
- inputDecorationTheme: const InputDecorationTheme(
- border: InputBorder.none,
- contentPadding: EdgeInsets.zero,
- ),
- trailingIcon: Padding(
- padding: const EdgeInsets.only(right: 10),
- child: Icon(
- Icons.arrow_drop_down,
- color: context.primaryColor,
- ),
- ),
- textStyle: context.textTheme.bodyLarge?.copyWith(
- color: context.primaryColor,
- ),
- menuStyle: const MenuStyle(
- fixedSize: MaterialStatePropertyAll(Size.fromWidth(350)),
- alignment: Alignment(-1.25, 0.5),
- ),
- onSelected: (value) => tzOffset.value = value!,
- initialSelection: tzOffset.value,
- dropdownMenuEntries: timeZones
- .map(
- (t) => DropdownMenuEntry<_TimeZoneOffset>(
- value: t,
- label: t.display,
- style: ButtonStyle(
- textStyle: MaterialStatePropertyAll(
- context.textTheme.bodyMedium,
- ),
- ),
- ),
- )
- .toList(),
- ),
- ],
- ),
- 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: popWithDateTime,
- child: Text(
- "action_common_update",
- style: context.textTheme.bodyMedium?.copyWith(
- fontWeight: FontWeight.w600,
- color: context.primaryColor,
- ),
- ).tr(),
- ),
- ],
- );
- }
- }
- class _TimeZoneOffset implements Comparable<_TimeZoneOffset> {
- final String display;
- final Location location;
- const _TimeZoneOffset({
- required this.display,
- required this.location,
- });
- _TimeZoneOffset copyWith({
- String? display,
- Location? location,
- }) {
- return _TimeZoneOffset(
- display: display ?? this.display,
- location: location ?? this.location,
- );
- }
- int get offsetInMilliseconds => location.currentTimeZone.offset;
- _TimeZoneOffset.fromLocation(tz.Location l)
- : display = _getFormattedOffset(l.currentTimeZone.offset, l),
- location = l;
- @override
- int compareTo(_TimeZoneOffset other) {
- return offsetInMilliseconds.compareTo(other.offsetInMilliseconds);
- }
- @override
- String toString() =>
- '_TimeZoneOffset(display: $display, location: $location)';
- @override
- bool operator ==(Object other) {
- if (identical(this, other)) return true;
- return other is _TimeZoneOffset &&
- other.display == display &&
- other.offsetInMilliseconds == offsetInMilliseconds;
- }
- @override
- int get hashCode =>
- display.hashCode ^ offsetInMilliseconds.hashCode ^ location.hashCode;
- }
|