mirror of
https://github.com/immich-app/immich.git
synced 2025-01-01 08:31:59 +00:00
feat(mobile): Use new search API and GridView for Places / Locations (#2043)
* Use new search API and GridView for Places / Locations * Fixes search service by adding clip: true * Rebased from master, uses view all explore grid now * localized view all button * adds empty * style text * Fix issue with horizontal Things not render due to missing height info --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
4dafc74223
commit
32a065afc7
11 changed files with 421 additions and 158 deletions
|
@ -246,5 +246,8 @@
|
||||||
"permission_onboarding_log_out": "Log out",
|
"permission_onboarding_log_out": "Log out",
|
||||||
"login_form_next_button": "Next",
|
"login_form_next_button": "Next",
|
||||||
"album_thumbnail_shared_by": "Shared by {}",
|
"album_thumbnail_shared_by": "Shared by {}",
|
||||||
"album_thumbnail_owned": "Owned"
|
"album_thumbnail_owned": "Owned",
|
||||||
|
"curated_object_page_title": "Things",
|
||||||
|
"curated_location_page_title": "Places",
|
||||||
|
"search_page_view_all_button": "View All"
|
||||||
}
|
}
|
||||||
|
|
15
mobile/lib/modules/search/models/curated_content.dart
Normal file
15
mobile/lib/modules/search/models/curated_content.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/// A wrapper for [CuratedLocationsResponseDto] objects
|
||||||
|
/// and [CuratedObjectsResponseDto] to be displayed in
|
||||||
|
/// a view
|
||||||
|
class CuratedContent {
|
||||||
|
/// The label to show associated with this curated object
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
/// The id to lookup the asset from the server
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
CuratedContent({
|
||||||
|
required this.id,
|
||||||
|
required this.label,
|
||||||
|
});
|
||||||
|
}
|
|
@ -32,13 +32,13 @@ class SearchService {
|
||||||
Future<List<Asset>?> searchAsset(String searchTerm) async {
|
Future<List<Asset>?> searchAsset(String searchTerm) async {
|
||||||
// TODO search in local DB: 1. when offline, 2. to find local assets
|
// TODO search in local DB: 1. when offline, 2. to find local assets
|
||||||
try {
|
try {
|
||||||
final List<AssetResponseDto>? results = await _apiService.assetApi
|
final SearchResponseDto? results = await _apiService.searchApi
|
||||||
.searchAsset(SearchAssetDto(searchTerm: searchTerm));
|
.search(query: searchTerm, clip: true);
|
||||||
if (results == null) {
|
if (results == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// TODO local DB might be out of date; add assets not yet in DB?
|
// TODO local DB might be out of date; add assets not yet in DB?
|
||||||
return _db.assets.getAllByRemoteId(results.map((e) => e.id));
|
return _db.assets.getAllByRemoteId(results.assets.items.map((e) => e.id));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("[ERROR] [searchAsset] ${e.toString()}");
|
debugPrint("[ERROR] [searchAsset] ${e.toString()}");
|
||||||
return null;
|
return null;
|
||||||
|
|
56
mobile/lib/modules/search/ui/explore_grid.dart
Normal file
56
mobile/lib/modules/search/ui/explore_grid.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/curated_content.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
|
|
||||||
|
class ExploreGrid extends StatelessWidget {
|
||||||
|
final List<CuratedContent> curatedContent;
|
||||||
|
const ExploreGrid({
|
||||||
|
super.key,
|
||||||
|
required this.curatedContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (curatedContent.isEmpty) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
child: ThumbnailWithInfo(
|
||||||
|
textInfo: '',
|
||||||
|
onTap: () {
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GridView.builder(
|
||||||
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 140,
|
||||||
|
mainAxisSpacing: 4,
|
||||||
|
crossAxisSpacing: 4,
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final content = curatedContent[index];
|
||||||
|
final thumbnailRequestUrl =
|
||||||
|
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${content.id}';
|
||||||
|
return ThumbnailWithInfo(
|
||||||
|
imageUrl: thumbnailRequestUrl,
|
||||||
|
textInfo: content.label,
|
||||||
|
onTap: () {
|
||||||
|
AutoRouter.of(context).push(
|
||||||
|
SearchResultRoute(searchTerm: content.label),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: curatedContent.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,63 +24,50 @@ class ThumbnailWithInfo extends StatelessWidget {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onTap();
|
onTap();
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Stack(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
alignment: Alignment.bottomCenter,
|
||||||
child: SizedBox(
|
children: [
|
||||||
width: MediaQuery.of(context).size.width / 3,
|
Container(
|
||||||
child: Stack(
|
decoration: BoxDecoration(
|
||||||
alignment: Alignment.bottomCenter,
|
borderRadius: BorderRadius.circular(25),
|
||||||
children: [
|
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
||||||
Container(
|
),
|
||||||
decoration: BoxDecoration(
|
child: imageUrl != null
|
||||||
borderRadius: BorderRadius.circular(25),
|
? ClipRRect(
|
||||||
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
child: CachedNetworkImage(
|
||||||
color: isDarkMode ? Colors.grey[800]! : Colors.grey[400]!,
|
width: double.infinity,
|
||||||
width: 1,
|
height: double.infinity,
|
||||||
),
|
fit: BoxFit.cover,
|
||||||
),
|
imageUrl: imageUrl!,
|
||||||
child: imageUrl != null
|
httpHeaders: {
|
||||||
? ClipRRect(
|
"Authorization":
|
||||||
borderRadius: BorderRadius.circular(20),
|
"Bearer ${Store.get(StoreKey.accessToken)}"
|
||||||
child: CachedNetworkImage(
|
},
|
||||||
width: 250,
|
errorWidget: (context, url, error) =>
|
||||||
height: 250,
|
const Icon(Icons.image_not_supported_outlined),
|
||||||
fit: BoxFit.cover,
|
),
|
||||||
imageUrl: imageUrl!,
|
)
|
||||||
httpHeaders: {
|
: Center(
|
||||||
"Authorization":
|
child: Icon(
|
||||||
"Bearer ${Store.get(StoreKey.accessToken)}"
|
noImageIcon ?? Icons.not_listed_location,
|
||||||
},
|
color: textAndIconColor,
|
||||||
errorWidget: (context, url, error) =>
|
|
||||||
const Icon(Icons.image_not_supported_outlined),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Center(
|
|
||||||
child: Icon(
|
|
||||||
noImageIcon ?? Icons.not_listed_location,
|
|
||||||
color: textAndIconColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: 12,
|
|
||||||
left: 14,
|
|
||||||
child: SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width / 3,
|
|
||||||
child: Text(
|
|
||||||
textInfo,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
|
bottom: 12,
|
||||||
|
left: 14,
|
||||||
|
child: Text(
|
||||||
|
textInfo,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
47
mobile/lib/modules/search/views/curated_location_page.dart
Normal file
47
mobile/lib/modules/search/views/curated_location_page.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/curated_content.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
class CuratedLocationPage extends HookConsumerWidget {
|
||||||
|
const CuratedLocationPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
AsyncValue<List<CuratedLocationsResponseDto>> curatedLocation =
|
||||||
|
ref.watch(getCuratedLocationProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
'curated_location_page_title',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16.0,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
body: curatedLocation.when(
|
||||||
|
loading: () => const Center(child: ImmichLoadingIndicator()),
|
||||||
|
error: (err, stack) => Center(
|
||||||
|
child: Text('Error: $err'),
|
||||||
|
),
|
||||||
|
data: (curatedLocations) => ExploreGrid(
|
||||||
|
curatedContent: curatedLocations
|
||||||
|
.map(
|
||||||
|
(l) => CuratedContent(
|
||||||
|
label: l.city,
|
||||||
|
id: l.id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
50
mobile/lib/modules/search/views/curated_object_page.dart
Normal file
50
mobile/lib/modules/search/views/curated_object_page.dart
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/curated_content.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||||
|
import 'package:immich_mobile/utils/capitalize_first_letter.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
class CuratedObjectPage extends HookConsumerWidget {
|
||||||
|
const CuratedObjectPage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
AsyncValue<List<CuratedObjectsResponseDto>> curatedObjects =
|
||||||
|
ref.watch(getCuratedObjectProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
'curated_object_page_title',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16.0,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
body: curatedObjects.when(
|
||||||
|
loading: () => const Center(child: ImmichLoadingIndicator()),
|
||||||
|
error: (err, stack) => Center(
|
||||||
|
child: Text('Error: $err'),
|
||||||
|
),
|
||||||
|
data: (curatedLocations) => ExploreGrid(
|
||||||
|
curatedContent: curatedLocations
|
||||||
|
.map(
|
||||||
|
(l) => CuratedContent(
|
||||||
|
label: l.object.capitalizeFirstLetter(),
|
||||||
|
id: l.id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,51 +45,56 @@ class SearchPage extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildPlaces() {
|
buildPlaces() {
|
||||||
return curatedLocation.when(
|
return SizedBox(
|
||||||
loading: () => SizedBox(
|
height: imageSize,
|
||||||
height: imageSize,
|
child: curatedLocation.when(
|
||||||
child: const Center(child: ImmichLoadingIndicator()),
|
loading: () => const Center(child: ImmichLoadingIndicator()),
|
||||||
|
error: (err, stack) => Center(child: Text('Error: $err')),
|
||||||
|
data: (curatedLocations) => ListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final locationInfo = curatedLocations[index];
|
||||||
|
final thumbnailRequestUrl =
|
||||||
|
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${locationInfo.id}';
|
||||||
|
return SizedBox(
|
||||||
|
width: imageSize,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
|
child: ThumbnailWithInfo(
|
||||||
|
imageUrl: thumbnailRequestUrl,
|
||||||
|
textInfo: locationInfo.city,
|
||||||
|
onTap: () {
|
||||||
|
AutoRouter.of(context).push(
|
||||||
|
SearchResultRoute(searchTerm: locationInfo.city),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: curatedLocations.length.clamp(0, 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildEmptyThumbnail() {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: SizedBox(
|
||||||
|
width: imageSize,
|
||||||
|
height: imageSize,
|
||||||
|
child: ThumbnailWithInfo(
|
||||||
|
textInfo: '',
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
error: (err, stack) => Text('Error: $err'),
|
|
||||||
data: (curatedLocations) {
|
|
||||||
return curatedLocations.isNotEmpty
|
|
||||||
? SizedBox(
|
|
||||||
height: imageSize,
|
|
||||||
child: ListView.builder(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
itemCount: curatedLocation.value?.length,
|
|
||||||
itemBuilder: ((context, index) {
|
|
||||||
var locationInfo = curatedLocations[index];
|
|
||||||
var thumbnailRequestUrl =
|
|
||||||
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${locationInfo.id}';
|
|
||||||
return ThumbnailWithInfo(
|
|
||||||
imageUrl: thumbnailRequestUrl,
|
|
||||||
textInfo: locationInfo.city,
|
|
||||||
onTap: () {
|
|
||||||
AutoRouter.of(context).push(
|
|
||||||
SearchResultRoute(searchTerm: locationInfo.city),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: SizedBox(
|
|
||||||
height: imageSize,
|
|
||||||
child: ListView.builder(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
itemCount: 1,
|
|
||||||
itemBuilder: ((context, index) {
|
|
||||||
return ThumbnailWithInfo(
|
|
||||||
textInfo: '',
|
|
||||||
onTap: () {},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,51 +104,46 @@ class SearchPage extends HookConsumerWidget {
|
||||||
height: imageSize,
|
height: imageSize,
|
||||||
child: const Center(child: ImmichLoadingIndicator()),
|
child: const Center(child: ImmichLoadingIndicator()),
|
||||||
),
|
),
|
||||||
error: (err, stack) => Text('Error: $err'),
|
error: (err, stack) => SizedBox(
|
||||||
data: (objects) {
|
height: imageSize,
|
||||||
return objects.isNotEmpty
|
child: Center(child: Text('Error: $err')),
|
||||||
? SizedBox(
|
),
|
||||||
height: imageSize,
|
data: (objects) => objects.isEmpty
|
||||||
child: ListView.builder(
|
? buildEmptyThumbnail()
|
||||||
padding: const EdgeInsets.only(left: 16),
|
: SizedBox(
|
||||||
scrollDirection: Axis.horizontal,
|
height: imageSize,
|
||||||
itemCount: curatedObjects.value?.length,
|
child: ListView.builder(
|
||||||
itemBuilder: ((context, index) {
|
shrinkWrap: true,
|
||||||
var curatedObjectInfo = objects[index];
|
scrollDirection: Axis.horizontal,
|
||||||
var thumbnailRequestUrl =
|
padding: const EdgeInsets.symmetric(
|
||||||
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${curatedObjectInfo.id}';
|
horizontal: 16,
|
||||||
|
|
||||||
return ThumbnailWithInfo(
|
|
||||||
imageUrl: thumbnailRequestUrl,
|
|
||||||
textInfo: curatedObjectInfo.object,
|
|
||||||
onTap: () {
|
|
||||||
AutoRouter.of(context).push(
|
|
||||||
SearchResultRoute(
|
|
||||||
searchTerm: curatedObjectInfo.object
|
|
||||||
.capitalizeFirstLetter(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
)
|
itemBuilder: (context, index) {
|
||||||
: SizedBox(
|
final curatedObjectInfo = objects[index];
|
||||||
height: imageSize,
|
final thumbnailRequestUrl =
|
||||||
child: ListView.builder(
|
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${curatedObjectInfo.id}';
|
||||||
padding: const EdgeInsets.only(left: 16),
|
return SizedBox(
|
||||||
scrollDirection: Axis.horizontal,
|
width: imageSize,
|
||||||
itemCount: 1,
|
child: Padding(
|
||||||
itemBuilder: ((context, index) {
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
return ThumbnailWithInfo(
|
child: ThumbnailWithInfo(
|
||||||
textInfo: '',
|
imageUrl: thumbnailRequestUrl,
|
||||||
noImageIcon: Icons.signal_cellular_no_sim_sharp,
|
textInfo: curatedObjectInfo.object,
|
||||||
onTap: () {},
|
onTap: () {
|
||||||
);
|
AutoRouter.of(context).push(
|
||||||
}),
|
SearchResultRoute(
|
||||||
),
|
searchTerm: curatedObjectInfo.object
|
||||||
);
|
.capitalizeFirstLetter(),
|
||||||
},
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: objects.length.clamp(0, 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,24 +160,71 @@ class SearchPage extends HookConsumerWidget {
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
ListView(
|
ListView(
|
||||||
shrinkWrap: true,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.symmetric(
|
||||||
child: const Text(
|
horizontal: 16.0,
|
||||||
"search_page_places",
|
vertical: 4.0,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
),
|
||||||
).tr(),
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"search_page_places",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
TextButton(
|
||||||
|
child: Text(
|
||||||
|
'search_page_view_all_button',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14.0,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
onPressed: () => AutoRouter.of(context).push(
|
||||||
|
const CuratedLocationRoute(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
buildPlaces(),
|
buildPlaces(),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.symmetric(
|
||||||
child: const Text(
|
horizontal: 16.0,
|
||||||
"search_page_things",
|
vertical: 4.0,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
),
|
||||||
).tr(),
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"search_page_things",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
TextButton(
|
||||||
|
child: Text(
|
||||||
|
'search_page_view_all_button',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14.0,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
onPressed: () => AutoRouter.of(context).push(
|
||||||
|
const CuratedObjectRoute(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
buildThings()
|
buildThings(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isSearchEnabled)
|
if (isSearchEnabled)
|
||||||
|
|
|
@ -21,6 +21,8 @@ import 'package:immich_mobile/modules/login/views/change_password_page.dart';
|
||||||
import 'package:immich_mobile/modules/login/views/login_page.dart';
|
import 'package:immich_mobile/modules/login/views/login_page.dart';
|
||||||
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/modules/onboarding/views/permission_onboarding_page.dart';
|
import 'package:immich_mobile/modules/onboarding/views/permission_onboarding_page.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/views/curated_location_page.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/views/curated_object_page.dart';
|
||||||
import 'package:immich_mobile/modules/search/views/search_page.dart';
|
import 'package:immich_mobile/modules/search/views/search_page.dart';
|
||||||
import 'package:immich_mobile/modules/search/views/search_result_page.dart';
|
import 'package:immich_mobile/modules/search/views/search_result_page.dart';
|
||||||
import 'package:immich_mobile/modules/settings/views/settings_page.dart';
|
import 'package:immich_mobile/modules/settings/views/settings_page.dart';
|
||||||
|
@ -64,6 +66,8 @@ part 'router.gr.dart';
|
||||||
AutoRoute(page: VideoViewerPage, guards: [AuthGuard, DuplicateGuard]),
|
AutoRoute(page: VideoViewerPage, guards: [AuthGuard, DuplicateGuard]),
|
||||||
AutoRoute(page: BackupControllerPage, guards: [AuthGuard, DuplicateGuard]),
|
AutoRoute(page: BackupControllerPage, guards: [AuthGuard, DuplicateGuard]),
|
||||||
AutoRoute(page: SearchResultPage, guards: [AuthGuard, DuplicateGuard]),
|
AutoRoute(page: SearchResultPage, guards: [AuthGuard, DuplicateGuard]),
|
||||||
|
AutoRoute(page: CuratedLocationPage, guards: [AuthGuard, DuplicateGuard]),
|
||||||
|
AutoRoute(page: CuratedObjectPage, guards: [AuthGuard, DuplicateGuard]),
|
||||||
AutoRoute(page: CreateAlbumPage, guards: [AuthGuard, DuplicateGuard]),
|
AutoRoute(page: CreateAlbumPage, guards: [AuthGuard, DuplicateGuard]),
|
||||||
AutoRoute(page: FavoritesPage, guards: [AuthGuard, DuplicateGuard]),
|
AutoRoute(page: FavoritesPage, guards: [AuthGuard, DuplicateGuard]),
|
||||||
CustomRoute<AssetSelectionPageResult?>(
|
CustomRoute<AssetSelectionPageResult?>(
|
||||||
|
|
|
@ -102,6 +102,18 @@ class _$AppRouter extends RootStackRouter {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
CuratedLocationRoute.name: (routeData) {
|
||||||
|
return MaterialPageX<dynamic>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: const CuratedLocationPage(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
CuratedObjectRoute.name: (routeData) {
|
||||||
|
return MaterialPageX<dynamic>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: const CuratedObjectPage(),
|
||||||
|
);
|
||||||
|
},
|
||||||
CreateAlbumRoute.name: (routeData) {
|
CreateAlbumRoute.name: (routeData) {
|
||||||
final args = routeData.argsAs<CreateAlbumRouteArgs>();
|
final args = routeData.argsAs<CreateAlbumRouteArgs>();
|
||||||
return MaterialPageX<dynamic>(
|
return MaterialPageX<dynamic>(
|
||||||
|
@ -331,6 +343,22 @@ class _$AppRouter extends RootStackRouter {
|
||||||
duplicateGuard,
|
duplicateGuard,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
RouteConfig(
|
||||||
|
CuratedLocationRoute.name,
|
||||||
|
path: '/curated-location-page',
|
||||||
|
guards: [
|
||||||
|
authGuard,
|
||||||
|
duplicateGuard,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
RouteConfig(
|
||||||
|
CuratedObjectRoute.name,
|
||||||
|
path: '/curated-object-page',
|
||||||
|
guards: [
|
||||||
|
authGuard,
|
||||||
|
duplicateGuard,
|
||||||
|
],
|
||||||
|
),
|
||||||
RouteConfig(
|
RouteConfig(
|
||||||
CreateAlbumRoute.name,
|
CreateAlbumRoute.name,
|
||||||
path: '/create-album-page',
|
path: '/create-album-page',
|
||||||
|
@ -618,6 +646,30 @@ class SearchResultRouteArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [CuratedLocationPage]
|
||||||
|
class CuratedLocationRoute extends PageRouteInfo<void> {
|
||||||
|
const CuratedLocationRoute()
|
||||||
|
: super(
|
||||||
|
CuratedLocationRoute.name,
|
||||||
|
path: '/curated-location-page',
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'CuratedLocationRoute';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [CuratedObjectPage]
|
||||||
|
class CuratedObjectRoute extends PageRouteInfo<void> {
|
||||||
|
const CuratedObjectRoute()
|
||||||
|
: super(
|
||||||
|
CuratedObjectRoute.name,
|
||||||
|
path: '/curated-object-page',
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'CuratedObjectRoute';
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [CreateAlbumPage]
|
/// [CreateAlbumPage]
|
||||||
class CreateAlbumRoute extends PageRouteInfo<CreateAlbumRouteArgs> {
|
class CreateAlbumRoute extends PageRouteInfo<CreateAlbumRouteArgs> {
|
||||||
|
|
|
@ -14,6 +14,7 @@ class ApiService {
|
||||||
late OAuthApi oAuthApi;
|
late OAuthApi oAuthApi;
|
||||||
late AlbumApi albumApi;
|
late AlbumApi albumApi;
|
||||||
late AssetApi assetApi;
|
late AssetApi assetApi;
|
||||||
|
late SearchApi searchApi;
|
||||||
late ServerInfoApi serverInfoApi;
|
late ServerInfoApi serverInfoApi;
|
||||||
late DeviceInfoApi deviceInfoApi;
|
late DeviceInfoApi deviceInfoApi;
|
||||||
|
|
||||||
|
@ -36,6 +37,7 @@ class ApiService {
|
||||||
albumApi = AlbumApi(_apiClient);
|
albumApi = AlbumApi(_apiClient);
|
||||||
assetApi = AssetApi(_apiClient);
|
assetApi = AssetApi(_apiClient);
|
||||||
serverInfoApi = ServerInfoApi(_apiClient);
|
serverInfoApi = ServerInfoApi(_apiClient);
|
||||||
|
searchApi = SearchApi(_apiClient);
|
||||||
deviceInfoApi = DeviceInfoApi(_apiClient);
|
deviceInfoApi = DeviceInfoApi(_apiClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue