feat(mobile): lazy loading of assets (#2413)

This commit is contained in:
Fynn Petersen-Frey
2023-05-17 19:36:02 +02:00
committed by GitHub
parent 93863b0629
commit 0dde76bbbc
54 changed files with 1494 additions and 2328 deletions

View File

@@ -2,47 +2,20 @@ import 'package:collection/collection.dart';
import 'package:immich_mobile/shared/models/asset.dart';
class AssetSelectionPageResult {
final Set<Asset> selectedNewAsset;
final Set<Asset> selectedAdditionalAsset;
final bool isAlbumExist;
final Set<Asset> selectedAssets;
AssetSelectionPageResult({
required this.selectedNewAsset,
required this.selectedAdditionalAsset,
required this.isAlbumExist,
required this.selectedAssets,
});
AssetSelectionPageResult copyWith({
Set<Asset>? selectedNewAsset,
Set<Asset>? selectedAdditionalAsset,
bool? isAlbumExist,
}) {
return AssetSelectionPageResult(
selectedNewAsset: selectedNewAsset ?? this.selectedNewAsset,
selectedAdditionalAsset:
selectedAdditionalAsset ?? this.selectedAdditionalAsset,
isAlbumExist: isAlbumExist ?? this.isAlbumExist,
);
}
@override
String toString() =>
'AssetSelectionPageResult(selectedNewAsset: $selectedNewAsset, selectedAdditionalAsset: $selectedAdditionalAsset, isAlbumExist: $isAlbumExist)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
final setEquals = const DeepCollectionEquality().equals;
return other is AssetSelectionPageResult &&
setEquals(other.selectedNewAsset, selectedNewAsset) &&
setEquals(other.selectedAdditionalAsset, selectedAdditionalAsset) &&
other.isAlbumExist == isAlbumExist;
setEquals(other.selectedAssets, selectedAssets);
}
@override
int get hashCode =>
selectedNewAsset.hashCode ^
selectedAdditionalAsset.hashCode ^
isAlbumExist.hashCode;
int get hashCode => selectedAssets.hashCode;
}

View File

@@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/shared/models/asset.dart';
@@ -9,50 +10,38 @@ import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:isar/isar.dart';
class AlbumNotifier extends StateNotifier<List<Album>> {
AlbumNotifier(this._albumService, this._db) : super([]);
AlbumNotifier(this._albumService, Isar db) : super([]) {
final query = db.albums
.filter()
.owner((q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId));
query.findAll().then((value) => state = value);
_streamSub = query.watch().listen((data) => state = data);
}
final AlbumService _albumService;
final Isar _db;
late final StreamSubscription<List<Album>> _streamSub;
Future<void> getAllAlbums() async {
final User me = Store.get(StoreKey.currentUser);
List<Album> albums = await _db.albums
.filter()
.owner((q) => q.isarIdEqualTo(me.isarId))
.findAll();
if (!const ListEquality().equals(albums, state)) {
state = albums;
}
await Future.wait([
_albumService.refreshDeviceAlbums(),
_albumService.refreshRemoteAlbums(isShared: false),
]);
albums = await _db.albums
.filter()
.owner((q) => q.isarIdEqualTo(me.isarId))
.findAll();
if (!const ListEquality().equals(albums, state)) {
state = albums;
}
}
Future<void> getAllAlbums() => Future.wait([
_albumService.refreshDeviceAlbums(),
_albumService.refreshRemoteAlbums(isShared: false),
]);
Future<bool> deleteAlbum(Album album) async {
state = state.where((a) => a.id != album.id).toList();
return _albumService.deleteAlbum(album);
}
Future<bool> deleteAlbum(Album album) => _albumService.deleteAlbum(album);
Future<Album?> createAlbum(
String albumTitle,
Set<Asset> assets,
) async {
Album? album = await _albumService.createAlbum(albumTitle, assets, []);
if (album != null) {
state = [...state, album];
}
return album;
) =>
_albumService.createAlbum(albumTitle, assets, []);
@override
void dispose() {
_streamSub.cancel();
super.dispose();
}
}
final albumProvider = StateNotifierProvider<AlbumNotifier, List<Album>>((ref) {
final albumProvider =
StateNotifierProvider.autoDispose<AlbumNotifier, List<Album>>((ref) {
return AlbumNotifier(
ref.watch(albumServiceProvider),
ref.watch(dbProvider),

View File

@@ -1,134 +0,0 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_state.model.dart';
import 'package:immich_mobile/shared/models/asset.dart';
class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
AssetSelectionNotifier()
: super(
AssetSelectionState(
selectedNewAssetsForAlbum: {},
selectedMonths: {},
selectedAdditionalAssetsForAlbum: {},
selectedAssetsInAlbumViewer: {},
isAlbumExist: false,
isMultiselectEnable: false,
),
);
void setIsAlbumExist(bool isAlbumExist) {
state = state.copyWith(isAlbumExist: isAlbumExist);
}
void removeAssetsInMonth(
String removedMonth,
List<Asset> assetsInMonth,
) {
Set<Asset> currentAssetList = state.selectedNewAssetsForAlbum;
Set<String> currentMonthList = state.selectedMonths;
currentMonthList
.removeWhere((selectedMonth) => selectedMonth == removedMonth);
for (Asset asset in assetsInMonth) {
currentAssetList.removeWhere((e) => e.id == asset.id);
}
state = state.copyWith(
selectedNewAssetsForAlbum: currentAssetList,
selectedMonths: currentMonthList,
);
}
void addAdditionalAssets(List<Asset> assets) {
state = state.copyWith(
selectedAdditionalAssetsForAlbum: {
...state.selectedAdditionalAssetsForAlbum,
...assets
},
);
}
void addAllAssetsInMonth(String month, List<Asset> assetsInMonth) {
state = state.copyWith(
selectedMonths: {...state.selectedMonths, month},
selectedNewAssetsForAlbum: {
...state.selectedNewAssetsForAlbum,
...assetsInMonth
},
);
}
void addNewAssets(Iterable<Asset> assets) {
state = state.copyWith(
selectedNewAssetsForAlbum: {
...state.selectedNewAssetsForAlbum,
...assets
},
);
}
void removeSelectedNewAssets(List<Asset> assets) {
Set<Asset> currentList = state.selectedNewAssetsForAlbum;
for (Asset asset in assets) {
currentList.removeWhere((e) => e.id == asset.id);
}
state = state.copyWith(selectedNewAssetsForAlbum: currentList);
}
void removeSelectedAdditionalAssets(List<Asset> assets) {
Set<Asset> currentList = state.selectedAdditionalAssetsForAlbum;
for (Asset asset in assets) {
currentList.removeWhere((e) => e.id == asset.id);
}
state = state.copyWith(selectedAdditionalAssetsForAlbum: currentList);
}
void removeAll() {
state = state.copyWith(
selectedNewAssetsForAlbum: {},
selectedMonths: {},
selectedAdditionalAssetsForAlbum: {},
selectedAssetsInAlbumViewer: {},
isAlbumExist: false,
);
}
void enableMultiselection() {
state = state.copyWith(isMultiselectEnable: true);
}
void disableMultiselection() {
state = state.copyWith(
isMultiselectEnable: false,
selectedAssetsInAlbumViewer: {},
);
}
void addAssetsInAlbumViewer(List<Asset> assets) {
state = state.copyWith(
selectedAssetsInAlbumViewer: {
...state.selectedAssetsInAlbumViewer,
...assets
},
);
}
void removeAssetsInAlbumViewer(List<Asset> assets) {
Set<Asset> currentList = state.selectedAssetsInAlbumViewer;
for (Asset asset in assets) {
currentList.removeWhere((e) => e.id == asset.id);
}
state = state.copyWith(selectedAssetsInAlbumViewer: currentList);
}
}
final assetSelectionProvider =
StateNotifierProvider<AssetSelectionNotifier, AssetSelectionState>((ref) {
return AssetSelectionNotifier();
});

View File

@@ -1,7 +1,9 @@
import 'package:collection/collection.dart';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/user.dart';
@@ -9,10 +11,14 @@ import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:isar/isar.dart';
class SharedAlbumNotifier extends StateNotifier<List<Album>> {
SharedAlbumNotifier(this._albumService, this._db) : super([]);
SharedAlbumNotifier(this._albumService, Isar db) : super([]) {
final query = db.albums.filter().sharedEqualTo(true).sortByCreatedAtDesc();
query.findAll().then((value) => state = value);
_streamSub = query.watch().listen((data) => state = data);
}
final AlbumService _albumService;
final Isar _db;
late final StreamSubscription<List<Album>> _streamSub;
Future<Album?> createSharedAlbum(
String albumName,
@@ -20,46 +26,21 @@ class SharedAlbumNotifier extends StateNotifier<List<Album>> {
Iterable<User> sharedUsers,
) async {
try {
final Album? newAlbum = await _albumService.createAlbum(
return await _albumService.createAlbum(
albumName,
assets,
sharedUsers,
);
if (newAlbum != null) {
state = [...state, newAlbum];
return newAlbum;
}
} catch (e) {
debugPrint("Error createSharedAlbum ${e.toString()}");
}
return null;
}
Future<void> getAllSharedAlbums() async {
var albums = await _db.albums
.filter()
.sharedEqualTo(true)
.sortByCreatedAtDesc()
.findAll();
if (!const ListEquality().equals(albums, state)) {
state = albums;
}
await _albumService.refreshRemoteAlbums(isShared: true);
albums = await _db.albums
.filter()
.sharedEqualTo(true)
.sortByCreatedAtDesc()
.findAll();
if (!const ListEquality().equals(albums, state)) {
state = albums;
}
}
Future<void> getAllSharedAlbums() =>
_albumService.refreshRemoteAlbums(isShared: true);
Future<bool> deleteAlbum(Album album) {
state = state.where((a) => a.id != album.id).toList();
return _albumService.deleteAlbum(album);
}
Future<bool> deleteAlbum(Album album) => _albumService.deleteAlbum(album);
Future<bool> leaveAlbum(Album album) async {
var res = await _albumService.leaveAlbum(album);
@@ -75,10 +56,16 @@ class SharedAlbumNotifier extends StateNotifier<List<Album>> {
Future<bool> removeAssetFromAlbum(Album album, Iterable<Asset> assets) {
return _albumService.removeAssetFromAlbum(album, assets);
}
@override
void dispose() {
_streamSub.cancel();
super.dispose();
}
}
final sharedAlbumProvider =
StateNotifierProvider<SharedAlbumNotifier, List<Album>>((ref) {
StateNotifierProvider.autoDispose<SharedAlbumNotifier, List<Album>>((ref) {
return SharedAlbumNotifier(
ref.watch(albumServiceProvider),
ref.watch(dbProvider),
@@ -86,10 +73,15 @@ final sharedAlbumProvider =
});
final sharedAlbumDetailProvider =
FutureProvider.autoDispose.family<Album?, int>((ref, albumId) async {
StreamProvider.autoDispose.family<Album, int>((ref, albumId) async* {
final AlbumService sharedAlbumService = ref.watch(albumServiceProvider);
final Album? a = await sharedAlbumService.getAlbumDetail(albumId);
await a?.loadSortedAssets();
return a;
await for (final a in sharedAlbumService.watchAlbum(albumId)) {
if (a == null) {
throw Exception("Album with ID=$albumId does not exist anymore!");
}
await for (final _ in a.watchRenderList(GroupAssetsBy.none)) {
yield a;
}
}
});

View File

@@ -214,8 +214,9 @@ class AlbumService {
);
}
Future<Album?> getAlbumDetail(int albumId) {
return _db.albums.get(albumId);
Stream<Album?> watchAlbum(int albumId) async* {
yield await _db.albums.get(albumId);
yield* _db.albums.watchObject(albumId);
}
Future<AddAssetsResponseDto?> addAdditionalAssetToAlbum(

View File

@@ -4,7 +4,6 @@ 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';
@@ -110,12 +109,6 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
TextStyle(color: Theme.of(context).primaryColor),
),
onPressed: () {
ref
.watch(assetSelectionProvider.notifier)
.removeAll();
ref
.watch(assetSelectionProvider.notifier)
.addNewAssets(assets);
AutoRouter.of(context).push(
CreateAlbumRoute(
isSharedAlbum: false,

View File

@@ -9,12 +9,14 @@ class AddToAlbumSliverList extends HookConsumerWidget {
final List<Album> albums;
final List<Album> sharedAlbums;
final void Function(Album) onAddToAlbum;
final bool enabled;
const AddToAlbumSliverList({
Key? key,
required this.onAddToAlbum,
required this.albums,
required this.sharedAlbums,
this.enabled = true,
}) : super(key: key);
@override
@@ -28,14 +30,14 @@ class AddToAlbumSliverList extends HookConsumerWidget {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: ExpansionTile(
title: Text('common_shared'.tr()),
title: Text('common_shared'.tr()),
tilePadding: const EdgeInsets.symmetric(horizontal: 10.0),
leading: const Icon(Icons.group),
children: sharedAlbums
.map(
(album) => AlbumThumbnailListTile(
album: album,
onTap: () => onAddToAlbum(album),
onTap: enabled ? () => onAddToAlbum(album) : () {},
),
)
.toList(),
@@ -48,7 +50,7 @@ class AddToAlbumSliverList extends HookConsumerWidget {
final album = albums[offset];
return AlbumThumbnailListTile(
album: album,
onTap: () => onAddToAlbum(album),
onTap: enabled ? () => onAddToAlbum(album) : () {},
);
}),
);

View File

@@ -5,10 +5,10 @@ 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/album_viewer.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/routing/router.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
@@ -18,17 +18,19 @@ class AlbumViewerAppbar extends HookConsumerWidget
Key? key,
required this.album,
required this.userId,
required this.selected,
required this.selectionDisabled,
required this.titleFocusNode,
}) : super(key: key);
final Album album;
final String userId;
final Set<Asset> selected;
final void Function() selectionDisabled;
final FocusNode titleFocusNode;
@override
Widget build(BuildContext context, WidgetRef ref) {
final isMultiSelectionEnable =
ref.watch(assetSelectionProvider).isMultiselectEnable;
final selectedAssetsInAlbum =
ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText;
final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum;
@@ -86,12 +88,12 @@ class AlbumViewerAppbar extends HookConsumerWidget
bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum(
album,
selectedAssetsInAlbum,
selected,
);
if (isSuccess) {
Navigator.pop(context);
ref.watch(assetSelectionProvider.notifier).disableMultiselection();
selectionDisabled();
ref.watch(albumProvider.notifier).getAllAlbums();
ref.invalidate(sharedAlbumDetailProvider(album.id));
} else {
@@ -108,7 +110,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
}
buildBottomSheetActionButton() {
if (isMultiSelectionEnable) {
if (selected.isNotEmpty) {
if (album.ownerId == userId) {
return ListTile(
leading: const Icon(Icons.delete_sweep_rounded),
@@ -163,11 +165,9 @@ class AlbumViewerAppbar extends HookConsumerWidget
}
buildLeadingButton() {
if (isMultiSelectionEnable) {
if (selected.isNotEmpty) {
return IconButton(
onPressed: () => ref
.watch(assetSelectionProvider.notifier)
.disableMultiselection(),
onPressed: selectionDisabled,
icon: const Icon(Icons.close_rounded),
splashRadius: 25,
);
@@ -202,9 +202,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
return AppBar(
elevation: 0,
leading: buildLeadingButton(),
title: isMultiSelectionEnable
? Text('${selectedAssetsInAlbum.length}')
: null,
title: selected.isNotEmpty ? Text('${selected.length}') : null,
centerTitle: false,
actions: [
if (album.isRemote)

View File

@@ -1,163 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:immich_mobile/utils/storage_indicator.dart';
class AlbumViewerThumbnail extends HookConsumerWidget {
final Asset asset;
final List<Asset> assetList;
final bool showStorageIndicator;
const AlbumViewerThumbnail({
Key? key,
required this.asset,
required this.assetList,
this.showStorageIndicator = true,
}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedAssetsInAlbumViewer =
ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
final isMultiSelectionEnable =
ref.watch(assetSelectionProvider).isMultiselectEnable;
final isFavorite = ref.watch(favoriteProvider).contains(asset.id);
viewAsset() {
AutoRouter.of(context).push(
GalleryViewerRoute(
asset: asset,
assetList: assetList,
),
);
}
BoxBorder drawBorderColor() {
if (selectedAssetsInAlbumViewer.contains(asset)) {
return Border.all(
color: Theme.of(context).primaryColorLight,
width: 10,
);
} else {
return const Border();
}
}
enableMultiSelection() {
ref.watch(assetSelectionProvider.notifier).enableMultiselection();
ref
.watch(assetSelectionProvider.notifier)
.addAssetsInAlbumViewer([asset]);
}
disableMultiSelection() {
ref.watch(assetSelectionProvider.notifier).disableMultiselection();
}
buildVideoLabel() {
return Positioned(
top: 5,
right: 5,
child: Row(
children: [
Text(
asset.duration.toString().substring(0, 7),
style: const TextStyle(
color: Colors.white,
fontSize: 10,
),
),
const Icon(
Icons.play_circle_outline_rounded,
color: Colors.white,
),
],
),
);
}
buildAssetStoreLocationIcon() {
return Positioned(
right: 10,
bottom: 5,
child: Icon(
storageIcon(asset),
color: Colors.white,
size: 18,
),
);
}
buildAssetFavoriteIcon() {
return const Positioned(
left: 10,
bottom: 5,
child: Icon(
Icons.favorite,
color: Colors.white,
size: 18,
),
);
}
buildAssetSelectionIcon() {
bool isSelected = selectedAssetsInAlbumViewer.contains(asset);
return Positioned(
left: 10,
top: 5,
child: isSelected
? Icon(
Icons.check_circle_rounded,
color: Theme.of(context).primaryColor,
)
: const Icon(
Icons.check_circle_outline_rounded,
color: Colors.white,
),
);
}
buildThumbnailImage() {
return Container(
decoration: BoxDecoration(border: drawBorderColor()),
child: ImmichImage(asset, width: 300, height: 300),
);
}
handleSelectionGesture() {
if (selectedAssetsInAlbumViewer.contains(asset)) {
ref
.watch(assetSelectionProvider.notifier)
.removeAssetsInAlbumViewer([asset]);
if (selectedAssetsInAlbumViewer.isEmpty) {
disableMultiSelection();
}
} else {
ref
.watch(assetSelectionProvider.notifier)
.addAssetsInAlbumViewer([asset]);
}
}
return GestureDetector(
onTap: isMultiSelectionEnable ? handleSelectionGesture : viewAsset,
onLongPress: enableMultiSelection,
child: Stack(
children: [
buildThumbnailImage(),
if (isFavorite) buildAssetFavoriteIcon(),
if (showStorageIndicator) buildAssetStoreLocationIcon(),
if (!asset.isImage) buildVideoLabel(),
if (isMultiSelectionEnable) buildAssetSelectionIcon(),
],
),
);
}
}

View File

@@ -1,26 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/ui/selection_thumbnail_image.dart';
import 'package:immich_mobile/shared/models/asset.dart';
class AssetGridByMonth extends HookConsumerWidget {
final List<Asset> assetGroup;
const AssetGridByMonth({Key? key, required this.assetGroup})
: super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 5.0,
mainAxisSpacing: 5,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return SelectionThumbnailImage(asset: assetGroup[index]);
},
childCount: assetGroup.length,
),
);
}
}

View File

@@ -1,117 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/shared/models/asset.dart';
class MonthGroupTitle extends HookConsumerWidget {
final String month;
final List<Asset> assetGroup;
const MonthGroupTitle({
Key? key,
required this.month,
required this.assetGroup,
}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedDateGroup = ref.watch(assetSelectionProvider).selectedMonths;
final selectedAssets =
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
handleTitleIconClick() {
HapticFeedback.heavyImpact();
if (isAlbumExist) {
if (selectedDateGroup.contains(month)) {
ref
.watch(assetSelectionProvider.notifier)
.removeAssetsInMonth(month, []);
ref
.watch(assetSelectionProvider.notifier)
.removeSelectedAdditionalAssets(assetGroup);
} else {
ref
.watch(assetSelectionProvider.notifier)
.addAllAssetsInMonth(month, []);
// Deep clone assetGroup
var assetGroupWithNewItems = [...assetGroup];
for (var selectedAsset in selectedAssets) {
assetGroupWithNewItems.removeWhere((a) => a.id == selectedAsset.id);
}
ref
.watch(assetSelectionProvider.notifier)
.addAdditionalAssets(assetGroupWithNewItems);
}
} else {
if (selectedDateGroup.contains(month)) {
ref
.watch(assetSelectionProvider.notifier)
.removeAssetsInMonth(month, assetGroup);
} else {
ref
.watch(assetSelectionProvider.notifier)
.addAllAssetsInMonth(month, assetGroup);
}
}
}
getSimplifiedMonth() {
var monthAndYear = month.split(',');
var yearText = monthAndYear[1].trim();
var monthText = monthAndYear[0].trim();
var currentYear = DateTime.now().year.toString();
if (yearText == currentYear) {
return monthText;
} else {
return month;
}
}
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(
top: 29.0,
bottom: 29.0,
left: 14.0,
right: 8.0,
),
child: Row(
children: [
GestureDetector(
onTap: handleTitleIconClick,
child: selectedDateGroup.contains(month)
? Icon(
Icons.check_circle_rounded,
color: Theme.of(context).primaryColor,
)
: const Icon(
Icons.circle_outlined,
color: Colors.grey,
),
),
GestureDetector(
onTap: handleTitleIconClick,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
getSimplifiedMonth(),
style: TextStyle(
fontSize: 24,
color: Theme.of(context).primaryColor,
),
),
),
),
],
),
),
);
}
}

View File

@@ -1,141 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
class SelectionThumbnailImage extends HookConsumerWidget {
final Asset asset;
const SelectionThumbnailImage({Key? key, required this.asset})
: super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
var selectedAsset =
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
var newAssetsForAlbum =
ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
var isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
Widget buildSelectionIcon(Asset asset) {
var isSelected = selectedAsset.map((item) => item.id).contains(asset.id);
var isNewlySelected =
newAssetsForAlbum.map((item) => item.id).contains(asset.id);
if (isSelected && !isAlbumExist) {
return Icon(
Icons.check_circle,
color: Theme.of(context).primaryColor,
);
} else if (isSelected && isAlbumExist) {
return const Icon(
Icons.check_circle,
color: Color.fromARGB(255, 233, 233, 233),
);
} else if (isNewlySelected && isAlbumExist) {
return Icon(
Icons.check_circle,
color: Theme.of(context).primaryColor,
);
} else {
return const Icon(
Icons.circle_outlined,
color: Colors.white,
);
}
}
BoxBorder drawBorderColor() {
var isSelected = selectedAsset.map((item) => item.id).contains(asset.id);
var isNewlySelected =
newAssetsForAlbum.map((item) => item.id).contains(asset.id);
if (isSelected && !isAlbumExist) {
return Border.all(
color: Theme.of(context).primaryColorLight,
width: 10,
);
} else if (isSelected && isAlbumExist) {
return Border.all(
color: const Color.fromARGB(255, 190, 190, 190),
width: 10,
);
} else if (isNewlySelected && isAlbumExist) {
return Border.all(
color: Theme.of(context).primaryColorLight,
width: 10,
);
}
return const Border();
}
return GestureDetector(
onTap: () {
var isSelected =
selectedAsset.map((item) => item.id).contains(asset.id);
var isNewlySelected =
newAssetsForAlbum.map((item) => item.id).contains(asset.id);
if (isAlbumExist) {
// Operation for existing album
if (!isSelected) {
if (isNewlySelected) {
ref
.watch(assetSelectionProvider.notifier)
.removeSelectedAdditionalAssets([asset]);
} else {
ref
.watch(assetSelectionProvider.notifier)
.addAdditionalAssets([asset]);
}
}
} else {
// Operation for new album
if (isSelected) {
ref
.watch(assetSelectionProvider.notifier)
.removeSelectedNewAssets([asset]);
} else {
ref.watch(assetSelectionProvider.notifier).addNewAssets([asset]);
}
}
},
child: Stack(
children: [
Container(
decoration: BoxDecoration(border: drawBorderColor()),
child: ImmichImage(asset, width: 150, height: 150),
),
Padding(
padding: const EdgeInsets.all(3.0),
child: Align(
alignment: Alignment.topLeft,
child: buildSelectionIcon(asset),
),
),
if (!asset.isImage)
Positioned(
bottom: 5,
right: 5,
child: Row(
children: [
Text(
asset.duration.toString().substring(0, 7),
style: const TextStyle(
color: Colors.white,
fontSize: 10,
),
),
const Icon(
Icons.play_circle_outline_rounded,
color: Colors.white,
),
],
),
),
],
),
);
}
}

View File

@@ -5,21 +5,18 @@ import 'package:easy_localization/easy_localization.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/home/ui/draggable_scrollbar.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.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/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/album/ui/album_action_outlined_button.dart';
import 'package:immich_mobile/modules/album/ui/album_viewer_appbar.dart';
import 'package:immich_mobile/modules/album/ui/album_viewer_editable_title.dart';
import 'package:immich_mobile/modules/album/ui/album_viewer_thumbnail.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/ui/album_viewer_appbar.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_sliver_persistent_app_bar_delegate.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
@@ -32,33 +29,51 @@ class AlbumViewerPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
FocusNode titleFocusNode = useFocusNode();
ScrollController scrollController = useScrollController();
final album = ref.watch(sharedAlbumDetailProvider(albumId));
final userId = ref.watch(authenticationProvider).userId;
final selection = useState<Set<Asset>>({});
final multiSelectEnabled = useState(false);
bool? isTop;
Future<bool> onWillPop() async {
if (multiSelectEnabled.value) {
selection.value = {};
multiSelectEnabled.value = false;
return false;
}
return true;
}
void selectionListener(bool active, Set<Asset> selected) {
selection.value = selected;
multiSelectEnabled.value = selected.isNotEmpty;
}
void disableSelection() {
selection.value = {};
multiSelectEnabled.value = false;
}
/// Find out if the assets in album exist on the device
/// If they exist, add to selected asset state to show they are already selected.
void onAddPhotosPressed(Album albumInfo) async {
if (albumInfo.assets.isNotEmpty == true) {
ref.watch(assetSelectionProvider.notifier).addNewAssets(
albumInfo.assets,
);
}
ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(true);
AssetSelectionPageResult? returnPayload = await AutoRouter.of(context)
.push<AssetSelectionPageResult?>(const AssetSelectionRoute());
AssetSelectionPageResult? returnPayload =
await AutoRouter.of(context).push<AssetSelectionPageResult?>(
AssetSelectionRoute(
existingAssets: albumInfo.assets,
isNewAlbum: false,
),
);
if (returnPayload != null) {
// Check if there is new assets add
if (returnPayload.selectedAdditionalAsset.isNotEmpty) {
if (returnPayload.selectedAssets.isNotEmpty) {
ImmichLoadingOverlayController.appLoader.show();
var addAssetsResult =
await ref.watch(albumServiceProvider).addAdditionalAssetToAlbum(
returnPayload.selectedAdditionalAsset,
returnPayload.selectedAssets,
albumInfo,
);
@@ -70,10 +85,6 @@ class AlbumViewerPage extends HookConsumerWidget {
ImmichLoadingOverlayController.appLoader.hide();
}
ref.watch(assetSelectionProvider.notifier).removeAll();
} else {
ref.watch(assetSelectionProvider.notifier).removeAll();
}
}
@@ -91,13 +102,38 @@ class AlbumViewerPage extends HookConsumerWidget {
.addAdditionalUserToAlbum(sharedUserIds, album);
if (isSuccess) {
ref.invalidate(sharedAlbumDetailProvider(albumId));
ref.invalidate(sharedAlbumDetailProvider(album.id));
}
ImmichLoadingOverlayController.appLoader.hide();
}
}
Widget buildControlButton(Album album) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8),
child: SizedBox(
height: 40,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
AlbumActionOutlinedButton(
iconData: Icons.add_photo_alternate_outlined,
onPressed: () => onAddPhotosPressed(album),
labelText: "share_add_photos".tr(),
),
if (userId == album.ownerId)
AlbumActionOutlinedButton(
iconData: Icons.person_add_alt_rounded,
onPressed: () => onAddUsersPressed(album),
labelText: "album_viewer_page_share_add_users".tr(),
),
],
),
),
);
}
Widget buildTitle(Album album) {
return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 16),
@@ -146,171 +182,104 @@ class AlbumViewerPage extends HookConsumerWidget {
}
Widget buildHeader(Album album) {
return SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildTitle(album),
if (album.assets.isNotEmpty == true) buildAlbumDateRange(album),
if (album.shared)
SizedBox(
height: 60,
child: ListView.builder(
padding: const EdgeInsets.only(left: 16),
scrollDirection: Axis.horizontal,
itemBuilder: ((context, index) {
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: CircleAvatar(
backgroundColor: Colors.grey[300],
radius: 18,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child: Image.asset(
'assets/immich-logo-no-outline.png',
),
return Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildTitle(album),
if (album.assets.isNotEmpty == true) buildAlbumDateRange(album),
if (album.shared)
SizedBox(
height: 50,
child: ListView.builder(
padding: const EdgeInsets.only(left: 16),
scrollDirection: Axis.horizontal,
itemBuilder: ((context, index) {
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: CircleAvatar(
backgroundColor: Colors.grey[300],
radius: 18,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child: Image.asset(
'assets/immich-logo-no-outline.png',
),
),
),
);
}),
itemCount: album.sharedUsers.length,
),
)
],
),
);
}
Widget buildImageGrid(Album album) {
final appSettingService = ref.watch(appSettingsServiceProvider);
final bool showStorageIndicator =
appSettingService.getSetting(AppSettingsEnum.storageIndicator);
if (album.sortedAssets.isNotEmpty) {
return SliverPadding(
padding: const EdgeInsets.only(top: 10.0),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount:
appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
crossAxisSpacing: 5.0,
mainAxisSpacing: 5,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return AlbumViewerThumbnail(
asset: album.sortedAssets[index],
assetList: album.sortedAssets,
showStorageIndicator: showStorageIndicator,
);
},
childCount: album.assetCount,
),
),
);
}
return const SliverToBoxAdapter();
}
Widget buildControlButton(Album album) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8),
child: SizedBox(
height: 40,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
AlbumActionOutlinedButton(
iconData: Icons.add_photo_alternate_outlined,
onPressed: () => onAddPhotosPressed(album),
labelText: "share_add_photos".tr(),
),
if (userId == album.ownerId)
AlbumActionOutlinedButton(
iconData: Icons.person_add_alt_rounded,
onPressed: () => onAddUsersPressed(album),
labelText: "album_viewer_page_share_add_users".tr(),
),
],
),
),
);
}
Future<bool> onWillPop() async {
final isMultiselectEnable = ref
.read(assetSelectionProvider)
.selectedAssetsInAlbumViewer
.isNotEmpty;
if (isMultiselectEnable) {
ref.watch(assetSelectionProvider.notifier).removeAll();
return false;
}
return true;
}
Widget buildBody(Album album) {
return WillPopScope(
onWillPop: onWillPop,
child: GestureDetector(
onTap: () {
titleFocusNode.unfocus();
},
child: DraggableScrollbar.semicircle(
backgroundColor: Theme.of(context).hintColor,
controller: scrollController,
heightScrollThumb: 48.0,
child: CustomScrollView(
controller: scrollController,
slivers: [
buildHeader(album),
if (album.isRemote)
SliverPersistentHeader(
pinned: true,
delegate: ImmichSliverPersistentAppBarDelegate(
minHeight: 50,
maxHeight: 50,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: buildControlButton(album),
),
),
),
SliverSafeArea(
sliver: buildImageGrid(album),
),
],
);
}),
itemCount: album.sharedUsers.length,
),
),
),
),
],
);
}
final scroll = ScrollController();
return Scaffold(
appBar: album.when(
data: (Album? data) {
if (data != null) {
return AlbumViewerAppbar(
album: data,
userId: userId,
);
}
return null;
},
error: (e, _) => null,
loading: () => null,
data: (data) => AlbumViewerAppbar(
titleFocusNode: titleFocusNode,
album: data,
userId: userId,
selected: selection.value,
selectionDisabled: disableSelection,
),
error: (error, stackTrace) => AppBar(title: const Text("Error")),
loading: () => AppBar(),
),
body: album.when(
data: (albumInfo) => albumInfo != null
? buildBody(albumInfo)
: const Center(
child: CircularProgressIndicator(),
data: (data) => WillPopScope(
onWillPop: onWillPop,
child: GestureDetector(
onTap: () {
titleFocusNode.unfocus();
},
child: NestedScrollView(
controller: scroll,
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverToBoxAdapter(child: buildHeader(data)),
SliverPersistentHeader(
pinned: true,
delegate: ImmichSliverPersistentAppBarDelegate(
minHeight: 50,
maxHeight: 50,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: buildControlButton(data),
),
),
)
],
body: ImmichAssetGrid(
renderList: data.renderList,
listener: selectionListener,
selectionActive: multiSelectEnabled.value,
showMultiSelectIndicator: false,
visibleItemsListener: (start, end) {
final top = start.index == 0 && start.itemLeadingEdge == 0.0;
if (top != isTop) {
isTop = top;
scroll.animateTo(
top
? scroll.position.minScrollExtent
: scroll.position.maxScrollExtent,
duration: const Duration(milliseconds: 500),
curve: top ? Curves.easeOut : Curves.easeIn,
);
}
},
),
error: (e, _) => Center(child: Text("Error loading album info $e")),
),
),
),
error: (e, _) => Center(child: Text("Error loading album info!\n$e")),
loading: () => const Center(
child: ImmichLoadingIndicator(),
),

View File

@@ -4,54 +4,42 @@ 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/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/album/ui/asset_grid_by_month.dart';
import 'package:immich_mobile/modules/album/ui/month_group_title.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/modules/home/ui/draggable_scrollbar.dart';
class AssetSelectionPage extends HookConsumerWidget {
const AssetSelectionPage({Key? key}) : super(key: key);
const AssetSelectionPage({
Key? key,
required this.existingAssets,
this.isNewAlbum = false,
}) : super(key: key);
final Set<Asset> existingAssets;
final bool isNewAlbum;
@override
Widget build(BuildContext context, WidgetRef ref) {
ScrollController scrollController = useScrollController();
var assetGroupMonthYear = ref.watch(assetGroupByMonthYearProvider);
final selectedAssets =
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
final newAssetsForAlbum =
ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
List<Widget> imageGridGroup = [];
final renderList = ref.watch(remoteAssetsProvider);
final selected = useState<Set<Asset>>(existingAssets);
final selectionEnabledHook = useState(true);
String buildAssetCountText() {
if (isAlbumExist) {
return (selectedAssets.length + newAssetsForAlbum.length).toString();
} else {
return selectedAssets.length.toString();
}
return selected.value.length.toString();
}
Widget buildBody() {
assetGroupMonthYear.forEach((monthYear, assetGroup) {
imageGridGroup
.add(MonthGroupTitle(month: monthYear, assetGroup: assetGroup));
imageGridGroup.add(AssetGridByMonth(assetGroup: assetGroup));
});
return Stack(
children: [
DraggableScrollbar.semicircle(
backgroundColor: Theme.of(context).hintColor,
controller: scrollController,
heightScrollThumb: 48.0,
child: CustomScrollView(
controller: scrollController,
slivers: [...imageGridGroup],
),
),
],
Widget buildBody(RenderList renderList) {
return ImmichAssetGrid(
renderList: renderList,
listener: (active, assets) {
selectionEnabledHook.value = active;
selected.value = assets;
},
selectionActive: true,
preselectedAssets: isNewAlbum ? selected.value : existingAssets,
canDeselect: isNewAlbum,
showMultiSelectIndicator: false,
);
}
@@ -61,11 +49,10 @@ class AssetSelectionPage extends HookConsumerWidget {
leading: IconButton(
icon: const Icon(Icons.close_rounded),
onPressed: () {
ref.watch(assetSelectionProvider.notifier).removeAll();
AutoRouter.of(context).pop(null);
AutoRouter.of(context).popForced(null);
},
),
title: selectedAssets.isEmpty
title: selected.value.isEmpty
? const Text(
'share_add_photos',
style: TextStyle(fontSize: 18),
@@ -76,16 +63,13 @@ class AssetSelectionPage extends HookConsumerWidget {
),
centerTitle: false,
actions: [
if ((!isAlbumExist && selectedAssets.isNotEmpty) ||
(isAlbumExist && newAssetsForAlbum.isNotEmpty))
if (selected.value.isNotEmpty)
TextButton(
onPressed: () {
var payload = AssetSelectionPageResult(
isAlbumExist: isAlbumExist,
selectedAdditionalAsset: newAssetsForAlbum,
selectedNewAsset: selectedAssets,
);
AutoRouter.of(context).pop(payload);
var payload =
AssetSelectionPageResult(selectedAssets: selected.value);
AutoRouter.of(context)
.popForced<AssetSelectionPageResult>(payload);
},
child: const Text(
"share_add",
@@ -94,7 +78,13 @@ class AssetSelectionPage extends HookConsumerWidget {
),
],
),
body: buildBody(),
body: renderList.when(
data: (data) => buildBody(data),
error: (error, stackTrace) => Center(
child: Text(error.toString()),
),
loading: () => const Center(child: CircularProgressIndicator()),
),
);
}
}

View File

@@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/providers/album_title.provider.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/album/ui/album_action_outlined_button.dart';
import 'package:immich_mobile/modules/album/ui/album_title_text_field.dart';
import 'package:immich_mobile/modules/album/ui/shared_album_thumbnail_image.dart';
@@ -31,12 +30,15 @@ class CreateAlbumPage extends HookConsumerWidget {
final albumTitleTextFieldFocusNode = useFocusNode();
final isAlbumTitleTextFieldFocus = useState(false);
final isAlbumTitleEmpty = useState(true);
final selectedAssets =
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
final selectedAssets = useState<Set<Asset>>(const {});
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
showSelectUserPage() {
AutoRouter.of(context).push(const SelectUserForSharingRoute());
showSelectUserPage() async {
final bool? ok = await AutoRouter.of(context)
.push<bool?>(SelectUserForSharingRoute(assets: selectedAssets.value));
if (ok == true) {
selectedAssets.value = {};
}
}
void onBackgroundTapped() {
@@ -52,13 +54,17 @@ class CreateAlbumPage extends HookConsumerWidget {
}
onSelectPhotosButtonPressed() async {
ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(false);
AssetSelectionPageResult? selectedAsset = await AutoRouter.of(context)
.push<AssetSelectionPageResult?>(const AssetSelectionRoute());
AssetSelectionPageResult? selectedAsset =
await AutoRouter.of(context).push<AssetSelectionPageResult?>(
AssetSelectionRoute(
existingAssets: selectedAssets.value,
isNewAlbum: true,
),
);
if (selectedAsset == null) {
ref.watch(assetSelectionProvider.notifier).removeAll();
selectedAssets.value = const {};
} else {
selectedAssets.value = selectedAsset.selectedAssets;
}
}
@@ -78,7 +84,7 @@ class CreateAlbumPage extends HookConsumerWidget {
}
buildTitle() {
if (selectedAssets.isEmpty) {
if (selectedAssets.value.isEmpty) {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 200, left: 18),
@@ -97,7 +103,7 @@ class CreateAlbumPage extends HookConsumerWidget {
}
buildSelectPhotosButton() {
if (selectedAssets.isEmpty) {
if (selectedAssets.value.isEmpty) {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 16, left: 18, right: 18),
@@ -158,7 +164,7 @@ class CreateAlbumPage extends HookConsumerWidget {
}
buildSelectedImageGrid() {
if (selectedAssets.isNotEmpty) {
if (selectedAssets.value.isNotEmpty) {
return SliverPadding(
padding: const EdgeInsets.only(top: 16),
sliver: SliverGrid(
@@ -172,11 +178,11 @@ class CreateAlbumPage extends HookConsumerWidget {
return GestureDetector(
onTap: onBackgroundTapped,
child: SharedAlbumThumbnailImage(
asset: selectedAssets.elementAt(index),
asset: selectedAssets.value.elementAt(index),
),
);
},
childCount: selectedAssets.length,
childCount: selectedAssets.value.length,
),
),
);
@@ -188,12 +194,12 @@ class CreateAlbumPage extends HookConsumerWidget {
createNonSharedAlbum() async {
var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(
ref.watch(albumTitleProvider),
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum,
selectedAssets.value,
);
if (newAlbum != null) {
ref.watch(albumProvider.notifier).getAllAlbums();
ref.watch(assetSelectionProvider.notifier).removeAll();
selectedAssets.value = {};
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
AutoRouter.of(context).replace(AlbumViewerRoute(albumId: newAlbum.id));
@@ -207,7 +213,7 @@ class CreateAlbumPage extends HookConsumerWidget {
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
leading: IconButton(
onPressed: () {
ref.watch(assetSelectionProvider.notifier).removeAll();
selectedAssets.value = {};
AutoRouter.of(context).pop();
},
icon: const Icon(Icons.close_rounded),
@@ -237,7 +243,7 @@ class CreateAlbumPage extends HookConsumerWidget {
if (!isSharedAlbum)
TextButton(
onPressed: albumTitleController.text.isNotEmpty &&
selectedAssets.isNotEmpty
selectedAssets.value.isNotEmpty
? createNonSharedAlbum
: null,
child: Text(
@@ -264,7 +270,7 @@ class CreateAlbumPage extends HookConsumerWidget {
child: Column(
children: [
buildTitleInputField(),
if (selectedAssets.isNotEmpty) buildControlButton(),
if (selectedAssets.value.isNotEmpty) buildControlButton(),
],
),
),

View File

@@ -4,15 +4,18 @@ 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_title.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/providers/suggested_shared_users.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
class SelectUserForSharingPage extends HookConsumerWidget {
const SelectUserForSharingPage({Key? key}) : super(key: key);
const SelectUserForSharingPage({Key? key, required this.assets})
: super(key: key);
final Set<Asset> assets;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -24,15 +27,15 @@ class SelectUserForSharingPage extends HookConsumerWidget {
var newAlbum =
await ref.watch(sharedAlbumProvider.notifier).createSharedAlbum(
ref.watch(albumTitleProvider),
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum,
assets,
sharedUsersList.value,
);
if (newAlbum != null) {
await ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
ref.watch(assetSelectionProvider.notifier).removeAll();
// ref.watch(assetSelectionProvider.notifier).removeAll();
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
AutoRouter.of(context).pop(true);
AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [SharingRoute()]));
}