diff --git a/mobile/lib/modules/search/models/store_model_here.txt b/mobile/lib/modules/search/models/store_model_here.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mobile/lib/modules/search/providers/search_page_state.provider.dart b/mobile/lib/modules/search/providers/search_page_state.provider.dart new file mode 100644 index 0000000000..2782da2af3 --- /dev/null +++ b/mobile/lib/modules/search/providers/search_page_state.provider.dart @@ -0,0 +1,107 @@ +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class SearchPageState { + final String searchTerm; + final bool isSearchEnabled; + final List searchSuggestion; + + SearchPageState({ + required this.searchTerm, + required this.isSearchEnabled, + required this.searchSuggestion, + }); + + SearchPageState copyWith({ + String? searchTerm, + bool? isSearchEnabled, + List? searchSuggestion, + }) { + return SearchPageState( + searchTerm: searchTerm ?? this.searchTerm, + isSearchEnabled: isSearchEnabled ?? this.isSearchEnabled, + searchSuggestion: searchSuggestion ?? this.searchSuggestion, + ); + } + + Map toMap() { + return { + 'searchTerm': searchTerm, + 'isSearchEnabled': isSearchEnabled, + 'searchSuggestion': searchSuggestion, + }; + } + + factory SearchPageState.fromMap(Map map) { + return SearchPageState( + searchTerm: map['searchTerm'] ?? '', + isSearchEnabled: map['isSearchEnabled'] ?? false, + searchSuggestion: List.from(map['searchSuggestion']), + ); + } + + String toJson() => json.encode(toMap()); + + factory SearchPageState.fromJson(String source) => SearchPageState.fromMap(json.decode(source)); + + @override + String toString() => + 'SearchPageState(searchTerm: $searchTerm, isSearchEnabled: $isSearchEnabled, searchSuggestion: $searchSuggestion)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is SearchPageState && + other.searchTerm == searchTerm && + other.isSearchEnabled == isSearchEnabled && + listEquals(other.searchSuggestion, searchSuggestion); + } + + @override + int get hashCode => searchTerm.hashCode ^ isSearchEnabled.hashCode ^ searchSuggestion.hashCode; +} + +class SearchPageStateNotifier extends StateNotifier { + SearchPageStateNotifier() + : super( + SearchPageState( + searchTerm: "", + isSearchEnabled: false, + searchSuggestion: [], + ), + ); + + void enableSearch() { + state = state.copyWith(isSearchEnabled: true); + } + + void disableSearch() { + state = state.copyWith(isSearchEnabled: false); + } + + void setSearchTerm(String value) { + state = state.copyWith(searchTerm: value); + + _getSearchSuggestion(state.searchTerm); + } + + void _getSearchSuggestion(String searchTerm) { + var searchList = ['January', '01 2022', 'feburary', "February", 'home', '3413']; + + var newList = searchList.where((e) => e.toLowerCase().contains(searchTerm)); + + state = state.copyWith(searchSuggestion: [...newList]); + + if (searchTerm.isEmpty) { + state = state.copyWith(searchSuggestion: []); + } + } +} + +final searchPageStateProvider = StateNotifierProvider((ref) { + return SearchPageStateNotifier(); +}); diff --git a/mobile/lib/modules/search/services/store_services_here.txt b/mobile/lib/modules/search/services/store_services_here.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mobile/lib/modules/search/ui/search_bar.dart b/mobile/lib/modules/search/ui/search_bar.dart new file mode 100644 index 0000000000..af3b2fd6ce --- /dev/null +++ b/mobile/lib/modules/search/ui/search_bar.dart @@ -0,0 +1,55 @@ +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'; + +class SearchBar extends HookConsumerWidget with PreferredSizeWidget { + SearchBar({Key? key, required this.searchFocusNode}) : super(key: key); + FocusNode searchFocusNode; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final searchTermController = useTextEditingController(text: ""); + final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled; + + return AppBar( + automaticallyImplyLeading: false, + leading: isSearchEnabled + ? IconButton( + onPressed: () { + searchFocusNode.unfocus(); + ref.watch(searchPageStateProvider.notifier).disableSearch(); + }, + icon: const Icon(Icons.arrow_back_ios_rounded)) + : const Icon(Icons.search_rounded), + title: TextField( + controller: searchTermController, + focusNode: searchFocusNode, + autofocus: false, + onTap: () { + ref.watch(searchPageStateProvider.notifier).enableSearch(); + searchFocusNode.requestFocus(); + }, + onSubmitted: (searchTerm) { + ref.watch(searchPageStateProvider.notifier).disableSearch(); + searchFocusNode.unfocus(); + }, + onChanged: (value) { + ref.watch(searchPageStateProvider.notifier).setSearchTerm(value); + }, + decoration: const InputDecoration( + hintText: 'Search your photos', + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.transparent), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.transparent), + ), + ), + ), + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} diff --git a/mobile/lib/modules/search/ui/search_suggestion_list.dart b/mobile/lib/modules/search/ui/search_suggestion_list.dart new file mode 100644 index 0000000000..b3a73b5fc9 --- /dev/null +++ b/mobile/lib/modules/search/ui/search_suggestion_list.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +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); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final searchTerm = ref.watch(searchPageStateProvider).searchTerm; + final searchSuggestion = ref.watch(searchPageStateProvider).searchSuggestion; + + return Container( + color: searchTerm.isEmpty ? Colors.black.withOpacity(0.5) : Theme.of(context).scaffoldBackgroundColor, + child: CustomScrollView( + slivers: [ + SliverFillRemaining( + hasScrollBody: true, + child: ListView.builder( + itemBuilder: ((context, index) { + return ListTile( + onTap: () { + print("navigate to this search result: ${searchSuggestion[index]} "); + }, + title: Text(searchSuggestion[index]), + ); + }), + itemCount: searchSuggestion.length, + ), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/modules/search/views/search_page.dart b/mobile/lib/modules/search/views/search_page.dart new file mode 100644 index 0000000000..b6b36db1b4 --- /dev/null +++ b/mobile/lib/modules/search/views/search_page.dart @@ -0,0 +1,68 @@ +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'; + +// ignore: must_be_immutable +class SearchPage extends HookConsumerWidget { + SearchPage({Key? key}) : super(key: key); + + late FocusNode searchFocusNode; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled; + + useEffect(() { + searchFocusNode = FocusNode(); + return () { + searchFocusNode.dispose(); + }; + }, []); + + return Scaffold( + appBar: SearchBar(searchFocusNode: searchFocusNode), + body: GestureDetector( + onTap: () { + searchFocusNode.unfocus(); + ref.watch(searchPageStateProvider.notifier).disableSearch(); + }, + child: Stack( + children: [ + ListView( + children: [ + Container( + height: 300, + color: Colors.blue, + ), + Container( + height: 300, + color: Colors.red, + ), + Container( + height: 300, + color: Colors.green, + ), + Container( + height: 300, + color: Colors.blue, + ), + Container( + height: 300, + color: Colors.red, + ), + Container( + height: 300, + color: Colors.green, + ), + ], + ), + isSearchEnabled ? const SearchSuggestionList() : Container(), + ], + ), + ), + ); + } +}