Skip to content

Commit 5990a28

Browse files
authored
Implemented search result page (#37)
1 parent bd34be9 commit 5990a28

16 files changed

+467
-17
lines changed

mobile/lib/modules/home/ui/immich_sliver_appbar.dart

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,8 @@ class ImmichSliverAppBar extends ConsumerWidget {
8383
),
8484
child: const Icon(Icons.backup_rounded)),
8585
tooltip: 'Backup Controller',
86-
onPressed: () async {
87-
var onPop = await AutoRouter.of(context).push(const BackupControllerRoute());
88-
89-
if (onPop == true) {
90-
onPopBack!();
91-
}
86+
onPressed: () {
87+
AutoRouter.of(context).push(const BackupControllerRoute());
9288
},
9389
),
9490
_backupState.backupProgress == BackUpProgressEnum.inProgress
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import 'dart:convert';
2+
3+
import 'package:collection/collection.dart';
4+
import 'package:hooks_riverpod/hooks_riverpod.dart';
5+
6+
import 'package:immich_mobile/modules/search/services/search.service.dart';
7+
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
8+
import 'package:intl/intl.dart';
9+
10+
class SearchresultPageState {
11+
final bool isLoading;
12+
final bool isSuccess;
13+
final bool isError;
14+
final List<ImmichAsset> searchResult;
15+
16+
SearchresultPageState({
17+
required this.isLoading,
18+
required this.isSuccess,
19+
required this.isError,
20+
required this.searchResult,
21+
});
22+
23+
SearchresultPageState copyWith({
24+
bool? isLoading,
25+
bool? isSuccess,
26+
bool? isError,
27+
List<ImmichAsset>? searchResult,
28+
}) {
29+
return SearchresultPageState(
30+
isLoading: isLoading ?? this.isLoading,
31+
isSuccess: isSuccess ?? this.isSuccess,
32+
isError: isError ?? this.isError,
33+
searchResult: searchResult ?? this.searchResult,
34+
);
35+
}
36+
37+
Map<String, dynamic> toMap() {
38+
return {
39+
'isLoading': isLoading,
40+
'isSuccess': isSuccess,
41+
'isError': isError,
42+
'searchResult': searchResult.map((x) => x.toMap()).toList(),
43+
};
44+
}
45+
46+
factory SearchresultPageState.fromMap(Map<String, dynamic> map) {
47+
return SearchresultPageState(
48+
isLoading: map['isLoading'] ?? false,
49+
isSuccess: map['isSuccess'] ?? false,
50+
isError: map['isError'] ?? false,
51+
searchResult: List<ImmichAsset>.from(map['searchResult']?.map((x) => ImmichAsset.fromMap(x))),
52+
);
53+
}
54+
55+
String toJson() => json.encode(toMap());
56+
57+
factory SearchresultPageState.fromJson(String source) => SearchresultPageState.fromMap(json.decode(source));
58+
59+
@override
60+
String toString() {
61+
return 'SearchresultPageState(isLoading: $isLoading, isSuccess: $isSuccess, isError: $isError, searchResult: $searchResult)';
62+
}
63+
64+
@override
65+
bool operator ==(Object other) {
66+
if (identical(this, other)) return true;
67+
final listEquals = const DeepCollectionEquality().equals;
68+
69+
return other is SearchresultPageState &&
70+
other.isLoading == isLoading &&
71+
other.isSuccess == isSuccess &&
72+
other.isError == isError &&
73+
listEquals(other.searchResult, searchResult);
74+
}
75+
76+
@override
77+
int get hashCode {
78+
return isLoading.hashCode ^ isSuccess.hashCode ^ isError.hashCode ^ searchResult.hashCode;
79+
}
80+
}
81+
82+
class SearchResultPageStateNotifier extends StateNotifier<SearchresultPageState> {
83+
SearchResultPageStateNotifier()
84+
: super(SearchresultPageState(searchResult: [], isError: false, isLoading: true, isSuccess: false));
85+
86+
final SearchService _searchService = SearchService();
87+
88+
search(String searchTerm) async {
89+
state = state.copyWith(searchResult: [], isError: false, isLoading: true, isSuccess: false);
90+
91+
List<ImmichAsset>? assets = await _searchService.searchAsset(searchTerm);
92+
93+
if (assets != null) {
94+
state = state.copyWith(searchResult: assets, isError: false, isLoading: false, isSuccess: true);
95+
} else {
96+
state = state.copyWith(searchResult: [], isError: true, isLoading: false, isSuccess: false);
97+
}
98+
}
99+
}
100+
101+
final searchResultPageStateProvider =
102+
StateNotifierProvider<SearchResultPageStateNotifier, SearchresultPageState>((ref) {
103+
return SearchResultPageStateNotifier();
104+
});
105+
106+
final searchResultGroupByDateTimeProvider = StateProvider((ref) {
107+
var assets = ref.watch(searchResultPageStateProvider).searchResult;
108+
109+
assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
110+
return assets.groupListsBy((element) => DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)));
111+
});

mobile/lib/modules/search/services/search.service.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:convert';
22

33
import 'package:flutter/material.dart';
4+
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
45
import 'package:immich_mobile/shared/services/network.service.dart';
56

67
class SearchService {
@@ -17,4 +18,22 @@ class SearchService {
1718
return [];
1819
}
1920
}
21+
22+
Future<List<ImmichAsset>?> searchAsset(String searchTerm) async {
23+
try {
24+
var res = await _networkService.postRequest(
25+
url: "asset/search",
26+
data: {"searchTerm": searchTerm},
27+
);
28+
29+
List<dynamic> decodedData = jsonDecode(res.toString());
30+
31+
List<ImmichAsset> result = List.from(decodedData.map((a) => ImmichAsset.fromMap(a)));
32+
33+
return result;
34+
} catch (e) {
35+
debugPrint("[ERROR] [searchAsset] ${e.toString()}");
36+
return null;
37+
}
38+
}
2039
}

mobile/lib/modules/search/services/store_services_here.txt

Whitespace-only changes.

mobile/lib/modules/search/ui/search_bar.dart

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
44
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
55

66
class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
7-
SearchBar({Key? key, required this.searchFocusNode}) : super(key: key);
8-
FocusNode searchFocusNode;
7+
SearchBar({Key? key, required this.searchFocusNode, required this.onSubmitted}) : super(key: key);
8+
9+
final FocusNode searchFocusNode;
10+
final Function(String) onSubmitted;
911

1012
@override
1113
Widget build(BuildContext context, WidgetRef ref) {
@@ -19,6 +21,7 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
1921
onPressed: () {
2022
searchFocusNode.unfocus();
2123
ref.watch(searchPageStateProvider.notifier).disableSearch();
24+
searchTermController.clear();
2225
},
2326
icon: const Icon(Icons.arrow_back_ios_rounded))
2427
: const Icon(Icons.search_rounded),
@@ -27,13 +30,17 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
2730
focusNode: searchFocusNode,
2831
autofocus: false,
2932
onTap: () {
33+
searchTermController.clear();
3034
ref.watch(searchPageStateProvider.notifier).getSuggestedSearchTerms();
3135
ref.watch(searchPageStateProvider.notifier).enableSearch();
36+
ref.watch(searchPageStateProvider.notifier).setSearchTerm("");
37+
3238
searchFocusNode.requestFocus();
3339
},
3440
onSubmitted: (searchTerm) {
35-
ref.watch(searchPageStateProvider.notifier).disableSearch();
36-
searchFocusNode.unfocus();
41+
onSubmitted(searchTerm);
42+
searchTermController.clear();
43+
ref.watch(searchPageStateProvider.notifier).setSearchTerm("");
3744
},
3845
onChanged: (value) {
3946
ref.watch(searchPageStateProvider.notifier).setSearchTerm(value);

mobile/lib/modules/search/ui/search_suggestion_list.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
33
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
44

55
class SearchSuggestionList extends ConsumerWidget {
6-
const SearchSuggestionList({Key? key}) : super(key: key);
6+
const SearchSuggestionList({Key? key, required this.onSubmitted}) : super(key: key);
77

8+
final Function(String) onSubmitted;
89
@override
910
Widget build(BuildContext context, WidgetRef ref) {
1011
final searchTerm = ref.watch(searchPageStateProvider).searchTerm;
@@ -20,7 +21,7 @@ class SearchSuggestionList extends ConsumerWidget {
2021
itemBuilder: ((context, index) {
2122
return ListTile(
2223
onTap: () {
23-
print("navigate to this search result: ${searchSuggestion[index]} ");
24+
onSubmitted(searchSuggestion[index]);
2425
},
2526
title: Text(searchSuggestion[index]),
2627
);

mobile/lib/modules/search/views/search_page.dart

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import 'package:auto_route/auto_route.dart';
12
import 'package:flutter/material.dart';
23
import 'package:flutter_hooks/flutter_hooks.dart';
34
import 'package:hooks_riverpod/hooks_riverpod.dart';
45
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
56
import 'package:immich_mobile/modules/search/ui/search_bar.dart';
67
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
8+
import 'package:immich_mobile/routing/router.dart';
79

810
// ignore: must_be_immutable
911
class SearchPage extends HookConsumerWidget {
@@ -16,13 +18,22 @@ class SearchPage extends HookConsumerWidget {
1618
final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled;
1719

1820
useEffect(() {
19-
print("search");
2021
searchFocusNode = FocusNode();
2122
return () => searchFocusNode.dispose();
2223
}, []);
2324

25+
_onSearchSubmitted(String searchTerm) async {
26+
searchFocusNode.unfocus();
27+
ref.watch(searchPageStateProvider.notifier).disableSearch();
28+
29+
AutoRouter.of(context).push(SearchResultRoute(searchTerm: searchTerm));
30+
}
31+
2432
return Scaffold(
25-
appBar: SearchBar(searchFocusNode: searchFocusNode),
33+
appBar: SearchBar(
34+
searchFocusNode: searchFocusNode,
35+
onSubmitted: _onSearchSubmitted,
36+
),
2637
body: GestureDetector(
2738
onTap: () {
2839
searchFocusNode.unfocus();
@@ -58,7 +69,7 @@ class SearchPage extends HookConsumerWidget {
5869
),
5970
],
6071
),
61-
isSearchEnabled ? const SearchSuggestionList() : Container(),
72+
isSearchEnabled ? SearchSuggestionList(onSubmitted: _onSearchSubmitted) : Container(),
6273
],
6374
),
6475
),

0 commit comments

Comments
 (0)