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

Dev/bottom tab bar #35

Merged
merged 9 commits into from
Feb 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions mobile/lib/modules/home/ui/immich_sliver_appbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ import 'package:immich_mobile/shared/providers/backup.provider.dart';
class ImmichSliverAppBar extends ConsumerWidget {
const ImmichSliverAppBar({
Key? key,
required this.imageGridGroup,
this.onPopBack,
}) : super(key: key);

final List<Widget> imageGridGroup;
final Function? onPopBack;

@override
Expand Down Expand Up @@ -46,7 +44,7 @@ class ImmichSliverAppBar extends ConsumerWidget {
style: GoogleFonts.snowburstOne(
textStyle: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
fontSize: 22,
color: Theme.of(context).primaryColor,
),
),
Expand Down
9 changes: 1 addition & 8 deletions mobile/lib/modules/home/views/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ class HomePage extends HookConsumerWidget {
return null;
}, []);

onPopBackFromBackupPage() {
// ref.read(assetProvider.notifier).getAllAsset();
}

Widget _buildBody() {
if (assetGroupByDateTime.isNotEmpty) {
int? lastMonth;
Expand Down Expand Up @@ -88,10 +84,7 @@ class HomePage extends HookConsumerWidget {
child: null,
),
)
: ImmichSliverAppBar(
imageGridGroup: _imageGridGroup,
onPopBack: onPopBackFromBackupPage,
),
: const ImmichSliverAppBar(),
duration: const Duration(milliseconds: 350),
),
..._imageGridGroup
Expand Down
5 changes: 3 additions & 2 deletions mobile/lib/modules/login/ui/login_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class LoginForm extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final usernameController = useTextEditingController(text: '[email protected]');
final passwordController = useTextEditingController(text: 'password');
final serverEndpointController = useTextEditingController(text: 'http://192.168.1.103:2283');
final serverEndpointController = useTextEditingController(text: 'http://192.168.1.216:2283');

return Center(
child: ConstrainedBox(
Expand Down Expand Up @@ -124,7 +124,8 @@ class LoginButton extends ConsumerWidget {
if (isAuthenicated) {
// Resume backup (if enable) then navigate
ref.watch(backupProvider.notifier).resumeBackup();
AutoRouter.of(context).pushNamed("/home-page");
// AutoRouter.of(context).pushNamed("/home-page");
AutoRouter.of(context).pushNamed("/tab-controller-page");
} else {
ImmichToast.show(
context: context,
Expand Down
Empty file.
131 changes: 131 additions & 0 deletions mobile/lib/modules/search/providers/search_page_state.provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
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';

class SearchPageState {
final String searchTerm;
final bool isSearchEnabled;
final List<String> searchSuggestion;
final List<String> userSuggestedSearchTerms;

SearchPageState({
required this.searchTerm,
required this.isSearchEnabled,
required this.searchSuggestion,
required this.userSuggestedSearchTerms,
});

SearchPageState copyWith({
String? searchTerm,
bool? isSearchEnabled,
List<String>? searchSuggestion,
List<String>? userSuggestedSearchTerms,
}) {
return SearchPageState(
searchTerm: searchTerm ?? this.searchTerm,
isSearchEnabled: isSearchEnabled ?? this.isSearchEnabled,
searchSuggestion: searchSuggestion ?? this.searchSuggestion,
userSuggestedSearchTerms: userSuggestedSearchTerms ?? this.userSuggestedSearchTerms,
);
}

Map<String, dynamic> toMap() {
return {
'searchTerm': searchTerm,
'isSearchEnabled': isSearchEnabled,
'searchSuggestion': searchSuggestion,
'userSuggestedSearchTerms': userSuggestedSearchTerms,
};
}

factory SearchPageState.fromMap(Map<String, dynamic> map) {
return SearchPageState(
searchTerm: map['searchTerm'] ?? '',
isSearchEnabled: map['isSearchEnabled'] ?? false,
searchSuggestion: List<String>.from(map['searchSuggestion']),
userSuggestedSearchTerms: List<String>.from(map['userSuggestedSearchTerms']),
);
}

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

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

@override
String toString() {
return 'SearchPageState(searchTerm: $searchTerm, isSearchEnabled: $isSearchEnabled, searchSuggestion: $searchSuggestion, userSuggestedSearchTerms: $userSuggestedSearchTerms)';
}

@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) &&
listEquals(other.userSuggestedSearchTerms, userSuggestedSearchTerms);
}

@override
int get hashCode {
return searchTerm.hashCode ^
isSearchEnabled.hashCode ^
searchSuggestion.hashCode ^
userSuggestedSearchTerms.hashCode;
}
}

class SearchPageStateNotifier extends StateNotifier<SearchPageState> {
SearchPageStateNotifier()
: super(
SearchPageState(
searchTerm: "",
isSearchEnabled: false,
searchSuggestion: [],
userSuggestedSearchTerms: [],
),
);

final SearchService _searchService = SearchService();

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 = state.userSuggestedSearchTerms;

var newList = searchList.where((e) => e.toLowerCase().contains(searchTerm));

state = state.copyWith(searchSuggestion: [...newList]);

if (searchTerm.isEmpty) {
state = state.copyWith(searchSuggestion: []);
}
}

void getSuggestedSearchTerms() async {
var userSuggestedSearchTerms = await _searchService.getUserSuggestedSearchTerms();

state = state.copyWith(userSuggestedSearchTerms: userSuggestedSearchTerms);
}
}

final searchPageStateProvider = StateNotifierProvider<SearchPageStateNotifier, SearchPageState>((ref) {
return SearchPageStateNotifier();
});
20 changes: 20 additions & 0 deletions mobile/lib/modules/search/services/search.service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'dart:convert';

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

class SearchService {
final NetworkService _networkService = NetworkService();

Future<List<String>?> getUserSuggestedSearchTerms() async {
try {
var res = await _networkService.getRequest(url: "asset/searchTerm");
List<dynamic> decodedData = jsonDecode(res.toString());

return List.from(decodedData);
} catch (e) {
debugPrint("[ERROR] [getUserSuggestedSearchTerms] ${e.toString()}");
return [];
}
}
}
56 changes: 56 additions & 0 deletions mobile/lib/modules/search/ui/search_bar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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).getSuggestedSearchTerms();
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);
}
35 changes: 35 additions & 0 deletions mobile/lib/modules/search/ui/search_suggestion_list.dart
Original file line number Diff line number Diff line change
@@ -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,
),
),
],
),
);
}
}
67 changes: 67 additions & 0 deletions mobile/lib/modules/search/views/search_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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(() {
print("search");
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(),
],
),
),
);
}
}
Loading