feat(mobile): Add multi selected assets to album (#1446)
* refactored to use multiple assets in AddToAlbumList * add to album from multiselect * consistent language * fixed accidental boolean
This commit is contained in:
parent
3f2513a717
commit
8d47798fa2
7 changed files with 269 additions and 111 deletions
129
mobile/lib/modules/album/ui/add_to_album_bottom_sheet.dart
Normal file
129
mobile/lib/modules/album/ui/add_to_album_bottom_sheet.dart
Normal file
|
@ -0,0 +1,129 @@
|
|||
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/modules/album/providers/album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/album_thumbnail_listtile.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class AddToAlbumBottomSheet extends HookConsumerWidget {
|
||||
|
||||
/// The asset to add to an album
|
||||
final List<Asset> assets;
|
||||
|
||||
const AddToAlbumBottomSheet({
|
||||
Key? key,
|
||||
required this.assets,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final albums = ref.watch(albumProvider);
|
||||
final albumService = ref.watch(albumServiceProvider);
|
||||
final sharedAlbums = ref.watch(sharedAlbumProvider);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
// Fetch album updates, e.g., cover image
|
||||
ref.read(albumProvider.notifier).getAllAlbums();
|
||||
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
void addToAlbum(AlbumResponseDto album) async {
|
||||
final result = await albumService.addAdditionalAssetToAlbum(
|
||||
assets,
|
||||
album.id,
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
if (result.alreadyInAlbum.isNotEmpty) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'Already in ${album.albumName}',
|
||||
);
|
||||
} else {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'Added to ${album.albumName}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ref.read(albumProvider.notifier).getAllAlbums();
|
||||
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
|
||||
return Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(15),
|
||||
topRight: Radius.circular(15),
|
||||
),
|
||||
),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Align(
|
||||
alignment: Alignment.center,
|
||||
child: CustomDraggingHandle(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Add to album',
|
||||
style: Theme.of(context).textTheme.headline2,
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Create new album'),
|
||||
onPressed: () {
|
||||
ref.watch(assetSelectionProvider.notifier).removeAll();
|
||||
ref.watch(assetSelectionProvider.notifier).addNewAssets(assets);
|
||||
AutoRouter.of(context).push(
|
||||
CreateAlbumRoute(
|
||||
isSharedAlbum: false,
|
||||
initialAssets: assets,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: AddToAlbumSliverList(
|
||||
albums: albums,
|
||||
sharedAlbums: sharedAlbums,
|
||||
onAddToAlbum: addToAlbum,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -16,11 +16,11 @@ import 'package:openapi/api.dart';
|
|||
class AddToAlbumList extends HookConsumerWidget {
|
||||
|
||||
/// The asset to add to an album
|
||||
final Asset asset;
|
||||
final List<Asset> assets;
|
||||
|
||||
const AddToAlbumList({
|
||||
Key? key,
|
||||
required this.asset,
|
||||
required this.assets,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -42,7 +42,7 @@ class AddToAlbumList extends HookConsumerWidget {
|
|||
|
||||
void addToAlbum(AlbumResponseDto album) async {
|
||||
final result = await albumService.addAdditionalAssetToAlbum(
|
||||
[asset],
|
||||
assets,
|
||||
album.id,
|
||||
);
|
||||
|
||||
|
@ -84,22 +84,27 @@ class AddToAlbumList extends HookConsumerWidget {
|
|||
child: CustomDraggingHandle(),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text('Add to album',
|
||||
style: Theme.of(context).textTheme.headline1,
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('New album'),
|
||||
onPressed: () {
|
||||
ref.watch(assetSelectionProvider.notifier).removeAll();
|
||||
ref.watch(assetSelectionProvider.notifier).addNewAssets([asset]);
|
||||
AutoRouter.of(context).push(
|
||||
CreateAlbumRoute(
|
||||
isSharedAlbum: false,
|
||||
initialAssets: [asset],
|
||||
),
|
||||
);
|
||||
},
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Add to album',
|
||||
style: Theme.of(context).textTheme.headline2,
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('New album'),
|
||||
onPressed: () {
|
||||
ref.watch(assetSelectionProvider.notifier).removeAll();
|
||||
ref.watch(assetSelectionProvider.notifier).addNewAssets(assets);
|
||||
AutoRouter.of(context).push(
|
||||
CreateAlbumRoute(
|
||||
isSharedAlbum: false,
|
||||
initialAssets: assets,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
56
mobile/lib/modules/album/ui/add_to_album_sliverlist.dart
Normal file
56
mobile/lib/modules/album/ui/add_to_album_sliverlist.dart
Normal file
|
@ -0,0 +1,56 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/album_thumbnail_listtile.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class AddToAlbumSliverList extends HookConsumerWidget {
|
||||
|
||||
/// The asset to add to an album
|
||||
final List<AlbumResponseDto> albums;
|
||||
final List<AlbumResponseDto> sharedAlbums;
|
||||
final void Function(AlbumResponseDto) onAddToAlbum;
|
||||
|
||||
const AddToAlbumSliverList({
|
||||
Key? key,
|
||||
required this.onAddToAlbum,
|
||||
required this.albums,
|
||||
required this.sharedAlbums,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1),
|
||||
(context, index) {
|
||||
// Build shared expander
|
||||
if (index == 0 && sharedAlbums.isNotEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: ExpansionTile(
|
||||
title: const Text('Shared'),
|
||||
tilePadding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
leading: const Icon(Icons.group),
|
||||
children: sharedAlbums.map((album) =>
|
||||
AlbumThumbnailListTile(
|
||||
album: album,
|
||||
onTap: () => onAddToAlbum(album),
|
||||
),
|
||||
).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Build albums list
|
||||
final offset = index - (sharedAlbums.isNotEmpty ? 1 : 0);
|
||||
final album = albums[offset];
|
||||
return AlbumThumbnailListTile(
|
||||
album: album,
|
||||
onTap: () => onAddToAlbum(album),
|
||||
);
|
||||
}
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||
import 'package:hive/hive.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/add_to_album_bottom_sheet.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/add_to_album_list.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
|
||||
|
@ -115,8 +116,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (BuildContext _) {
|
||||
return AddToAlbumList(
|
||||
asset: addToAlbumAsset,
|
||||
return AddToAlbumBottomSheet(
|
||||
assets: [addToAlbumAsset],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
|
||||
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class ControlBottomAppBar extends ConsumerWidget {
|
||||
|
@ -16,11 +13,13 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
final void Function() onCreateNewAlbum;
|
||||
|
||||
final List<AlbumResponseDto> albums;
|
||||
final List<AlbumResponseDto> sharedAlbums;
|
||||
|
||||
const ControlBottomAppBar({
|
||||
Key? key,
|
||||
required this.onShare,
|
||||
required this.onDelete,
|
||||
required this.sharedAlbums,
|
||||
required this.albums,
|
||||
required this.onAddToAlbum,
|
||||
required this.onCreateNewAlbum,
|
||||
|
@ -56,60 +55,6 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget renderAlbums() {
|
||||
Widget renderAlbum(AlbumResponseDto album) {
|
||||
final box = Hive.box(userInfoBox);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: GestureDetector(
|
||||
onTap: () => onAddToAlbum(album),
|
||||
child: Container(
|
||||
width: 112,
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: CachedNetworkImage(
|
||||
width: 100,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
imageUrl: getAlbumThumbnailUrl(album),
|
||||
httpHeaders: {
|
||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||
},
|
||||
cacheKey: getAlbumThumbNailCacheKey(album),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Text(
|
||||
album.albumName,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (buildContext, i) => renderAlbum(albums[i]),
|
||||
itemCount: albums.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return DraggableScrollableSheet(
|
||||
initialChildSize: 0.30,
|
||||
minChildSize: 0.15,
|
||||
|
@ -119,42 +64,53 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
BuildContext context,
|
||||
ScrollController scrollController,
|
||||
) {
|
||||
return SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: Card(
|
||||
elevation: 12.0,
|
||||
shape: const RoundedRectangleBorder(
|
||||
return Card(
|
||||
elevation: 12.0,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
margin: const EdgeInsets.all(0),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
margin: const EdgeInsets.all(0),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 12),
|
||||
const CustomDraggingHandle(),
|
||||
const SizedBox(height: 12),
|
||||
renderActionButtons(),
|
||||
const Divider(
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
thickness: 1,
|
||||
),
|
||||
AddToAlbumTitleRow(onCreateNewAlbum: onCreateNewAlbum),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 12),
|
||||
const CustomDraggingHandle(),
|
||||
const SizedBox(height: 12),
|
||||
renderActionButtons(),
|
||||
const Divider(
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
thickness: 1,
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
sliver: AddToAlbumSliverList(
|
||||
albums: albums,
|
||||
sharedAlbums: sharedAlbums,
|
||||
onAddToAlbum: onAddToAlbum,
|
||||
),
|
||||
AddToAlbumTitleRow(
|
||||
onCreateNewAlbum: () => onCreateNewAlbum(),
|
||||
),
|
||||
renderAlbums(),
|
||||
const SizedBox(height: 200),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 200),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -185,9 +141,10 @@ class AddToAlbumTitleRow extends StatelessWidget {
|
|||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
TextButton(
|
||||
TextButton.icon(
|
||||
onPressed: onCreateNewAlbum,
|
||||
child: Text(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(
|
||||
"control_bottom_app_bar_create_new_album",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||
|
@ -37,6 +38,7 @@ class HomePage extends HookConsumerWidget {
|
|||
|
||||
final selection = useState(<Asset>{});
|
||||
final albums = ref.watch(albumProvider);
|
||||
final sharedAlbums = ref.watch(sharedAlbumProvider);
|
||||
final albumService = ref.watch(albumServiceProvider);
|
||||
|
||||
final tipOneOpacity = useState(0.0);
|
||||
|
@ -46,6 +48,7 @@ class HomePage extends HookConsumerWidget {
|
|||
ref.read(websocketProvider.notifier).connect();
|
||||
ref.read(assetProvider.notifier).getAllAsset();
|
||||
ref.read(albumProvider.notifier).getAllAlbums();
|
||||
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||
ref.watch(serverInfoProvider.notifier).getServerVersion();
|
||||
|
||||
selectionEnabledHook.addListener(() {
|
||||
|
@ -147,6 +150,7 @@ class HomePage extends HookConsumerWidget {
|
|||
|
||||
if (result != null) {
|
||||
ref.watch(albumProvider.notifier).getAllAlbums();
|
||||
ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||
selectionEnabledHook.value = false;
|
||||
|
||||
AutoRouter.of(context).push(AlbumViewerRoute(albumId: result.id));
|
||||
|
@ -220,6 +224,7 @@ class HomePage extends HookConsumerWidget {
|
|||
onDelete: onDelete,
|
||||
onAddToAlbum: onAddToAlbum,
|
||||
albums: albums,
|
||||
sharedAlbums: sharedAlbums,
|
||||
onCreateNewAlbum: onCreateNewAlbum,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -31,6 +31,11 @@ ThemeData immichDarkTheme = ThemeData(
|
|||
snackBarTheme: const SnackBarThemeData(
|
||||
contentTextStyle: TextStyle(fontFamily: 'WorkSans'),
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: immichDarkThemePrimaryColor,
|
||||
),
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
titleTextStyle: TextStyle(
|
||||
fontFamily: 'WorkSans',
|
||||
|
@ -59,7 +64,7 @@ ThemeData immichDarkTheme = ThemeData(
|
|||
headline2: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color.fromARGB(255, 148, 151, 155),
|
||||
color: Color.fromARGB(255, 255, 255, 255),
|
||||
),
|
||||
headline3: TextStyle(
|
||||
fontSize: 12,
|
||||
|
|
Loading…
Reference in a new issue