From bd2deda50c4481903a04d51a5dc7042fdeea095a Mon Sep 17 00:00:00 2001
From: Yaros <thedj.launchpadder.dmx512@gmail.com>
Date: Fri, 18 Apr 2025 18:19:51 +0200
Subject: [PATCH] feat(mobile): search on places page (#17679)

* feat: search on places page

* chore: use searchfield on people page
---
 i18n/en.json                                  |  1 +
 .../people/people_collection.page.dart        | 43 ++-----------
 .../places/places_collection.page.dart        | 62 ++++++++++++++-----
 3 files changed, 51 insertions(+), 55 deletions(-)

diff --git a/i18n/en.json b/i18n/en.json
index 3b52c2019e..c4b4746871 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -996,6 +996,7 @@
   "filetype": "Filetype",
   "filter": "Filter",
   "filter_people": "Filter people",
+  "filter_places": "Filter places",
   "find_them_fast": "Find them fast by name with search",
   "fix_incorrect_match": "Fix incorrect match",
   "folder": "Folder",
diff --git a/mobile/lib/pages/library/people/people_collection.page.dart b/mobile/lib/pages/library/people/people_collection.page.dart
index 5f587c0c76..27daf0a887 100644
--- a/mobile/lib/pages/library/people/people_collection.page.dart
+++ b/mobile/lib/pages/library/people/people_collection.page.dart
@@ -4,11 +4,11 @@ import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/extensions/build_context_extensions.dart';
-import 'package:immich_mobile/extensions/theme_extensions.dart';
 import 'package:immich_mobile/providers/search/people.provider.dart';
 import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/services/api.service.dart';
 import 'package:immich_mobile/utils/image_url_builder.dart';
+import 'package:immich_mobile/widgets/common/search_field.dart';
 import 'package:immich_mobile/widgets/search/person_name_edit_form.dart';
 
 @RoutePage()
@@ -42,47 +42,12 @@ class PeopleCollectionPage extends HookConsumerWidget {
           appBar: AppBar(
             automaticallyImplyLeading: search.value == null,
             title: search.value != null
-                ? TextField(
+                ? SearchField(
                     focusNode: formFocus,
                     onTapOutside: (_) => formFocus.unfocus(),
                     onChanged: (value) => search.value = value,
-                    decoration: InputDecoration(
-                      contentPadding: const EdgeInsets.only(left: 24),
-                      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: 'filter_people'.tr(),
-                    ),
+                    filled: true,
+                    hintText: 'filter_people'.tr(),
                     autofocus: true,
                   )
                 : Text('people'.tr()),
diff --git a/mobile/lib/pages/library/places/places_collection.page.dart b/mobile/lib/pages/library/places/places_collection.page.dart
index d4da3ff37e..f9a2d4292c 100644
--- a/mobile/lib/pages/library/places/places_collection.page.dart
+++ b/mobile/lib/pages/library/places/places_collection.page.dart
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
 import 'package:cached_network_image/cached_network_image.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/domain/models/store.model.dart';
 import 'package:immich_mobile/entities/asset.entity.dart';
@@ -12,6 +13,7 @@ import 'package:immich_mobile/pages/common/large_leading_tile.dart';
 import 'package:immich_mobile/providers/search/search_page_state.provider.dart';
 import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/services/api.service.dart';
+import 'package:immich_mobile/widgets/common/search_field.dart';
 import 'package:immich_mobile/widgets/map/map_thumbnail.dart';
 import 'package:maplibre_gl/maplibre_gl.dart';
 
@@ -21,34 +23,62 @@ class PlacesCollectionPage extends HookConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     final places = ref.watch(getAllPlacesProvider);
+    final formFocus = useFocusNode();
+    final ValueNotifier<String?> search = useState(null);
 
     return Scaffold(
       appBar: AppBar(
-        title: Text('places'.tr()),
+        automaticallyImplyLeading: search.value == null,
+        title: search.value != null
+            ? SearchField(
+                autofocus: true,
+                filled: true,
+                focusNode: formFocus,
+                onChanged: (value) => search.value = value,
+                onTapOutside: (_) => formFocus.unfocus(),
+                hintText: 'filter_places'.tr(),
+              )
+            : Text('places'.tr()),
+        actions: [
+          IconButton(
+            icon: Icon(search.value != null ? Icons.close : Icons.search),
+            onPressed: () {
+              search.value = search.value == null ? '' : null;
+            },
+          ),
+        ],
       ),
       body: ListView(
         shrinkWrap: true,
         children: [
-          Padding(
-            padding: const EdgeInsets.all(16.0),
-            child: SizedBox(
-              height: 200,
-              width: context.width,
-              child: MapThumbnail(
-                onTap: (_, __) => context.pushRoute(const MapRoute()),
-                zoom: 8,
-                centre: const LatLng(
-                  21.44950,
-                  -157.91959,
+          if (search.value == null)
+            Padding(
+              padding: const EdgeInsets.all(16.0),
+              child: SizedBox(
+                height: 200,
+                width: context.width,
+                child: MapThumbnail(
+                  onTap: (_, __) => context.pushRoute(const MapRoute()),
+                  zoom: 8,
+                  centre: const LatLng(
+                    21.44950,
+                    -157.91959,
+                  ),
+                  showAttribution: false,
+                  themeMode:
+                      context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
                 ),
-                showAttribution: false,
-                themeMode:
-                    context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
               ),
             ),
-          ),
           places.when(
             data: (places) {
+              if (search.value != null) {
+                places = places.where((place) {
+                  return place.label
+                      .toLowerCase()
+                      .contains(search.value!.toLowerCase());
+                }).toList();
+              }
               return ListView.builder(
                 shrinkWrap: true,
                 physics: const NeverScrollableScrollPhysics(),