mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
feat(mobile): improve explore page and allow metadata search (#2097)
This commit is contained in:
@@ -6,7 +6,7 @@ import 'package:immich_mobile/shared/models/store.dart';
|
||||
class CuratedRow extends StatelessWidget {
|
||||
final List<CuratedContent> content;
|
||||
final double imageSize;
|
||||
|
||||
|
||||
/// Callback with the content and the index when tapped
|
||||
final Function(CuratedContent, int)? onTap;
|
||||
|
||||
@@ -19,7 +19,6 @@ class CuratedRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
// Guard empty [content]
|
||||
if (content.isEmpty) {
|
||||
// Return empty thumbnail
|
||||
|
||||
@@ -22,8 +22,7 @@ class ExploreGrid extends StatelessWidget {
|
||||
width: 100,
|
||||
child: ThumbnailWithInfo(
|
||||
textInfo: '',
|
||||
onTap: () {
|
||||
},
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -42,9 +41,10 @@ class ExploreGrid extends StatelessWidget {
|
||||
return ThumbnailWithInfo(
|
||||
imageUrl: thumbnailRequestUrl,
|
||||
textInfo: content.label,
|
||||
borderRadius: 0,
|
||||
onTap: () {
|
||||
AutoRouter.of(context).push(
|
||||
SearchResultRoute(searchTerm: content.label),
|
||||
SearchResultRoute(searchTerm: 'm:${content.label}'),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -52,5 +52,4 @@ class ExploreGrid extends StatelessWidget {
|
||||
itemCount: curatedContent.length,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,10 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
)
|
||||
: const Icon(Icons.search_rounded),
|
||||
: const Icon(
|
||||
Icons.search_rounded,
|
||||
size: 20,
|
||||
),
|
||||
title: TextField(
|
||||
controller: searchTermController,
|
||||
focusNode: searchFocusNode,
|
||||
@@ -55,6 +58,8 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
hintText: 'search_bar_hint'.tr(),
|
||||
hintStyle: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
enabledBorder: const UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.transparent),
|
||||
|
||||
31
mobile/lib/modules/search/ui/search_result_grid.dart
Normal file
31
mobile/lib/modules/search/ui/search_result_grid.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
|
||||
class SearchResultGrid extends HookConsumerWidget {
|
||||
const SearchResultGrid({super.key, required this.assets});
|
||||
|
||||
final List<Asset> assets;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 4,
|
||||
childAspectRatio: 1,
|
||||
crossAxisSpacing: 4,
|
||||
mainAxisSpacing: 4,
|
||||
),
|
||||
itemCount: assets.length,
|
||||
itemBuilder: (context, index) {
|
||||
final asset = assets[index];
|
||||
return ThumbnailImage(
|
||||
asset: asset,
|
||||
assetList: assets,
|
||||
useGrayBoxPlaceholder: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
||||
@@ -12,6 +13,7 @@ class SearchSuggestionList extends ConsumerWidget {
|
||||
final searchTerm = ref.watch(searchPageStateProvider).searchTerm;
|
||||
final searchSuggestion =
|
||||
ref.watch(searchPageStateProvider).searchSuggestion;
|
||||
var isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Container(
|
||||
color: searchTerm.isEmpty
|
||||
@@ -19,13 +21,38 @@ class SearchSuggestionList extends ConsumerWidget {
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
color: isDarkTheme ? Colors.grey[800] : Colors.grey[100],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'search_suggestion_list_smart_search_hint_1'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
TextSpan(
|
||||
text: 'search_suggestion_list_smart_search_hint_2'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: true,
|
||||
child: ListView.builder(
|
||||
itemBuilder: ((context, index) {
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
onSubmitted(searchSuggestion[index]);
|
||||
onSubmitted("m:${searchSuggestion[index]}");
|
||||
},
|
||||
title: Text(searchSuggestion[index]),
|
||||
);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/utils/capitalize_first_letter.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ThumbnailWithInfo extends StatelessWidget {
|
||||
const ThumbnailWithInfo({
|
||||
ThumbnailWithInfo({
|
||||
Key? key,
|
||||
required this.textInfo,
|
||||
this.imageUrl,
|
||||
this.noImageIcon,
|
||||
this.borderRadius = 10,
|
||||
required this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -15,6 +18,7 @@ class ThumbnailWithInfo extends StatelessWidget {
|
||||
final String? imageUrl;
|
||||
final Function onTap;
|
||||
final IconData? noImageIcon;
|
||||
double borderRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -29,12 +33,12 @@ class ThumbnailWithInfo extends StatelessWidget {
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
||||
),
|
||||
child: imageUrl != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
child: CachedNetworkImage(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
@@ -55,15 +59,32 @@ class ThumbnailWithInfo extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
color: Colors.white,
|
||||
gradient: LinearGradient(
|
||||
begin: FractionalOffset.topCenter,
|
||||
end: FractionalOffset.bottomCenter,
|
||||
colors: [
|
||||
Colors.grey.withOpacity(0.0),
|
||||
textInfo == ''
|
||||
? Colors.black.withOpacity(0.1)
|
||||
: Colors.black.withOpacity(0.5),
|
||||
],
|
||||
stops: const [0.0, 1.0],
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 12,
|
||||
left: 14,
|
||||
child: Text(
|
||||
textInfo,
|
||||
textInfo == '' ? textInfo : textInfo.capitalizeFirstLetter(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user