mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	chore(mobile): search field in separate widget (#16977)
* chore(mobile): search field in separate widget * fix: removed unnecessary use of context * chore: minor styling tweak * fix: controller not bound --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
		
							parent
							
								
									8bc80076bb
								
							
						
					
					
						commit
						a651a4bf0e
					
				@ -17,6 +17,7 @@ import 'package:immich_mobile/routing/router.dart';
 | 
				
			|||||||
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
 | 
					import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
 | 
				
			||||||
import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
 | 
					import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
 | 
				
			||||||
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
 | 
					import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/widgets/common/search_field.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@RoutePage()
 | 
					@RoutePage()
 | 
				
			||||||
class AlbumsPage extends HookConsumerWidget {
 | 
					class AlbumsPage extends HookConsumerWidget {
 | 
				
			||||||
@ -115,46 +116,17 @@ class AlbumsPage extends HookConsumerWidget {
 | 
				
			|||||||
                  transform: const GradientRotation(0.5 * pi),
 | 
					                  transform: const GradientRotation(0.5 * pi),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              child: TextField(
 | 
					              child: SearchField(
 | 
				
			||||||
                autofocus: false,
 | 
					                autofocus: false,
 | 
				
			||||||
                decoration: InputDecoration(
 | 
					 | 
				
			||||||
                contentPadding: const EdgeInsets.all(16),
 | 
					                contentPadding: const EdgeInsets.all(16),
 | 
				
			||||||
                  border: OutlineInputBorder(
 | 
					 | 
				
			||||||
                    borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                    borderSide: BorderSide(
 | 
					 | 
				
			||||||
                      color: context.colorScheme.surfaceDim,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                  enabledBorder: OutlineInputBorder(
 | 
					 | 
				
			||||||
                    borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                    borderSide: BorderSide(
 | 
					 | 
				
			||||||
                      color: context.colorScheme.surfaceContainer,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                  disabledBorder: OutlineInputBorder(
 | 
					 | 
				
			||||||
                    borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                    borderSide: BorderSide(
 | 
					 | 
				
			||||||
                      color: context.colorScheme.surfaceDim,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                  focusedBorder: OutlineInputBorder(
 | 
					 | 
				
			||||||
                    borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                    borderSide: BorderSide(
 | 
					 | 
				
			||||||
                      color: context.colorScheme.primary.withAlpha(100),
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                hintText: 'search_albums'.tr(),
 | 
					                hintText: 'search_albums'.tr(),
 | 
				
			||||||
                  hintStyle: context.textTheme.bodyLarge?.copyWith(
 | 
					 | 
				
			||||||
                    color: context.colorScheme.onSurfaceSecondary,
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                prefixIcon: const Icon(Icons.search_rounded),
 | 
					                prefixIcon: const Icon(Icons.search_rounded),
 | 
				
			||||||
                suffixIcon: searchController.text.isNotEmpty
 | 
					                suffixIcon: searchController.text.isNotEmpty
 | 
				
			||||||
                    ? IconButton(
 | 
					                    ? IconButton(
 | 
				
			||||||
                        icon: const Icon(Icons.clear_rounded),
 | 
					                        icon: const Icon(Icons.clear_rounded),
 | 
				
			||||||
                        onPressed: clearSearch,
 | 
					                        onPressed: clearSearch,
 | 
				
			||||||
                      )
 | 
					                      )
 | 
				
			||||||
                      : const SizedBox.shrink(),
 | 
					                    : null,
 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                controller: searchController,
 | 
					                controller: searchController,
 | 
				
			||||||
                onChanged: (_) =>
 | 
					                onChanged: (_) =>
 | 
				
			||||||
                    onSearch(searchController.text, filterMode.value),
 | 
					                    onSearch(searchController.text, filterMode.value),
 | 
				
			||||||
 | 
				
			|||||||
@ -8,13 +8,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			|||||||
import 'package:immich_mobile/constants/enums.dart';
 | 
					import 'package:immich_mobile/constants/enums.dart';
 | 
				
			||||||
import 'package:immich_mobile/entities/asset.entity.dart';
 | 
					import 'package:immich_mobile/entities/asset.entity.dart';
 | 
				
			||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | 
					import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | 
				
			||||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/interfaces/person_api.interface.dart';
 | 
					import 'package:immich_mobile/interfaces/person_api.interface.dart';
 | 
				
			||||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
 | 
					import 'package:immich_mobile/models/search/search_filter.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
 | 
					import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
 | 
					import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/routing/router.dart';
 | 
					import 'package:immich_mobile/routing/router.dart';
 | 
				
			||||||
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
 | 
					import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/widgets/common/search_field.dart';
 | 
				
			||||||
import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart';
 | 
					import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart';
 | 
				
			||||||
import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart';
 | 
					import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart';
 | 
				
			||||||
import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
 | 
					import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
 | 
				
			||||||
@ -640,10 +640,10 @@ class SearchPage extends HookConsumerWidget {
 | 
				
			|||||||
              end: Alignment.bottomRight,
 | 
					              end: Alignment.bottomRight,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          child: TextField(
 | 
					          child: SearchField(
 | 
				
			||||||
 | 
					            hintText: searchHintText.value,
 | 
				
			||||||
            key: const Key('search_text_field'),
 | 
					            key: const Key('search_text_field'),
 | 
				
			||||||
            controller: textSearchController,
 | 
					            controller: textSearchController,
 | 
				
			||||||
            decoration: InputDecoration(
 | 
					 | 
				
			||||||
            contentPadding: prefilter != null
 | 
					            contentPadding: prefilter != null
 | 
				
			||||||
                ? const EdgeInsets.only(left: 24)
 | 
					                ? const EdgeInsets.only(left: 24)
 | 
				
			||||||
                : const EdgeInsets.all(8),
 | 
					                : const EdgeInsets.all(8),
 | 
				
			||||||
@ -653,38 +653,8 @@ class SearchPage extends HookConsumerWidget {
 | 
				
			|||||||
                    getSearchPrefixIcon(),
 | 
					                    getSearchPrefixIcon(),
 | 
				
			||||||
                    color: context.colorScheme.primary,
 | 
					                    color: context.colorScheme.primary,
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
              hintText: searchHintText.value,
 | 
					 | 
				
			||||||
              hintStyle: context.textTheme.bodyLarge?.copyWith(
 | 
					 | 
				
			||||||
                color: context.themeData.colorScheme.onSurfaceSecondary,
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              border: OutlineInputBorder(
 | 
					 | 
				
			||||||
                borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                borderSide: BorderSide(
 | 
					 | 
				
			||||||
                  color: context.colorScheme.surfaceDim,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              enabledBorder: OutlineInputBorder(
 | 
					 | 
				
			||||||
                borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                borderSide: BorderSide(
 | 
					 | 
				
			||||||
                  color: context.colorScheme.surfaceContainer,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              disabledBorder: OutlineInputBorder(
 | 
					 | 
				
			||||||
                borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                borderSide: BorderSide(
 | 
					 | 
				
			||||||
                  color: context.colorScheme.surfaceDim,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              focusedBorder: OutlineInputBorder(
 | 
					 | 
				
			||||||
                borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                borderSide: BorderSide(
 | 
					 | 
				
			||||||
                  color: context.colorScheme.primary.withAlpha(100),
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            onSubmitted: handleTextSubmitted,
 | 
					            onSubmitted: handleTextSubmitted,
 | 
				
			||||||
            focusNode: ref.watch(searchInputFocusProvider),
 | 
					            focusNode: ref.watch(searchInputFocusProvider),
 | 
				
			||||||
            onTapOutside: (_) => ref.read(searchInputFocusProvider).unfocus(),
 | 
					 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										79
									
								
								mobile/lib/widgets/common/search_field.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								mobile/lib/widgets/common/search_field.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/extensions/theme_extensions.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SearchField extends StatelessWidget {
 | 
				
			||||||
 | 
					  const SearchField({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    required this.hintText,
 | 
				
			||||||
 | 
					    this.autofocus = false,
 | 
				
			||||||
 | 
					    this.controller,
 | 
				
			||||||
 | 
					    this.focusNode,
 | 
				
			||||||
 | 
					    this.onChanged,
 | 
				
			||||||
 | 
					    this.onSubmitted,
 | 
				
			||||||
 | 
					    this.onTapOutside,
 | 
				
			||||||
 | 
					    this.contentPadding = const EdgeInsets.only(left: 24),
 | 
				
			||||||
 | 
					    this.prefixIcon,
 | 
				
			||||||
 | 
					    this.suffixIcon,
 | 
				
			||||||
 | 
					    this.filled = false,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final FocusNode? focusNode;
 | 
				
			||||||
 | 
					  final void Function(String)? onChanged;
 | 
				
			||||||
 | 
					  final void Function(String)? onSubmitted;
 | 
				
			||||||
 | 
					  final void Function(PointerDownEvent)? onTapOutside;
 | 
				
			||||||
 | 
					  final TextEditingController? controller;
 | 
				
			||||||
 | 
					  final String hintText;
 | 
				
			||||||
 | 
					  final EdgeInsetsGeometry contentPadding;
 | 
				
			||||||
 | 
					  final Widget? prefixIcon;
 | 
				
			||||||
 | 
					  final Widget? suffixIcon;
 | 
				
			||||||
 | 
					  final bool autofocus;
 | 
				
			||||||
 | 
					  final bool filled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return TextField(
 | 
				
			||||||
 | 
					      controller: controller,
 | 
				
			||||||
 | 
					      autofocus: autofocus,
 | 
				
			||||||
 | 
					      focusNode: focusNode,
 | 
				
			||||||
 | 
					      onChanged: onChanged,
 | 
				
			||||||
 | 
					      onTapOutside: onTapOutside ?? (_) => focusNode?.unfocus(),
 | 
				
			||||||
 | 
					      onSubmitted: onSubmitted,
 | 
				
			||||||
 | 
					      decoration: InputDecoration(
 | 
				
			||||||
 | 
					        contentPadding: contentPadding,
 | 
				
			||||||
 | 
					        filled: filled,
 | 
				
			||||||
 | 
					        fillColor: context.primaryColor.withValues(alpha: 0.1),
 | 
				
			||||||
 | 
					        hintStyle: context.textTheme.bodyLarge?.copyWith(
 | 
				
			||||||
 | 
					          color: context.themeData.colorScheme.onSurfaceSecondary,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        border: OutlineInputBorder(
 | 
				
			||||||
 | 
					          borderRadius: BorderRadius.circular(25),
 | 
				
			||||||
 | 
					          borderSide: BorderSide(
 | 
				
			||||||
 | 
					            color: context.colorScheme.surfaceDim,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        enabledBorder: OutlineInputBorder(
 | 
				
			||||||
 | 
					          borderRadius: BorderRadius.circular(25),
 | 
				
			||||||
 | 
					          borderSide: BorderSide(
 | 
				
			||||||
 | 
					            color: context.colorScheme.surfaceContainer,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        disabledBorder: OutlineInputBorder(
 | 
				
			||||||
 | 
					          borderRadius: BorderRadius.circular(25),
 | 
				
			||||||
 | 
					          borderSide: BorderSide(
 | 
				
			||||||
 | 
					            color: context.colorScheme.surfaceDim,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        focusedBorder: OutlineInputBorder(
 | 
				
			||||||
 | 
					          borderRadius: BorderRadius.circular(25),
 | 
				
			||||||
 | 
					          borderSide: BorderSide(
 | 
				
			||||||
 | 
					            color: context.colorScheme.primary.withAlpha(100),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        prefixIcon: prefixIcon,
 | 
				
			||||||
 | 
					        suffixIcon: suffixIcon,
 | 
				
			||||||
 | 
					        hintText: hintText,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -4,12 +4,12 @@ import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
 | 
					import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
 | 
				
			||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | 
					import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | 
				
			||||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/interfaces/person_api.interface.dart';
 | 
					import 'package:immich_mobile/interfaces/person_api.interface.dart';
 | 
				
			||||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
 | 
					import 'package:immich_mobile/pages/common/large_leading_tile.dart';
 | 
				
			||||||
import 'package:immich_mobile/providers/search/people.provider.dart';
 | 
					import 'package:immich_mobile/providers/search/people.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/services/api.service.dart';
 | 
					import 'package:immich_mobile/services/api.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
 | 
					import 'package:immich_mobile/utils/image_url_builder.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/widgets/common/search_field.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PeoplePicker extends HookConsumerWidget {
 | 
					class PeoplePicker extends HookConsumerWidget {
 | 
				
			||||||
  const PeoplePicker({super.key, required this.onSelect, this.filter});
 | 
					  const PeoplePicker({super.key, required this.onSelect, this.filter});
 | 
				
			||||||
@ -30,49 +30,14 @@ class PeoplePicker extends HookConsumerWidget {
 | 
				
			|||||||
      children: [
 | 
					      children: [
 | 
				
			||||||
        Padding(
 | 
					        Padding(
 | 
				
			||||||
          padding: const EdgeInsets.all(8),
 | 
					          padding: const EdgeInsets.all(8),
 | 
				
			||||||
          child: TextField(
 | 
					          child: SearchField(
 | 
				
			||||||
            focusNode: formFocus,
 | 
					            focusNode: formFocus,
 | 
				
			||||||
            onChanged: (value) => searchQuery.value = value,
 | 
					            onChanged: (value) => searchQuery.value = value,
 | 
				
			||||||
            onTapOutside: (_) => formFocus.unfocus(),
 | 
					            onTapOutside: (_) => formFocus.unfocus(),
 | 
				
			||||||
            decoration: InputDecoration(
 | 
					 | 
				
			||||||
              contentPadding: const EdgeInsets.only(left: 24),
 | 
					 | 
				
			||||||
            filled: true,
 | 
					            filled: true,
 | 
				
			||||||
              fillColor: context.primaryColor.withValues(alpha: 0.1),
 | 
					 | 
				
			||||||
              hintStyle: context.textTheme.bodyLarge?.copyWith(
 | 
					 | 
				
			||||||
                color: context.themeData.colorScheme.onSurfaceSecondary,
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              border: OutlineInputBorder(
 | 
					 | 
				
			||||||
                borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                borderSide: BorderSide(
 | 
					 | 
				
			||||||
                  color: context.colorScheme.surfaceContainerHighest,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              enabledBorder: OutlineInputBorder(
 | 
					 | 
				
			||||||
                borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                borderSide: BorderSide(
 | 
					 | 
				
			||||||
                  color: context.colorScheme.surfaceContainerHighest,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              disabledBorder: OutlineInputBorder(
 | 
					 | 
				
			||||||
                borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                borderSide: BorderSide(
 | 
					 | 
				
			||||||
                  color: context.colorScheme.surfaceContainerHighest,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              focusedBorder: OutlineInputBorder(
 | 
					 | 
				
			||||||
                borderRadius: BorderRadius.circular(25),
 | 
					 | 
				
			||||||
                borderSide: BorderSide(
 | 
					 | 
				
			||||||
                  color: context.colorScheme.primary.withAlpha(150),
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              prefixIcon: Icon(
 | 
					 | 
				
			||||||
                Icons.search_rounded,
 | 
					 | 
				
			||||||
                color: context.colorScheme.primary,
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            hintText: 'search_filter_people_hint'.tr(),
 | 
					            hintText: 'search_filter_people_hint'.tr(),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        Padding(
 | 
					        Padding(
 | 
				
			||||||
          padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 0),
 | 
					          padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 0),
 | 
				
			||||||
          child: Divider(
 | 
					          child: Divider(
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user