diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index bd2397ce3b..9a5a6d267d 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -75,6 +75,7 @@ "backup_album_selection_page_albums_device": "Albums on device ({})", "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude", "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.", + "backup_album_selection_page_search_hint": "Filter albums", "backup_album_selection_page_select_albums": "Select albums", "backup_album_selection_page_selection_info": "Selection Info", "backup_album_selection_page_total_assets": "Total unique assets", diff --git a/mobile/lib/pages/backup/backup_album_selection.page.dart b/mobile/lib/pages/backup/backup_album_selection.page.dart index 0869e75e9f..67b24ece68 100644 --- a/mobile/lib/pages/backup/backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/backup_album_selection.page.dart @@ -4,6 +4,7 @@ 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/album/album.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; @@ -24,6 +25,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { useAppSettingsState(AppSettingsEnum.syncAlbums); final isDarkTheme = context.isDarkTheme; final albums = ref.watch(backupProvider).availableAlbums; + final searchQuery = useState(''); + final formFocus = useFocusNode(); useEffect( () { @@ -42,16 +45,22 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { ); } + var filteredAlbums = albums.where((album) { + return album.name + .toLowerCase() + .contains(searchQuery.value.toLowerCase()); + }).toList(); + return SliverPadding( padding: const EdgeInsets.symmetric(vertical: 12.0), sliver: SliverList( delegate: SliverChildBuilderDelegate( ((context, index) { return AlbumInfoListTile( - album: albums[index], + album: filteredAlbums[index], ); }), - childCount: albums.length, + childCount: filteredAlbums.length, ), ), ); @@ -66,6 +75,12 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { ); } + var filteredAlbums = albums.where((album) { + return album.name + .toLowerCase() + .contains(searchQuery.value.toLowerCase()); + }).toList(); + return SliverPadding( padding: const EdgeInsets.all(12.0), sliver: SliverGrid.builder( @@ -74,10 +89,10 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { mainAxisSpacing: 12, crossAxisSpacing: 12, ), - itemCount: albums.length, + itemCount: filteredAlbums.length, itemBuilder: ((context, index) { return AlbumInfoCard( - album: albums[index], + album: filteredAlbums[index], ); }), ), @@ -282,6 +297,53 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { ], ), ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(8), + child: TextField( + focusNode: formFocus, + onChanged: (value) => searchQuery.value = value, + onTapOutside: (_) => formFocus.unfocus(), + 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: 'backup_album_selection_page_search_hint'.tr(), + ), + ), + ), + ), SliverLayoutBuilder( builder: (context, constraints) { if (constraints.crossAxisExtent > 600) {