mirror of
https://github.com/immich-app/immich.git
synced 2025-02-14 19:35:28 +02:00
fix(mobile): search page issues (#15804)
* fix: don't repeat search * fix: show snackbar for no result * fix: do not search on empty filter * chore: syling
This commit is contained in:
parent
4fccc09fc1
commit
098bab7c9b
@ -1,4 +1,6 @@
|
||||
{
|
||||
"search_no_result": "No results found, try a different search term or combination",
|
||||
"search_no_more_result": "No more results",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "Cancel",
|
||||
"action_common_clear": "Clear",
|
||||
|
@ -1,3 +1,6 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
|
||||
abstract interface class IPersonApiRepository {
|
||||
Future<List<Person>> getAll();
|
||||
Future<Person> update(String id, {String? name});
|
||||
@ -6,10 +9,10 @@ abstract interface class IPersonApiRepository {
|
||||
class Person {
|
||||
Person({
|
||||
required this.id,
|
||||
this.birthDate,
|
||||
required this.isHidden,
|
||||
required this.name,
|
||||
required this.thumbnailPath,
|
||||
this.birthDate,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
@ -19,4 +22,80 @@ class Person {
|
||||
final String name;
|
||||
final String thumbnailPath;
|
||||
final DateTime? updatedAt;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Person(id: $id, birthDate: $birthDate, isHidden: $isHidden, name: $name, thumbnailPath: $thumbnailPath, updatedAt: $updatedAt)';
|
||||
}
|
||||
|
||||
Person copyWith({
|
||||
String? id,
|
||||
DateTime? birthDate,
|
||||
bool? isHidden,
|
||||
String? name,
|
||||
String? thumbnailPath,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return Person(
|
||||
id: id ?? this.id,
|
||||
birthDate: birthDate ?? this.birthDate,
|
||||
isHidden: isHidden ?? this.isHidden,
|
||||
name: name ?? this.name,
|
||||
thumbnailPath: thumbnailPath ?? this.thumbnailPath,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return <String, dynamic>{
|
||||
'id': id,
|
||||
'birthDate': birthDate?.millisecondsSinceEpoch,
|
||||
'isHidden': isHidden,
|
||||
'name': name,
|
||||
'thumbnailPath': thumbnailPath,
|
||||
'updatedAt': updatedAt?.millisecondsSinceEpoch,
|
||||
};
|
||||
}
|
||||
|
||||
factory Person.fromMap(Map<String, dynamic> map) {
|
||||
return Person(
|
||||
id: map['id'] as String,
|
||||
birthDate: map['birthDate'] != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(map['birthDate'] as int)
|
||||
: null,
|
||||
isHidden: map['isHidden'] as bool,
|
||||
name: map['name'] as String,
|
||||
thumbnailPath: map['thumbnailPath'] as String,
|
||||
updatedAt: map['updatedAt'] != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(map['updatedAt'] as int)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Person.fromJson(String source) =>
|
||||
Person.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||
|
||||
@override
|
||||
bool operator ==(covariant Person other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.id == id &&
|
||||
other.birthDate == birthDate &&
|
||||
other.isHidden == isHidden &&
|
||||
other.name == name &&
|
||||
other.thumbnailPath == thumbnailPath &&
|
||||
other.updatedAt == updatedAt;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^
|
||||
birthDate.hashCode ^
|
||||
isHidden.hashCode ^
|
||||
name.hashCode ^
|
||||
thumbnailPath.hashCode ^
|
||||
updatedAt.hashCode;
|
||||
}
|
||||
}
|
||||
|
@ -255,6 +255,23 @@ class SearchFilter {
|
||||
required this.mediaType,
|
||||
});
|
||||
|
||||
bool get isEmpty {
|
||||
return (context == null || (context != null && context!.isEmpty)) &&
|
||||
(filename == null || (filename!.isEmpty)) &&
|
||||
people.isEmpty &&
|
||||
location.country == null &&
|
||||
location.state == null &&
|
||||
location.city == null &&
|
||||
camera.make == null &&
|
||||
camera.model == null &&
|
||||
date.takenBefore == null &&
|
||||
date.takenAfter == null &&
|
||||
display.isNotInAlbum == false &&
|
||||
display.isArchive == false &&
|
||||
display.isFavorite == false &&
|
||||
mediaType == AssetType.other;
|
||||
}
|
||||
|
||||
SearchFilter copyWith({
|
||||
String? context,
|
||||
String? filename,
|
||||
|
@ -49,7 +49,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
|
||||
final previousFilter = useState(filter.value);
|
||||
final previousFilter = useState<SearchFilter?>(null);
|
||||
|
||||
final peopleCurrentFilterWidget = useState<Widget?>(null);
|
||||
final dateRangeCurrentFilterWidget = useState<Widget?>(null);
|
||||
@ -60,19 +60,55 @@ class SearchPage extends HookConsumerWidget {
|
||||
|
||||
final isSearching = useState(false);
|
||||
|
||||
SnackBar searchInfoSnackBar(String message) {
|
||||
return SnackBar(
|
||||
content: Text(
|
||||
message,
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
showCloseIcon: true,
|
||||
behavior: SnackBarBehavior.fixed,
|
||||
closeIconColor: context.colorScheme.onSurface,
|
||||
);
|
||||
}
|
||||
|
||||
search() async {
|
||||
if (prefilter == null && filter.value == previousFilter.value) return;
|
||||
if (filter.value.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefilter == null && filter.value == previousFilter.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
isSearching.value = true;
|
||||
ref.watch(paginatedSearchProvider.notifier).clear();
|
||||
await ref.watch(paginatedSearchProvider.notifier).search(filter.value);
|
||||
final hasResult = await ref
|
||||
.watch(paginatedSearchProvider.notifier)
|
||||
.search(filter.value);
|
||||
|
||||
if (!hasResult) {
|
||||
context.showSnackBar(
|
||||
searchInfoSnackBar('search_no_result'.tr()),
|
||||
);
|
||||
}
|
||||
|
||||
previousFilter.value = filter.value;
|
||||
isSearching.value = false;
|
||||
}
|
||||
|
||||
loadMoreSearchResult() async {
|
||||
isSearching.value = true;
|
||||
await ref.watch(paginatedSearchProvider.notifier).search(filter.value);
|
||||
final hasResult = await ref
|
||||
.watch(paginatedSearchProvider.notifier)
|
||||
.search(filter.value);
|
||||
|
||||
if (!hasResult) {
|
||||
context.showSnackBar(
|
||||
searchInfoSnackBar('search_no_more_result'.tr()),
|
||||
);
|
||||
}
|
||||
|
||||
isSearching.value = false;
|
||||
}
|
||||
|
||||
@ -596,10 +632,15 @@ class SearchPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
SearchResultGrid(
|
||||
onScrollEnd: loadMoreSearchResult,
|
||||
isSearching: isSearching.value,
|
||||
),
|
||||
if (isSearching.value)
|
||||
const Expanded(
|
||||
child: Center(child: CircularProgressIndicator.adaptive()),
|
||||
)
|
||||
else
|
||||
SearchResultGrid(
|
||||
onScrollEnd: loadMoreSearchResult,
|
||||
isSearching: isSearching.value,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -19,17 +19,23 @@ class PaginatedSearchNotifier extends StateNotifier<SearchResult> {
|
||||
PaginatedSearchNotifier(this._searchService)
|
||||
: super(SearchResult(assets: [], nextPage: 1));
|
||||
|
||||
search(SearchFilter filter) async {
|
||||
if (state.nextPage == null) return;
|
||||
Future<bool> search(SearchFilter filter) async {
|
||||
if (state.nextPage == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final result = await _searchService.search(filter, state.nextPage!);
|
||||
|
||||
if (result == null) return;
|
||||
if (result == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state = SearchResult(
|
||||
assets: [...state.assets, ...result.assets],
|
||||
nextPage: result.nextPage,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
@ -101,7 +101,7 @@ class SearchService {
|
||||
);
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
if (response == null || response.assets.items.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class PeoplePicker extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final formFocus = useFocusNode();
|
||||
final imageSize = 75.0;
|
||||
final imageSize = 60.0;
|
||||
final searchQuery = useState('');
|
||||
final people = ref.watch(getAllPeopleProvider);
|
||||
final headers = ApiService.getRequestHeaders();
|
||||
|
Loading…
x
Reference in New Issue
Block a user