Skip to content

Commit

Permalink
Implemented search result page (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
alextran1502 authored Mar 2, 2022
1 parent bd34be9 commit 5990a28
Show file tree
Hide file tree
Showing 16 changed files with 467 additions and 17 deletions.
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

0 comments on commit 5990a28

Please sign in to comment.