mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
feat(mobile): drift search page
This commit is contained in:
parent
87dd09d103
commit
3a878ddd29
@ -116,7 +116,7 @@ class TabShellPage extends ConsumerWidget {
|
||||
return AutoTabsRouter(
|
||||
routes: [
|
||||
const MainTimelineRoute(),
|
||||
SearchRoute(),
|
||||
const DriftSearchRoute(),
|
||||
const DriftAlbumsRoute(),
|
||||
const DriftLibraryRoute(),
|
||||
],
|
||||
|
313
mobile/lib/presentation/pages/dev/drift_search.page.dart
Normal file
313
mobile/lib/presentation/pages/dev/drift_search.page.dart
Normal file
@ -0,0 +1,313 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftSearchPage extends ConsumerStatefulWidget {
|
||||
const DriftSearchPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<DriftSearchPage> createState() => _DriftSearchPageState();
|
||||
}
|
||||
|
||||
class _DriftSearchPageState extends ConsumerState<DriftSearchPage> {
|
||||
final searchController = TextEditingController();
|
||||
final searchFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void onSearch(String searchTerm) {
|
||||
final userId = ref.watch(currentUserProvider)?.id;
|
||||
}
|
||||
|
||||
void clearSearch() {
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userId = ref.watch(currentUserProvider)?.id;
|
||||
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
const ImmichSliverAppBar(),
|
||||
_SearchBar(
|
||||
searchController: searchController,
|
||||
searchFocusNode: searchFocusNode,
|
||||
onClearSearch: clearSearch,
|
||||
),
|
||||
const _SearchFilterChipRow(),
|
||||
const _QuickLinkList(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchBar extends StatelessWidget {
|
||||
const _SearchBar({
|
||||
required this.searchController,
|
||||
required this.searchFocusNode,
|
||||
required this.onClearSearch,
|
||||
});
|
||||
|
||||
final TextEditingController searchController;
|
||||
final FocusNode searchFocusNode;
|
||||
final VoidCallback onClearSearch;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchFilterChipRow extends StatelessWidget {
|
||||
const _SearchFilterChipRow();
|
||||
|
||||
void showPeoplePicker() {
|
||||
|
||||
}
|
||||
|
||||
void showLocationPicker() {
|
||||
|
||||
}
|
||||
|
||||
void showCameraPicker() {
|
||||
|
||||
}
|
||||
|
||||
void showDatePicker() {
|
||||
|
||||
}
|
||||
|
||||
void showMediaTypePicker() {
|
||||
|
||||
}
|
||||
|
||||
void showDisplayOptionPicker() {
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 16, top: 12, right: 16),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: ListView(
|
||||
key: const Key('search_filter_chip_list'),
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
_SearchFilterChip(
|
||||
icon: Icons.people_alt_outlined,
|
||||
onTap: showPeoplePicker,
|
||||
label: 'people'.t(context: context),
|
||||
// currentFilter: peopleCurrentFilterWidget.value,
|
||||
),
|
||||
_SearchFilterChip(
|
||||
icon: Icons.location_on_outlined,
|
||||
onTap: showLocationPicker,
|
||||
label: 'search_filter_location'.t(context: context),
|
||||
// currentFilter: locationCurrentFilterWidget.value,
|
||||
),
|
||||
_SearchFilterChip(
|
||||
icon: Icons.camera_alt_outlined,
|
||||
onTap: showCameraPicker,
|
||||
label: 'camera'.t(context: context),
|
||||
// currentFilter: cameraCurrentFilterWidget.value,
|
||||
),
|
||||
_SearchFilterChip(
|
||||
icon: Icons.date_range_outlined,
|
||||
onTap: showDatePicker,
|
||||
label: 'search_filter_date'.t(context: context),
|
||||
// currentFilter: dateRangeCurrentFilterWidget.value,
|
||||
),
|
||||
_SearchFilterChip(
|
||||
icon: Icons.video_collection_outlined,
|
||||
onTap: showMediaTypePicker,
|
||||
label: 'search_filter_media_type'.t(context: context),
|
||||
// currentFilter: mediaTypeCurrentFilterWidget.value,
|
||||
),
|
||||
_SearchFilterChip(
|
||||
icon: Icons.display_settings_outlined,
|
||||
onTap: showDisplayOptionPicker,
|
||||
label: 'search_filter_display_options'.t(context: context),
|
||||
// currentFilter: displayOptionCurrentFilterWidget.value,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchFilterChip extends StatelessWidget {
|
||||
const _SearchFilterChip({
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
this.currentFilter,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
final Widget? currentFilter;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
color: currentFilter == null
|
||||
? context.colorScheme.surfaceContainerLow
|
||||
: context.primaryColor.withValues(alpha: .5),
|
||||
shape: StadiumBorder(
|
||||
side: BorderSide(
|
||||
color: currentFilter == null
|
||||
? context.colorScheme.outline.withAlpha(15)
|
||||
: context.colorScheme.secondaryContainer,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 2.0, horizontal: 14.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 4.0),
|
||||
currentFilter ?? Text(label),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QuickLinkList extends StatelessWidget {
|
||||
const _QuickLinkList();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
border: Border.all(
|
||||
color: context.colorScheme.outline.withAlpha(10),
|
||||
width: 1,
|
||||
),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withAlpha(10),
|
||||
context.colorScheme.primary.withAlpha(15),
|
||||
context.colorScheme.primary.withAlpha(20),
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.all(0),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
_QuickLink(
|
||||
label: 'recently_taken'.t(context: context),
|
||||
icon: Icons.schedule_outlined,
|
||||
onTap: () => context.pushRoute(const RecentlyTakenRoute()),
|
||||
isTop: true,
|
||||
),
|
||||
_QuickLink(
|
||||
label: 'videos'.t(context: context),
|
||||
icon: Icons.play_circle_outline_rounded,
|
||||
onTap: () => context.pushRoute(const DriftVideoRoute()),
|
||||
),
|
||||
_QuickLink(
|
||||
label: 'favorites'.t(context: context),
|
||||
icon: Icons.favorite_border_rounded,
|
||||
onTap: () => context.pushRoute(const DriftFavoriteRoute()),
|
||||
isBottom: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QuickLink extends StatelessWidget {
|
||||
const _QuickLink({
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
this.isTop = false,
|
||||
this.isBottom = false,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
final bool isTop;
|
||||
final bool isBottom;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final borderRadius = BorderRadius.only(
|
||||
topLeft: Radius.circular(isTop ? 20 : 0),
|
||||
topRight: Radius.circular(isTop ? 20 : 0),
|
||||
bottomLeft: Radius.circular(isBottom ? 20 : 0),
|
||||
bottomRight: Radius.circular(isBottom ? 20 : 0),
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
leading: Icon(
|
||||
icon,
|
||||
size: 26,
|
||||
),
|
||||
title: Text(
|
||||
label,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
@ -127,11 +127,6 @@ final _features = [
|
||||
icon: Icons.timeline_rounded,
|
||||
onTap: (ctx, _) => ctx.pushRoute(const TabShellRoute()),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Video',
|
||||
icon: Icons.video_collection_outlined,
|
||||
onTap: (ctx, _) => ctx.pushRoute(const DriftVideoRoute()),
|
||||
),
|
||||
];
|
||||
|
||||
@RoutePage()
|
||||
|
@ -68,6 +68,7 @@ import 'package:immich_mobile/pages/search/recently_taken.page.dart';
|
||||
import 'package:immich_mobile/pages/search/search.page.dart';
|
||||
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/drift_favorite.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/drift_search.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/drift_video.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/drift_trash.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/drift_archive.page.dart';
|
||||
@ -177,7 +178,7 @@ class AppRouter extends RootStackRouter {
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
page: SearchRoute.page,
|
||||
page: DriftSearchRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
maintainState: false,
|
||||
),
|
||||
@ -428,7 +429,10 @@ class AppRouter extends RootStackRouter {
|
||||
page: DriftAssetSelectionTimelineRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
|
||||
AutoRoute(
|
||||
page: DriftSearchRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
// required to handle all deeplinks in deep_link.service.dart
|
||||
// auto_route_library#1722
|
||||
RedirectRoute(path: '*', redirectTo: '/'),
|
||||
|
@ -783,6 +783,22 @@ class DriftMemoryRouteArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftSearchPage]
|
||||
class DriftSearchRoute extends PageRouteInfo<void> {
|
||||
const DriftSearchRoute({List<PageRouteInfo>? children})
|
||||
: super(DriftSearchRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DriftSearchRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const DriftSearchPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftTrashPage]
|
||||
class DriftTrashRoute extends PageRouteInfo<void> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user