0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-11 02:23:09 -05:00
immich/mobile/lib/widgets/common/dropdown_search_menu.dart
Gagan Yadav 19f2f888ee
fix(mobile): improve timezone picker (#15615)
- Fix missing timezones

- Remove the UTC prefix from timezone display text to align with web app

- Remove unnecessary layout builder

- Created a custom `DropdownSearchMenu` widget

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-01-25 13:36:49 -06:00

169 lines
5.8 KiB
Dart

import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
class DropdownSearchMenu<T> extends HookWidget {
const DropdownSearchMenu({
super.key,
required this.dropdownMenuEntries,
this.initialSelection,
this.onSelected,
this.trailingIcon,
this.hintText,
this.label,
this.textStyle,
this.menuConstraints,
});
final List<DropdownMenuEntry<T>> dropdownMenuEntries;
final T? initialSelection;
final ValueChanged<T>? onSelected;
final Widget? trailingIcon;
final String? hintText;
final Widget? label;
final TextStyle? textStyle;
final BoxConstraints? menuConstraints;
@override
Widget build(BuildContext context) {
final selectedItem = useState<DropdownMenuEntry<T>?>(
dropdownMenuEntries
.firstWhereOrNull((item) => item.value == initialSelection),
);
final showTimeZoneDropdown = useState<bool>(false);
final effectiveConstraints = menuConstraints ??
const BoxConstraints(
minWidth: 280,
maxWidth: 280,
minHeight: 0,
maxHeight: 280,
);
final inputDecoration = InputDecoration(
contentPadding: const EdgeInsets.fromLTRB(12, 4, 12, 4),
border: const OutlineInputBorder(),
suffixIcon: trailingIcon,
label: label,
hintText: hintText,
).applyDefaults(context.themeData.inputDecorationTheme);
if (!showTimeZoneDropdown.value) {
return ConstrainedBox(
constraints: effectiveConstraints,
child: GestureDetector(
onTap: () => showTimeZoneDropdown.value = true,
child: InputDecorator(
decoration: inputDecoration,
child: selectedItem.value != null
? Text(
selectedItem.value!.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: textStyle,
)
: null,
),
),
);
}
return ConstrainedBox(
constraints: effectiveConstraints,
child: Autocomplete<DropdownMenuEntry<T>>(
displayStringForOption: (option) => option.label,
optionsBuilder: (textEditingValue) {
return dropdownMenuEntries.where(
(item) => item.label
.toLowerCase()
.trim()
.contains(textEditingValue.text.toLowerCase().trim()),
);
},
onSelected: (option) {
selectedItem.value = option;
showTimeZoneDropdown.value = false;
onSelected?.call(option.value);
},
fieldViewBuilder: (context, textEditingController, focusNode, _) {
return TextField(
autofocus: true,
focusNode: focusNode,
controller: textEditingController,
decoration: inputDecoration.copyWith(
hintText: "edit_date_time_dialog_search_timezone".tr(),
),
maxLines: 1,
style: context.textTheme.bodyMedium,
expands: false,
onTapOutside: (event) {
showTimeZoneDropdown.value = false;
focusNode.unfocus();
},
onSubmitted: (_) {
showTimeZoneDropdown.value = false;
},
);
},
optionsViewBuilder: (context, onSelected, options) {
// This widget is a copy of the default implementation.
// We have only changed the `constraints` parameter.
return Align(
alignment: Alignment.topLeft,
child: ConstrainedBox(
constraints: effectiveConstraints,
child: Material(
elevation: 4.0,
child: ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final option = options.elementAt(index);
return InkWell(
onTap: () => onSelected(option),
child: Builder(
builder: (BuildContext context) {
final bool highlight =
AutocompleteHighlightedOption.of(context) ==
index;
if (highlight) {
SchedulerBinding.instance.addPostFrameCallback(
(Duration timeStamp) {
Scrollable.ensureVisible(
context,
alignment: 0.5,
);
},
debugLabel: 'AutocompleteOptions.ensureVisible',
);
}
return Container(
color: highlight
? Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.12)
: null,
padding: const EdgeInsets.all(16.0),
child: Text(
option.label,
style: textStyle,
),
);
},
),
);
},
),
),
),
);
},
),
);
}
}