Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented search result page #37

Merged
merged 10 commits into from
Mar 2, 2022
8 changes: 2 additions & 6 deletions mobile/lib/modules/home/ui/immich_sliver_appbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,8 @@ class ImmichSliverAppBar extends ConsumerWidget {
),
child: const Icon(Icons.backup_rounded)),
tooltip: 'Backup Controller',
onPressed: () async {
var onPop = await AutoRouter.of(context).push(const BackupControllerRoute());

if (onPop == true) {
onPopBack!();
}
onPressed: () {
AutoRouter.of(context).push(const BackupControllerRoute());
},
),
_backupState.backupProgress == BackUpProgressEnum.inProgress
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import 'dart:convert';

import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import 'package:immich_mobile/modules/search/services/search.service.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:intl/intl.dart';

class SearchresultPageState {
final bool isLoading;
final bool isSuccess;
final bool isError;
final List<ImmichAsset> searchResult;

SearchresultPageState({
required this.isLoading,
required this.isSuccess,
required this.isError,
required this.searchResult,
});

SearchresultPageState copyWith({
bool? isLoading,
bool? isSuccess,
bool? isError,
List<ImmichAsset>? searchResult,
}) {
return SearchresultPageState(
isLoading: isLoading ?? this.isLoading,
isSuccess: isSuccess ?? this.isSuccess,
isError: isError ?? this.isError,
searchResult: searchResult ?? this.searchResult,
);
}

Map<String, dynamic> toMap() {
return {
'isLoading': isLoading,
'isSuccess': isSuccess,
'isError': isError,
'searchResult': searchResult.map((x) => x.toMap()).toList(),
};
}

factory SearchresultPageState.fromMap(Map<String, dynamic> map) {
return SearchresultPageState(
isLoading: map['isLoading'] ?? false,
isSuccess: map['isSuccess'] ?? false,
isError: map['isError'] ?? false,
searchResult: List<ImmichAsset>.from(map['searchResult']?.map((x) => ImmichAsset.fromMap(x))),
);
}

String toJson() => json.encode(toMap());

factory SearchresultPageState.fromJson(String source) => SearchresultPageState.fromMap(json.decode(source));

@override
String toString() {
return 'SearchresultPageState(isLoading: $isLoading, isSuccess: $isSuccess, isError: $isError, searchResult: $searchResult)';
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
final listEquals = const DeepCollectionEquality().equals;

return other is SearchresultPageState &&
other.isLoading == isLoading &&
other.isSuccess == isSuccess &&
other.isError == isError &&
listEquals(other.searchResult, searchResult);
}

@override
int get hashCode {
return isLoading.hashCode ^ isSuccess.hashCode ^ isError.hashCode ^ searchResult.hashCode;
}
}

class SearchResultPageStateNotifier extends StateNotifier<SearchresultPageState> {
SearchResultPageStateNotifier()
: super(SearchresultPageState(searchResult: [], isError: false, isLoading: true, isSuccess: false));

final SearchService _searchService = SearchService();

search(String searchTerm) async {
state = state.copyWith(searchResult: [], isError: false, isLoading: true, isSuccess: false);

List<ImmichAsset>? assets = await _searchService.searchAsset(searchTerm);

if (assets != null) {
state = state.copyWith(searchResult: assets, isError: false, isLoading: false, isSuccess: true);
} else {
state = state.copyWith(searchResult: [], isError: true, isLoading: false, isSuccess: false);
}
}
}

final searchResultPageStateProvider =
StateNotifierProvider<SearchResultPageStateNotifier, SearchresultPageState>((ref) {
return SearchResultPageStateNotifier();
});

final searchResultGroupByDateTimeProvider = StateProvider((ref) {
var assets = ref.watch(searchResultPageStateProvider).searchResult;

assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
return assets.groupListsBy((element) => DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)));
});
19 changes: 19 additions & 0 deletions mobile/lib/modules/search/services/search.service.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:immich_mobile/shared/services/network.service.dart';

class SearchService {
Expand All @@ -17,4 +18,22 @@ class SearchService {
return [];
}
}

Future<List<ImmichAsset>?> searchAsset(String searchTerm) async {
try {
var res = await _networkService.postRequest(
url: "asset/search",
data: {"searchTerm": searchTerm},
);

List<dynamic> decodedData = jsonDecode(res.toString());

List<ImmichAsset> result = List.from(decodedData.map((a) => ImmichAsset.fromMap(a)));

return result;
} catch (e) {
debugPrint("[ERROR] [searchAsset] ${e.toString()}");
return null;
}
}
}
Empty file.
15 changes: 11 additions & 4 deletions mobile/lib/modules/search/ui/search_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';

class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
SearchBar({Key? key, required this.searchFocusNode}) : super(key: key);
FocusNode searchFocusNode;
SearchBar({Key? key, required this.searchFocusNode, required this.onSubmitted}) : super(key: key);

final FocusNode searchFocusNode;
final Function(String) onSubmitted;

@override
Widget build(BuildContext context, WidgetRef ref) {
Expand All @@ -19,6 +21,7 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
onPressed: () {
searchFocusNode.unfocus();
ref.watch(searchPageStateProvider.notifier).disableSearch();
searchTermController.clear();
},
icon: const Icon(Icons.arrow_back_ios_rounded))
: const Icon(Icons.search_rounded),
Expand All @@ -27,13 +30,17 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
focusNode: searchFocusNode,
autofocus: false,
onTap: () {
searchTermController.clear();
ref.watch(searchPageStateProvider.notifier).getSuggestedSearchTerms();
ref.watch(searchPageStateProvider.notifier).enableSearch();
ref.watch(searchPageStateProvider.notifier).setSearchTerm("");

searchFocusNode.requestFocus();
},
onSubmitted: (searchTerm) {
ref.watch(searchPageStateProvider.notifier).disableSearch();
searchFocusNode.unfocus();
onSubmitted(searchTerm);
searchTermController.clear();
ref.watch(searchPageStateProvider.notifier).setSearchTerm("");
},
onChanged: (value) {
ref.watch(searchPageStateProvider.notifier).setSearchTerm(value);
Expand Down
5 changes: 3 additions & 2 deletions mobile/lib/modules/search/ui/search_suggestion_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';

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

final Function(String) onSubmitted;
@override
Widget build(BuildContext context, WidgetRef ref) {
final searchTerm = ref.watch(searchPageStateProvider).searchTerm;
Expand All @@ -20,7 +21,7 @@ class SearchSuggestionList extends ConsumerWidget {
itemBuilder: ((context, index) {
return ListTile(
onTap: () {
print("navigate to this search result: ${searchSuggestion[index]} ");
onSubmitted(searchSuggestion[index]);
},
title: Text(searchSuggestion[index]),
);
Expand Down
17 changes: 14 additions & 3 deletions mobile/lib/modules/search/views/search_page.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
import 'package:immich_mobile/modules/search/ui/search_bar.dart';
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
import 'package:immich_mobile/routing/router.dart';

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

useEffect(() {
print("search");
searchFocusNode = FocusNode();
return () => searchFocusNode.dispose();
}, []);

_onSearchSubmitted(String searchTerm) async {
searchFocusNode.unfocus();
ref.watch(searchPageStateProvider.notifier).disableSearch();

AutoRouter.of(context).push(SearchResultRoute(searchTerm: searchTerm));
}

return Scaffold(
appBar: SearchBar(searchFocusNode: searchFocusNode),
appBar: SearchBar(
searchFocusNode: searchFocusNode,
onSubmitted: _onSearchSubmitted,
),
body: GestureDetector(
onTap: () {
searchFocusNode.unfocus();
Expand Down Expand Up @@ -58,7 +69,7 @@ class SearchPage extends HookConsumerWidget {
),
],
),
isSearchEnabled ? const SearchSuggestionList() : Container(),
isSearchEnabled ? SearchSuggestionList(onSubmitted: _onSearchSubmitted) : Container(),
],
),
),
Expand Down
Loading