feat: new create album page (#19731)

* feat: new create album page

* finished create album flow

* refactor into stateful widgets

* refactor

* focus fix

* lint

* default sort

* pr feedback
This commit is contained in:
Alex 2025-07-10 11:59:15 -05:00 committed by GitHub
parent 1f50a0075e
commit 68db17028b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 730 additions and 8 deletions

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
@ -117,8 +117,6 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Sync;
sourceTree = "<group>";
};
@ -473,10 +471,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@ -505,10 +507,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";

View File

@ -1,12 +1,14 @@
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
import 'package:immich_mobile/models/albums/album_search.model.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:immich_mobile/utils/remote_album.utils.dart';
class RemoteAlbumService {
final DriftRemoteAlbumRepository _repository;
final DriftAlbumApiRepository _albumApiRepository;
const RemoteAlbumService(this._repository);
const RemoteAlbumService(this._repository, this._albumApiRepository);
Future<List<RemoteAlbum>> getAll() {
return _repository.getAll();
@ -57,4 +59,20 @@ class RemoteAlbumService {
return filtered;
}
Future<RemoteAlbum> createAlbum({
required String title,
required List<String> assetIds,
String? description,
}) async {
final album = await _albumApiRepository.createDriftAlbum(
title,
description: description,
assetIds: assetIds,
);
await _repository.create(album, assetIds);
return album;
}
}

View File

@ -1,16 +1,17 @@
import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
enum SortRemoteAlbumsBy { id }
enum SortRemoteAlbumsBy { id, updatedAt }
class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftRemoteAlbumRepository(this._db) : super(_db);
Future<List<RemoteAlbum>> getAll({
Set<SortRemoteAlbumsBy> sortBy = const {},
Set<SortRemoteAlbumsBy> sortBy = const {SortRemoteAlbumsBy.updatedAt},
}) {
final assetCount = _db.remoteAlbumAssetEntity.assetId.count();
@ -43,6 +44,8 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
orderings.add(
switch (sort) {
SortRemoteAlbumsBy.id => OrderingTerm.asc(_db.remoteAlbumEntity.id),
SortRemoteAlbumsBy.updatedAt =>
OrderingTerm.desc(_db.remoteAlbumEntity.updatedAt),
},
);
}
@ -58,6 +61,43 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
)
.get();
}
Future<void> create(
RemoteAlbum album,
List<String> assetIds,
) async {
await _db.transaction(() async {
final entity = RemoteAlbumEntityCompanion(
id: Value(album.id),
name: Value(album.name),
ownerId: Value(album.ownerId),
createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt),
description: Value(album.description),
thumbnailAssetId: Value(album.thumbnailAssetId),
isActivityEnabled: Value(album.isActivityEnabled),
order: Value(album.order),
);
await _db.remoteAlbumEntity.insertOne(entity);
if (assetIds.isNotEmpty) {
final albumAssets = assetIds.map(
(assetId) => RemoteAlbumAssetEntityCompanion(
albumId: Value(album.id),
assetId: Value(assetId),
),
);
await _db.batch((batch) {
batch.insertAll(
_db.remoteAlbumAssetEntity,
albumAssets,
);
});
}
});
}
}
extension on RemoteAlbumEntityData {

View File

@ -97,7 +97,20 @@ class _DriftAlbumsPageState extends ConsumerState<DriftAlbumsPage> {
onRefresh: onRefresh,
child: CustomScrollView(
slivers: [
const ImmichSliverAppBar(),
ImmichSliverAppBar(
actions: [
IconButton(
icon: const Icon(
Icons.add_rounded,
size: 28,
),
onPressed: () => context.pushRoute(
const DriftCreateAlbumRoute(),
),
),
],
showUploadButton: false,
),
_SearchBar(
searchController: searchController,
searchFocusNode: searchFocusNode,

View File

@ -0,0 +1,500 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/album/album_action_filled_button.dart';
@RoutePage()
class DriftCreateAlbumPage extends ConsumerStatefulWidget {
const DriftCreateAlbumPage({super.key});
@override
ConsumerState<DriftCreateAlbumPage> createState() =>
_DriftCreateAlbumPageState();
}
class _DriftCreateAlbumPageState extends ConsumerState<DriftCreateAlbumPage> {
TextEditingController albumTitleController = TextEditingController();
TextEditingController albumDescriptionController = TextEditingController();
FocusNode albumTitleTextFieldFocusNode = FocusNode();
FocusNode albumDescriptionTextFieldFocusNode = FocusNode();
bool isAlbumTitleTextFieldFocus = false;
Set<BaseAsset> selectedAssets = {};
@override
void dispose() {
albumTitleController.dispose();
albumDescriptionController.dispose();
albumTitleTextFieldFocusNode.dispose();
albumDescriptionTextFieldFocusNode.dispose();
super.dispose();
}
bool get _canCreateAlbum => albumTitleController.text.isNotEmpty;
String _getEffectiveTitle() {
return albumTitleController.text.isNotEmpty
? albumTitleController.text
: 'create_album_page_untitled'.t(context: context);
}
Widget _buildSliverAppBar() {
return SliverAppBar(
backgroundColor: context.scaffoldBackgroundColor,
elevation: 0,
automaticallyImplyLeading: false,
pinned: true,
snap: false,
floating: false,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(200.0),
child: SizedBox(
height: 200,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
buildTitleInputField(),
buildDescriptionInputField(),
if (selectedAssets.isNotEmpty) buildControlButton(),
],
),
),
),
);
}
Widget _buildContent() {
if (selectedAssets.isEmpty) {
return SliverList(
delegate: SliverChildListDelegate([
_buildEmptyState(),
_buildSelectPhotosButton(),
]),
);
} else {
return _buildSelectedImageGrid();
}
}
Widget _buildEmptyState() {
return Padding(
padding: const EdgeInsets.only(top: 0, left: 18),
child: Text(
'create_shared_album_page_share_add_assets',
style: context.textTheme.labelLarge,
).t(),
);
}
Widget _buildSelectPhotosButton() {
return Padding(
padding: const EdgeInsets.all(16.0),
child: FilledButton.icon(
style: FilledButton.styleFrom(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(
vertical: 24.0,
horizontal: 16.0,
),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
backgroundColor: context.colorScheme.surfaceContainerHigh,
),
onPressed: onSelectPhotos,
icon: Icon(Icons.add_rounded, color: context.primaryColor),
label: Padding(
padding: const EdgeInsets.only(
left: 8.0,
),
child: Text(
'create_shared_album_page_share_select_photos',
style: context.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: context.primaryColor,
),
).t(),
),
),
);
}
Widget _buildSelectedImageGrid() {
return SliverPadding(
padding: const EdgeInsets.only(top: 16.0),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 1.0,
mainAxisSpacing: 1.0,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
final asset = selectedAssets.elementAt(index);
return GestureDetector(
onTap: onBackgroundTapped,
child: Thumbnail(asset: asset),
);
},
childCount: selectedAssets.length,
),
),
);
}
void onBackgroundTapped() {
albumTitleTextFieldFocusNode.unfocus();
albumDescriptionTextFieldFocusNode.unfocus();
setState(() {
isAlbumTitleTextFieldFocus = false;
});
if (albumTitleController.text.isEmpty) {
final untitledText = 'create_album_page_untitled'.t();
albumTitleController.text = untitledText;
}
}
Future<void> onSelectPhotos() async {
final assets = await context.pushRoute<Set<BaseAsset>>(
DriftAssetSelectionTimelineRoute(
lockedSelectionAssets: selectedAssets,
),
);
if (assets == null || assets.isEmpty) {
return;
}
setState(() {
selectedAssets = selectedAssets.union(assets);
});
}
Future<void> createAlbum() async {
onBackgroundTapped();
final title = _getEffectiveTitle().trim();
if (title.isEmpty) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('create_album_title_required'.t()),
backgroundColor: context.colorScheme.error,
),
);
}
return;
}
final album = await ref.watch(remoteAlbumProvider.notifier).createAlbum(
title: title,
description: albumDescriptionController.text.trim(),
assetIds: selectedAssets.map((asset) {
final remoteAsset = asset as RemoteAsset;
return remoteAsset.id;
}).toList(),
);
if (album != null) {
context.replaceRoute(
RemoteTimelineRoute(album: album),
);
}
}
Widget buildTitleInputField() {
return Padding(
padding: const EdgeInsets.only(
right: 10.0,
left: 10.0,
),
child: _AlbumTitleTextField(
focusNode: albumTitleTextFieldFocusNode,
textController: albumTitleController,
isFocus: isAlbumTitleTextFieldFocus,
onFocusChanged: (focus) {
setState(() {
isAlbumTitleTextFieldFocus = focus;
});
},
),
);
}
Widget buildDescriptionInputField() {
return Padding(
padding: const EdgeInsets.only(
right: 10.0,
left: 10.0,
top: 8,
),
child: _AlbumViewerEditableDescription(
textController: albumDescriptionController,
focusNode: albumDescriptionTextFieldFocusNode,
),
);
}
Widget buildControlButton() {
return Padding(
padding: const EdgeInsets.only(
left: 12.0,
top: 8.0,
bottom: 8.0,
),
child: SizedBox(
height: 42.0,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
AlbumActionFilledButton(
iconData: Icons.add_photo_alternate_outlined,
onPressed: onSelectPhotos,
labelText: "add_photos".t(),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
centerTitle: false,
backgroundColor: context.scaffoldBackgroundColor,
leading: IconButton(
onPressed: () => context.maybePop(),
icon: const Icon(Icons.close_rounded),
),
title: const Text('create_album').t(),
actions: [
TextButton(
onPressed: _canCreateAlbum ? createAlbum : null,
child: Text(
'create'.t(),
style: TextStyle(
fontWeight: FontWeight.bold,
color: _canCreateAlbum
? context.primaryColor
: context.themeData.disabledColor,
),
),
),
],
),
body: GestureDetector(
onTap: onBackgroundTapped,
child: CustomScrollView(
slivers: [
_buildSliverAppBar(),
_buildContent(),
],
),
),
);
}
}
class _AlbumTitleTextField extends StatefulWidget {
const _AlbumTitleTextField({
required this.focusNode,
required this.textController,
required this.isFocus,
required this.onFocusChanged,
});
final FocusNode focusNode;
final TextEditingController textController;
final bool isFocus;
final ValueChanged<bool> onFocusChanged;
@override
State<_AlbumTitleTextField> createState() => _AlbumTitleTextFieldState();
}
class _AlbumTitleTextFieldState extends State<_AlbumTitleTextField> {
@override
void initState() {
super.initState();
widget.focusNode.addListener(_onFocusChange);
}
@override
void dispose() {
widget.focusNode.removeListener(_onFocusChange);
super.dispose();
}
void _onFocusChange() {
widget.onFocusChanged(widget.focusNode.hasFocus);
}
@override
Widget build(BuildContext context) {
return TextField(
focusNode: widget.focusNode,
style: TextStyle(
fontSize: 28.0,
color: context.colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
controller: widget.textController,
onTap: () {
if (widget.textController.text ==
'create_album_page_untitled'.t(context: context)) {
widget.textController.clear();
}
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 16.0,
),
suffixIcon: widget.textController.text.isNotEmpty && widget.isFocus
? IconButton(
onPressed: () {
widget.textController.clear();
},
icon: Icon(
Icons.cancel_rounded,
color: context.primaryColor,
),
splashRadius: 10.0,
)
: null,
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
borderRadius: BorderRadius.all(
Radius.circular(16.0),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: context.primaryColor.withValues(alpha: 0.3),
),
borderRadius: const BorderRadius.all(
Radius.circular(16.0),
),
),
hintText: 'add_a_title'.t(),
hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith(
fontSize: 28.0,
fontWeight: FontWeight.bold,
height: 1.2,
),
focusColor: Colors.grey[300],
fillColor: context.colorScheme.surfaceContainerHigh,
filled: true,
),
);
}
}
class _AlbumViewerEditableDescription extends StatefulWidget {
const _AlbumViewerEditableDescription({
required this.textController,
required this.focusNode,
});
final TextEditingController textController;
final FocusNode focusNode;
@override
State<_AlbumViewerEditableDescription> createState() =>
_AlbumViewerEditableDescriptionState();
}
class _AlbumViewerEditableDescriptionState
extends State<_AlbumViewerEditableDescription> {
@override
void initState() {
super.initState();
widget.focusNode.addListener(_onFocusModeChange);
widget.textController.addListener(_onTextChange);
}
@override
void dispose() {
widget.focusNode.removeListener(_onFocusModeChange);
widget.textController.removeListener(_onTextChange);
super.dispose();
}
void _onFocusModeChange() {
setState(() {
if (!widget.focusNode.hasFocus && widget.textController.text.isEmpty) {
widget.textController.clear();
}
});
}
void _onTextChange() {
setState(() {});
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: TextField(
focusNode: widget.focusNode,
style: context.textTheme.bodyLarge,
maxLines: 3,
minLines: 1,
controller: widget.textController,
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 16.0,
),
suffixIcon:
widget.focusNode.hasFocus && widget.textController.text.isNotEmpty
? IconButton(
onPressed: () {
widget.textController.clear();
},
icon: Icon(
Icons.cancel_rounded,
color: context.primaryColor,
),
splashRadius: 10.0,
)
: null,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: context.colorScheme.outline.withValues(alpha: 0.3),
),
borderRadius: const BorderRadius.all(
Radius.circular(16.0),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: context.primaryColor.withValues(alpha: 0.3),
),
borderRadius: const BorderRadius.all(
Radius.circular(16.0),
),
),
hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith(
fontSize: 16.0,
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
),
focusColor: Colors.grey[300],
fillColor: context.scaffoldBackgroundColor,
filled: widget.focusNode.hasFocus,
hintText: 'add_a_description'.t(),
),
),
);
}
}

View File

@ -7,6 +7,7 @@ import 'package:immich_mobile/infrastructure/repositories/local_album.repository
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
final localAlbumRepository = Provider<DriftLocalAlbumRepository>(
(ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)),
@ -30,7 +31,10 @@ final remoteAlbumRepository = Provider<DriftRemoteAlbumRepository>(
);
final remoteAlbumServiceProvider = Provider<RemoteAlbumService>(
(ref) => RemoteAlbumService(ref.watch(remoteAlbumRepository)),
(ref) => RemoteAlbumService(
ref.watch(remoteAlbumRepository),
ref.watch(driftAlbumApiRepositoryProvider),
),
dependencies: [remoteAlbumRepository],
);

View File

@ -118,4 +118,31 @@ class RemoteAlbumNotifier extends Notifier<RemoteAlbumState> {
.sortAlbums(state.filteredAlbums, sortMode, isReverse: isReverse);
state = state.copyWith(filteredAlbums: sortedAlbums);
}
Future<RemoteAlbum?> createAlbum({
required String title,
String? description,
List<String> assetIds = const [],
}) async {
state = state.copyWith(isLoading: true, error: null);
try {
final album = await _remoteAlbumService.createAlbum(
title: title,
description: description,
assetIds: assetIds,
);
state = state.copyWith(
albums: [...state.albums, album],
filteredAlbums: [...state.filteredAlbums, album],
);
state = state.copyWith(isLoading: false);
return album;
} catch (e) {
state = state.copyWith(isLoading: false, error: e.toString());
rethrow;
}
}
}

View File

@ -1,5 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart'
show AlbumAssetOrder, RemoteAlbum;
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
@ -50,6 +52,25 @@ class AlbumApiRepository extends ApiRepository {
return _toAlbum(responseDto);
}
// TODO: Change name after removing old method
Future<RemoteAlbum> createDriftAlbum(
String name, {
required Iterable<String> assetIds,
String? description,
}) async {
final responseDto = await checkNull(
_api.createAlbum(
CreateAlbumDto(
albumName: name,
description: description,
assetIds: assetIds.toList(),
),
),
);
return _toRemoteAlbum(responseDto);
}
Future<Album> update(
String albumId, {
String? name,
@ -170,4 +191,22 @@ class AlbumApiRepository extends ApiRepository {
return album;
}
static RemoteAlbum _toRemoteAlbum(AlbumResponseDto dto) {
return RemoteAlbum(
id: dto.id,
name: dto.albumName,
ownerId: dto.owner.id,
description: dto.description,
createdAt: dto.createdAt,
updatedAt: dto.updatedAt,
thumbnailAssetId: dto.albumThumbnailAssetId,
isActivityEnabled: dto.isActivityEnabled,
order: dto.order == AssetOrder.asc
? AlbumAssetOrder.asc
: AlbumAssetOrder.desc,
assetCount: dto.assetCount,
ownerName: dto.owner.name,
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/repositories/api.repository.dart';
// ignore: import_rule_openapi
import 'package:openapi/api.dart';
final driftAlbumApiRepositoryProvider = Provider(
(ref) => DriftAlbumApiRepository(ref.watch(apiServiceProvider).albumsApi),
);
class DriftAlbumApiRepository extends ApiRepository {
final AlbumsApi _api;
DriftAlbumApiRepository(this._api);
Future<RemoteAlbum> createDriftAlbum(
String name, {
required Iterable<String> assetIds,
String? description,
}) async {
final responseDto = await checkNull(
_api.createAlbum(
CreateAlbumDto(
albumName: name,
description: description,
assetIds: assetIds.toList(),
),
),
);
return responseDto.toRemoteAlbum();
}
}
extension on AlbumResponseDto {
RemoteAlbum toRemoteAlbum() {
return RemoteAlbum(
id: id,
name: albumName,
ownerId: owner.id,
description: description,
createdAt: createdAt,
updatedAt: updatedAt,
thumbnailAssetId: albumThumbnailAssetId,
isActivityEnabled: isActivityEnabled,
order:
order == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc,
assetCount: assetCount,
ownerName: owner.name,
);
}
}

View File

@ -85,6 +85,7 @@ import 'package:immich_mobile/presentation/pages/dev/remote_timeline.page.dart';
import 'package:immich_mobile/presentation/pages/drift_album.page.dart';
import 'package:immich_mobile/presentation/pages/drift_library.page.dart';
import 'package:immich_mobile/presentation/pages/drift_asset_selection_timeline.page.dart';
import 'package:immich_mobile/presentation/pages/drift_create_album.page.dart';
import 'package:immich_mobile/presentation/pages/drift_memory.page.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
import 'package:immich_mobile/providers/api.provider.dart';
@ -448,6 +449,11 @@ class AppRouter extends RootStackRouter {
page: DriftLocalAlbumsRoute.page,
guards: [_authGuard, _duplicateGuard],
),
AutoRoute(
page: DriftCreateAlbumRoute.page,
guards: [_authGuard, _duplicateGuard],
),
// required to handle all deeplinks in deep_link.service.dart
// auto_route_library#1722
RedirectRoute(path: '*', redirectTo: '/'),

View File

@ -683,6 +683,22 @@ class DriftAssetSelectionTimelineRouteArgs {
}
}
/// generated route for
/// [DriftCreateAlbumPage]
class DriftCreateAlbumRoute extends PageRouteInfo<void> {
const DriftCreateAlbumRoute({List<PageRouteInfo>? children})
: super(DriftCreateAlbumRoute.name, initialChildren: children);
static const String name = 'DriftCreateAlbumRoute';
static PageInfo page = PageInfo(
name,
builder: (data) {
return const DriftCreateAlbumPage();
},
);
}
/// generated route for
/// [DriftFavoritePage]
class DriftFavoriteRoute extends PageRouteInfo<void> {