refactor(mobile): pages (#9246)

* refactor(mobile): pages

* refactor

* album pages

* pages

* pages

* use *.page.dart

* representation

* put back module
This commit is contained in:
Alex
2024-05-05 13:14:49 -05:00
committed by GitHub
parent 398d99a052
commit 090592e5ae
73 changed files with 166 additions and 165 deletions
@@ -1,32 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/providers/search/all_motion_photos.provider.dart';
@RoutePage()
class AllMotionPhotosPage extends HookConsumerWidget {
const AllMotionPhotosPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final motionPhotos = ref.watch(allMotionPhotosProvider);
return Scaffold(
appBar: AppBar(
title: const Text('motion_photos_page_title').tr(),
leading: IconButton(
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),
body: motionPhotos.widgetWhen(
onData: (assets) => ImmichAssetGrid(
assets: assets,
),
),
);
}
}
@@ -1,38 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/models/search/search_curated_content.model.dart';
import 'package:immich_mobile/providers/search/people.provider.dart';
import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
@RoutePage()
class AllPeoplePage extends HookConsumerWidget {
const AllPeoplePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final curatedPeople = ref.watch(getAllPeopleProvider);
return Scaffold(
appBar: AppBar(
title: const Text(
'all_people_page_title',
).tr(),
leading: IconButton(
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),
body: curatedPeople.widgetWhen(
onData: (people) => ExploreGrid(
isPeople: true,
curatedContent: people
.map((e) => SearchCuratedContent(label: e.name, id: e.id))
.toList(),
),
),
);
}
}
@@ -1,36 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/models/search/search_curated_content.model.dart';
import 'package:immich_mobile/providers/search/search_page_state.provider.dart';
import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
@RoutePage()
class AllPlacesPage extends HookConsumerWidget {
const AllPlacesPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
AsyncValue<List<SearchCuratedContent>> places =
ref.watch(getAllPlacesProvider);
return Scaffold(
appBar: AppBar(
title: const Text(
'curated_location_page_title',
).tr(),
leading: IconButton(
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),
body: places.widgetWhen(
onData: (data) => ExploreGrid(
curatedContent: data,
),
),
);
}
}
@@ -1,25 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/search/all_video_assets.provider.dart';
import 'package:immich_mobile/shared/ui/asset_grid/multiselect_grid.dart';
@RoutePage()
class AllVideosPage extends HookConsumerWidget {
const AllVideosPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('all_videos_page_title').tr(),
leading: IconButton(
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),
body: MultiselectGrid(renderListProvider: allVideoAssetsProvider),
);
}
}
@@ -1,140 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/search/people.provider.dart';
import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/shared/ui/asset_grid/multiselect_grid.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
@RoutePage()
class PersonResultPage extends HookConsumerWidget {
final String personId;
final String personName;
const PersonResultPage({
super.key,
required this.personId,
required this.personName,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final name = useState(personName);
showEditNameDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return PersonNameEditForm(
personId: personId,
personName: name.value,
);
},
).then((result) {
if (result != null && result.success) {
name.value = result.updatedName;
}
});
}
void buildBottomSheet() {
showModalBottomSheet(
backgroundColor: context.scaffoldBackgroundColor,
isScrollControlled: false,
context: context,
useSafeArea: true,
builder: (context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.edit_outlined),
title: const Text(
'search_page_person_edit_name',
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
onTap: showEditNameDialog,
),
],
),
);
},
);
}
buildTitleBlock() {
return GestureDetector(
onTap: showEditNameDialog,
child: name.value.isEmpty
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'search_page_person_add_name_title',
style: context.textTheme.titleMedium?.copyWith(
color: context.primaryColor,
),
).tr(),
Text(
'search_page_person_add_name_subtitle',
style: context.textTheme.labelLarge,
).tr(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name.value,
style: context.textTheme.titleLarge,
),
],
),
);
}
return Scaffold(
appBar: AppBar(
title: Text(name.value),
leading: IconButton(
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
actions: [
IconButton(
onPressed: buildBottomSheet,
icon: const Icon(Icons.more_vert_rounded),
),
],
),
body: MultiselectGrid(
renderListProvider: personAssetsProvider(personId),
topWidget: Padding(
padding: const EdgeInsets.only(left: 8.0, top: 24),
child: Row(
children: [
CircleAvatar(
radius: 36,
backgroundImage: NetworkImage(
getFaceThumbnailUrl(personId),
headers: {
"x-immich-user-token": Store.get(StoreKey.accessToken),
},
),
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: buildTitleBlock(),
),
],
),
),
),
);
}
}
@@ -1,32 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/providers/search/recently_added_asset.provider.dart';
@RoutePage()
class RecentlyAddedPage extends HookConsumerWidget {
const RecentlyAddedPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final recents = ref.watch(recentlyAddedAssetProvider);
return Scaffold(
appBar: AppBar(
title: const Text('recently_added_page_title').tr(),
leading: IconButton(
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),
body: recents.widgetWhen(
onData: (searchResponse) => ImmichAssetGrid(
assets: searchResponse,
),
),
);
}
}
@@ -1,563 +0,0 @@
import 'dart:async';
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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
import 'package:immich_mobile/modules/search/ui/search_filter/camera_picker.dart';
import 'package:immich_mobile/modules/search/ui/search_filter/display_option_picker.dart';
import 'package:immich_mobile/modules/search/ui/search_filter/filter_bottom_sheet_scaffold.dart';
import 'package:immich_mobile/modules/search/ui/search_filter/location_picker.dart';
import 'package:immich_mobile/modules/search/ui/search_filter/media_type_picker.dart';
import 'package:immich_mobile/modules/search/ui/search_filter/people_picker.dart';
import 'package:immich_mobile/modules/search/ui/search_filter/search_filter_chip.dart';
import 'package:immich_mobile/modules/search/ui/search_filter/search_filter_utils.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/shared/ui/asset_grid/multiselect_grid.dart';
import 'package:openapi/api.dart';
@RoutePage()
class SearchInputPage extends HookConsumerWidget {
const SearchInputPage({super.key, this.prefilter});
final SearchFilter? prefilter;
@override
Widget build(BuildContext context, WidgetRef ref) {
final isContextualSearch = useState(true);
final textSearchController = useTextEditingController();
final filter = useState<SearchFilter>(
SearchFilter(
people: prefilter?.people ?? {},
location: prefilter?.location ?? SearchLocationFilter(),
camera: prefilter?.camera ?? SearchCameraFilter(),
date: prefilter?.date ?? SearchDateFilter(),
display: prefilter?.display ??
SearchDisplayFilters(
isNotInAlbum: false,
isArchive: false,
isFavorite: false,
),
mediaType: prefilter?.mediaType ?? AssetType.other,
),
);
final previousFilter = useState(filter.value);
final peopleCurrentFilterWidget = useState<Widget?>(null);
final dateRangeCurrentFilterWidget = useState<Widget?>(null);
final cameraCurrentFilterWidget = useState<Widget?>(null);
final locationCurrentFilterWidget = useState<Widget?>(null);
final mediaTypeCurrentFilterWidget = useState<Widget?>(null);
final displayOptionCurrentFilterWidget = useState<Widget?>(null);
final currentPage = useState(1);
final searchProvider = ref.watch(paginatedSearchProvider);
final searchResultCount = useState(0);
search() async {
if (prefilter == null && filter.value == previousFilter.value) return;
ref.watch(paginatedSearchProvider.notifier).clear();
currentPage.value = 1;
final searchResult = await ref
.watch(paginatedSearchProvider.notifier)
.getNextPage(filter.value, currentPage.value);
previousFilter.value = filter.value;
searchResultCount.value = searchResult.length;
}
searchPrefilter() {
if (prefilter != null) {
Future.delayed(
Duration.zero,
() {
search();
if (prefilter!.location.city != null) {
locationCurrentFilterWidget.value = Text(
prefilter!.location.city!,
style: context.textTheme.labelLarge,
);
}
},
);
}
}
useEffect(
() {
searchPrefilter();
return null;
},
[],
);
loadMoreSearchResult() async {
currentPage.value += 1;
final searchResult = await ref
.watch(paginatedSearchProvider.notifier)
.getNextPage(filter.value, currentPage.value);
searchResultCount.value = searchResult.length;
}
showPeoplePicker() {
handleOnSelect(Set<PersonResponseDto> value) {
filter.value = filter.value.copyWith(
people: value,
);
peopleCurrentFilterWidget.value = Text(
value.map((e) => e.name != '' ? e.name : "No name").join(', '),
style: context.textTheme.labelLarge,
);
}
handleClear() {
filter.value = filter.value.copyWith(
people: {},
);
peopleCurrentFilterWidget.value = null;
search();
}
showFilterBottomSheet(
context: context,
isScrollControlled: true,
child: FractionallySizedBox(
heightFactor: 0.8,
child: FilterBottomSheetScaffold(
title: 'Select people',
expanded: true,
onSearch: search,
onClear: handleClear,
child: PeoplePicker(
onSelect: handleOnSelect,
filter: filter.value.people,
),
),
),
);
}
showLocationPicker() {
handleOnSelect(Map<String, String?> value) {
filter.value = filter.value.copyWith(
location: SearchLocationFilter(
country: value['country'],
city: value['city'],
state: value['state'],
),
);
final locationText = <String>[];
if (value['country'] != null) {
locationText.add(value['country']!);
}
if (value['state'] != null) {
locationText.add(value['state']!);
}
if (value['city'] != null) {
locationText.add(value['city']!);
}
locationCurrentFilterWidget.value = Text(
locationText.join(', '),
style: context.textTheme.labelLarge,
);
}
handleClear() {
filter.value = filter.value.copyWith(
location: SearchLocationFilter(),
);
locationCurrentFilterWidget.value = null;
search();
}
showFilterBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: false,
child: FilterBottomSheetScaffold(
title: 'Select location',
onSearch: search,
onClear: handleClear,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: LocationPicker(
onSelected: handleOnSelect,
filter: filter.value.location,
),
),
),
),
);
}
showCameraPicker() {
handleOnSelect(Map<String, String?> value) {
filter.value = filter.value.copyWith(
camera: SearchCameraFilter(
make: value['make'],
model: value['model'],
),
);
cameraCurrentFilterWidget.value = Text(
'${value['make'] ?? ''} ${value['model'] ?? ''}',
style: context.textTheme.labelLarge,
);
}
handleClear() {
filter.value = filter.value.copyWith(
camera: SearchCameraFilter(),
);
cameraCurrentFilterWidget.value = null;
search();
}
showFilterBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: false,
child: FilterBottomSheetScaffold(
title: 'Select camera type',
onSearch: search,
onClear: handleClear,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: CameraPicker(
onSelect: handleOnSelect,
filter: filter.value.camera,
),
),
),
);
}
showDatePicker() async {
final firstDate = DateTime(1900);
final lastDate = DateTime.now();
final date = await showDateRangePicker(
context: context,
firstDate: firstDate,
lastDate: lastDate,
currentDate: DateTime.now(),
initialDateRange: DateTimeRange(
start: filter.value.date.takenAfter ?? lastDate,
end: filter.value.date.takenBefore ?? lastDate,
),
helpText: 'Select a date range',
cancelText: 'Cancel',
confirmText: 'Select',
saveText: 'Save',
errorFormatText: 'Invalid date format',
errorInvalidText: 'Invalid date',
fieldStartHintText: 'Start date',
fieldEndHintText: 'End date',
initialEntryMode: DatePickerEntryMode.input,
);
if (date == null) {
filter.value = filter.value.copyWith(
date: SearchDateFilter(),
);
dateRangeCurrentFilterWidget.value = null;
search();
return;
}
filter.value = filter.value.copyWith(
date: SearchDateFilter(
takenAfter: date.start,
takenBefore: date.end.add(
const Duration(
hours: 23,
minutes: 59,
seconds: 59,
),
),
),
);
// If date range is less than 24 hours, set the end date to the end of the day
if (date.end.difference(date.start).inHours < 24) {
dateRangeCurrentFilterWidget.value = Text(
date.start.toLocal().toIso8601String().split('T').first,
style: context.textTheme.labelLarge,
);
} else {
dateRangeCurrentFilterWidget.value = Text(
'${date.start.toLocal().toIso8601String().split('T').first} to ${date.end.toLocal().toIso8601String().split('T').first}',
style: context.textTheme.labelLarge,
);
}
search();
}
// MEDIA PICKER
showMediaTypePicker() {
handleOnSelected(AssetType assetType) {
filter.value = filter.value.copyWith(
mediaType: assetType,
);
mediaTypeCurrentFilterWidget.value = Text(
assetType == AssetType.image ? 'Image' : 'Video',
style: context.textTheme.labelLarge,
);
}
handleClear() {
filter.value = filter.value.copyWith(
mediaType: AssetType.other,
);
mediaTypeCurrentFilterWidget.value = null;
search();
}
showFilterBottomSheet(
context: context,
child: FilterBottomSheetScaffold(
title: 'Select media type',
onSearch: search,
onClear: handleClear,
child: MediaTypePicker(
onSelect: handleOnSelected,
filter: filter.value.mediaType,
),
),
);
}
// DISPLAY OPTION
showDisplayOptionPicker() {
handleOnSelect(Map<DisplayOption, bool> value) {
final filterText = <String>[];
value.forEach((key, value) {
switch (key) {
case DisplayOption.notInAlbum:
filter.value = filter.value.copyWith(
display: filter.value.display.copyWith(
isNotInAlbum: value,
),
);
if (value) filterText.add('Not in album');
break;
case DisplayOption.archive:
filter.value = filter.value.copyWith(
display: filter.value.display.copyWith(
isArchive: value,
),
);
if (value) filterText.add('Archive');
break;
case DisplayOption.favorite:
filter.value = filter.value.copyWith(
display: filter.value.display.copyWith(
isFavorite: value,
),
);
if (value) filterText.add('Favorite');
break;
}
});
displayOptionCurrentFilterWidget.value = Text(
filterText.join(', '),
style: context.textTheme.labelLarge,
);
}
handleClear() {
filter.value = filter.value.copyWith(
display: SearchDisplayFilters(
isNotInAlbum: false,
isArchive: false,
isFavorite: false,
),
);
displayOptionCurrentFilterWidget.value = null;
search();
}
showFilterBottomSheet(
context: context,
child: FilterBottomSheetScaffold(
title: 'Display options',
onSearch: search,
onClear: handleClear,
child: DisplayOptionPicker(
onSelect: handleOnSelect,
filter: filter.value.display,
),
),
);
}
handleTextSubmitted(String value) {
if (isContextualSearch.value) {
filter.value = filter.value.copyWith(
context: value,
filename: null,
);
} else {
filter.value = filter.value.copyWith(filename: value, context: null);
}
search();
}
buildSearchResult() {
return switch (searchProvider) {
AsyncData() => Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: NotificationListener<ScrollEndNotification>(
onNotification: (notification) {
final metrics = notification.metrics;
final shouldLoadMore = searchResultCount.value > 75;
if (metrics.pixels >= metrics.maxScrollExtent &&
shouldLoadMore) {
loadMoreSearchResult();
}
return true;
},
child: MultiselectGrid(
renderListProvider: paginatedSearchRenderListProvider,
archiveEnabled: true,
deleteEnabled: true,
editEnabled: true,
favoriteEnabled: true,
stackEnabled: false,
emptyIndicator: const SizedBox(),
),
),
),
),
AsyncError(:final error) => Text('Error: $error'),
_ => const Expanded(child: Center(child: CircularProgressIndicator())),
};
}
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
automaticallyImplyLeading: true,
actions: [
IconButton(
icon: isContextualSearch.value
? const Icon(Icons.abc_rounded)
: const Icon(Icons.image_search_rounded),
onPressed: () {
isContextualSearch.value = !isContextualSearch.value;
textSearchController.clear();
},
),
],
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () {
context.router.pop();
},
),
title: TextField(
controller: textSearchController,
decoration: InputDecoration(
hintText: isContextualSearch.value
? 'Sunrise on the beach'
: 'File name or extension',
hintStyle: context.textTheme.bodyLarge?.copyWith(
color: context.themeData.colorScheme.onSurface.withOpacity(0.75),
fontWeight: FontWeight.w500,
),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
),
),
onSubmitted: handleTextSubmitted,
),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: SizedBox(
height: 50,
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
SearchFilterChip(
icon: Icons.people_alt_rounded,
onTap: showPeoplePicker,
label: 'People',
currentFilter: peopleCurrentFilterWidget.value,
),
SearchFilterChip(
icon: Icons.location_pin,
onTap: showLocationPicker,
label: 'Location',
currentFilter: locationCurrentFilterWidget.value,
),
SearchFilterChip(
icon: Icons.camera_alt_rounded,
onTap: showCameraPicker,
label: 'Camera',
currentFilter: cameraCurrentFilterWidget.value,
),
SearchFilterChip(
icon: Icons.date_range_rounded,
onTap: showDatePicker,
label: 'Date',
currentFilter: dateRangeCurrentFilterWidget.value,
),
SearchFilterChip(
icon: Icons.video_collection_outlined,
onTap: showMediaTypePicker,
label: 'Media Type',
currentFilter: mediaTypeCurrentFilterWidget.value,
),
SearchFilterChip(
icon: Icons.display_settings_outlined,
onTap: showDisplayOptionPicker,
label: 'Display Options',
currentFilter: displayOptionCurrentFilterWidget.value,
),
],
),
),
),
buildSearchResult(),
],
),
);
}
}
@@ -1,267 +0,0 @@
import 'dart:math' as math;
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/search/search_curated_content.model.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/people.provider.dart';
import 'package:immich_mobile/providers/search/search_page_state.provider.dart';
import 'package:immich_mobile/modules/search/ui/curated_people_row.dart';
import 'package:immich_mobile/modules/search/ui/curated_places_row.dart';
import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart';
import 'package:immich_mobile/modules/search/ui/search_row_title.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/ui/immich_app_bar.dart';
import 'package:immich_mobile/shared/ui/scaffold_error_body.dart';
@RoutePage()
// ignore: must_be_immutable
class SearchPage extends HookConsumerWidget {
const SearchPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final places = ref.watch(getPreviewPlacesProvider);
final curatedPeople = ref.watch(getAllPeopleProvider);
final isMapEnabled =
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.map));
double imageSize = math.min(context.width / 3, 150);
TextStyle categoryTitleStyle = const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 15.0,
);
Color categoryIconColor = context.isDarkTheme ? Colors.white : Colors.black;
showNameEditModel(
String personId,
String personName,
) {
return showDialog(
context: context,
builder: (BuildContext context) {
return PersonNameEditForm(personId: personId, personName: personName);
},
);
}
buildPeople() {
return SizedBox(
height: imageSize,
child: curatedPeople.widgetWhen(
onError: (error, stack) => const ScaffoldErrorBody(withIcon: false),
onData: (people) => Padding(
padding: const EdgeInsets.only(
left: 16,
top: 8,
),
child: CuratedPeopleRow(
content: people
.map((e) => SearchCuratedContent(label: e.name, id: e.id))
.take(12)
.toList(),
onTap: (content, index) {
context.pushRoute(
PersonResultRoute(
personId: content.id,
personName: content.label,
),
);
},
onNameTap: (person, index) => {
showNameEditModel(person.id, person.label),
},
),
),
),
);
}
buildPlaces() {
return SizedBox(
height: imageSize,
child: places.widgetWhen(
onError: (error, stack) => const ScaffoldErrorBody(withIcon: false),
onData: (data) => CuratedPlacesRow(
isMapEnabled: isMapEnabled,
content: data,
imageSize: imageSize,
onTap: (content, index) {
context.pushRoute(
SearchInputRoute(
prefilter: SearchFilter(
people: {},
location: SearchLocationFilter(
city: content.label,
),
camera: SearchCameraFilter(),
date: SearchDateFilter(),
display: SearchDisplayFilters(
isNotInAlbum: false,
isArchive: false,
isFavorite: false,
),
mediaType: AssetType.other,
),
),
);
},
),
),
);
}
buildSearchButton() {
return GestureDetector(
onTap: () {
context.pushRoute(SearchInputRoute());
},
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(
color: context.isDarkTheme
? Colors.grey[800]!
: const Color.fromARGB(255, 225, 225, 225),
),
),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 12.0,
),
child: Row(
children: [
Icon(Icons.search, color: context.primaryColor),
const SizedBox(width: 16.0),
Text(
"search_bar_hint",
style: context.textTheme.bodyLarge?.copyWith(
color:
context.isDarkTheme ? Colors.white70 : Colors.black54,
fontWeight: FontWeight.w400,
),
).tr(),
],
),
),
),
);
}
return Scaffold(
appBar: const ImmichAppBar(),
body: Stack(
children: [
ListView(
children: [
buildSearchButton(),
SearchRowTitle(
title: "search_page_people".tr(),
onViewAllPressed: () =>
context.pushRoute(const AllPeopleRoute()),
),
buildPeople(),
SearchRowTitle(
title: "search_page_places".tr(),
onViewAllPressed: () =>
context.pushRoute(const AllPlacesRoute()),
top: 0,
),
const SizedBox(height: 10.0),
buildPlaces(),
const SizedBox(height: 24.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'search_page_your_activity',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
).tr(),
),
ListTile(
leading: Icon(
Icons.favorite_border_rounded,
color: categoryIconColor,
),
title: Text('search_page_favorites', style: categoryTitleStyle)
.tr(),
onTap: () => context.pushRoute(const FavoritesRoute()),
),
const CategoryDivider(),
ListTile(
leading: Icon(
Icons.schedule_outlined,
color: categoryIconColor,
),
title: Text(
'search_page_recently_added',
style: categoryTitleStyle,
).tr(),
onTap: () => context.pushRoute(const RecentlyAddedRoute()),
),
const SizedBox(height: 24.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
'search_page_categories',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
).tr(),
),
ListTile(
title:
Text('search_page_videos', style: categoryTitleStyle).tr(),
leading: Icon(
Icons.play_circle_outline,
color: categoryIconColor,
),
onTap: () => context.pushRoute(const AllVideosRoute()),
),
const CategoryDivider(),
ListTile(
title: Text(
'search_page_motion_photos',
style: categoryTitleStyle,
).tr(),
leading: Icon(
Icons.motion_photos_on_outlined,
color: categoryIconColor,
),
onTap: () => context.pushRoute(const AllMotionPhotosRoute()),
),
],
),
],
),
);
}
}
class CategoryDivider extends StatelessWidget {
const CategoryDivider({super.key});
@override
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.only(
left: 56,
right: 16,
),
child: Divider(
height: 0,
),
);
}
}