mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	- 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>
		
			
				
	
	
		
			170 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			170 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,
 | 
						|
                            ),
 | 
						|
                          );
 | 
						|
                        },
 | 
						|
                      ),
 | 
						|
                    );
 | 
						|
                  },
 | 
						|
                ),
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
          );
 | 
						|
        },
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |