Fix backup not resuming after closed and reopen (#266)

* Fixed app not resuming backup after closing and reopening the app

* Fixed cosmetic effect of backup button doesn't change state right away after pressing start backup

* Fixed grammar

* Fixed deep copy problem that cause incorrect asset count when backing up

* Format code
This commit is contained in:
Alex 2022-06-25 15:12:47 -05:00 committed by GitHub
parent d02b97e1c1
commit 40a8115101
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 677 additions and 300 deletions

View file

@ -36,16 +36,20 @@ class AlbumViewerPageState {
String toJson() => json.encode(toMap());
factory AlbumViewerPageState.fromJson(String source) => AlbumViewerPageState.fromMap(json.decode(source));
factory AlbumViewerPageState.fromJson(String source) =>
AlbumViewerPageState.fromMap(json.decode(source));
@override
String toString() => 'AlbumViewerPageState(isEditAlbum: $isEditAlbum, editTitleText: $editTitleText)';
String toString() =>
'AlbumViewerPageState(isEditAlbum: $isEditAlbum, editTitleText: $editTitleText)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is AlbumViewerPageState && other.isEditAlbum == isEditAlbum && other.editTitleText == editTitleText;
return other is AlbumViewerPageState &&
other.isEditAlbum == isEditAlbum &&
other.editTitleText == editTitleText;
}
@override

View file

@ -22,7 +22,8 @@ class AssetSelectionPageResult {
}) {
return AssetSelectionPageResult(
selectedNewAsset: selectedNewAsset ?? this.selectedNewAsset,
selectedAdditionalAsset: selectedAdditionalAsset ?? this.selectedAdditionalAsset,
selectedAdditionalAsset:
selectedAdditionalAsset ?? this.selectedAdditionalAsset,
isAlbumExist: isAlbumExist ?? this.isAlbumExist,
);
}
@ -30,8 +31,12 @@ class AssetSelectionPageResult {
Map<String, dynamic> toMap() {
final result = <String, dynamic>{};
result.addAll({'selectedNewAsset': selectedNewAsset.map((x) => x.toMap()).toList()});
result.addAll({'selectedAdditionalAsset': selectedAdditionalAsset.map((x) => x.toMap()).toList()});
result.addAll(
{'selectedNewAsset': selectedNewAsset.map((x) => x.toMap()).toList()});
result.addAll({
'selectedAdditionalAsset':
selectedAdditionalAsset.map((x) => x.toMap()).toList()
});
result.addAll({'isAlbumExist': isAlbumExist});
return result;
@ -39,16 +44,18 @@ class AssetSelectionPageResult {
factory AssetSelectionPageResult.fromMap(Map<String, dynamic> map) {
return AssetSelectionPageResult(
selectedNewAsset: Set<ImmichAsset>.from(map['selectedNewAsset']?.map((x) => ImmichAsset.fromMap(x))),
selectedAdditionalAsset:
Set<ImmichAsset>.from(map['selectedAdditionalAsset']?.map((x) => ImmichAsset.fromMap(x))),
selectedNewAsset: Set<ImmichAsset>.from(
map['selectedNewAsset']?.map((x) => ImmichAsset.fromMap(x))),
selectedAdditionalAsset: Set<ImmichAsset>.from(
map['selectedAdditionalAsset']?.map((x) => ImmichAsset.fromMap(x))),
isAlbumExist: map['isAlbumExist'] ?? false,
);
}
String toJson() => json.encode(toMap());
factory AssetSelectionPageResult.fromJson(String source) => AssetSelectionPageResult.fromMap(json.decode(source));
factory AssetSelectionPageResult.fromJson(String source) =>
AssetSelectionPageResult.fromMap(json.decode(source));
@override
String toString() =>
@ -66,5 +73,8 @@ class AssetSelectionPageResult {
}
@override
int get hashCode => selectedNewAsset.hashCode ^ selectedAdditionalAsset.hashCode ^ isAlbumExist.hashCode;
int get hashCode =>
selectedNewAsset.hashCode ^
selectedAdditionalAsset.hashCode ^
isAlbumExist.hashCode;
}

View file

@ -32,9 +32,12 @@ class AssetSelectionState {
}) {
return AssetSelectionState(
selectedMonths: selectedMonths ?? this.selectedMonths,
selectedNewAssetsForAlbum: selectedNewAssetsForAlbum ?? this.selectedNewAssetsForAlbum,
selectedAdditionalAssetsForAlbum: selectedAdditionalAssetsForAlbum ?? this.selectedAdditionalAssetsForAlbum,
selectedAssetsInAlbumViewer: selectedAssetsInAlbumViewer ?? this.selectedAssetsInAlbumViewer,
selectedNewAssetsForAlbum:
selectedNewAssetsForAlbum ?? this.selectedNewAssetsForAlbum,
selectedAdditionalAssetsForAlbum: selectedAdditionalAssetsForAlbum ??
this.selectedAdditionalAssetsForAlbum,
selectedAssetsInAlbumViewer:
selectedAssetsInAlbumViewer ?? this.selectedAssetsInAlbumViewer,
isMultiselectEnable: isMultiselectEnable ?? this.isMultiselectEnable,
isAlbumExist: isAlbumExist ?? this.isAlbumExist,
);
@ -44,10 +47,18 @@ class AssetSelectionState {
final result = <String, dynamic>{};
result.addAll({'selectedMonths': selectedMonths.toList()});
result.addAll({'selectedNewAssetsForAlbum': selectedNewAssetsForAlbum.map((x) => x.toMap()).toList()});
result
.addAll({'selectedAdditionalAssetsForAlbum': selectedAdditionalAssetsForAlbum.map((x) => x.toMap()).toList()});
result.addAll({'selectedAssetsInAlbumViewer': selectedAssetsInAlbumViewer.map((x) => x.toMap()).toList()});
result.addAll({
'selectedNewAssetsForAlbum':
selectedNewAssetsForAlbum.map((x) => x.toMap()).toList()
});
result.addAll({
'selectedAdditionalAssetsForAlbum':
selectedAdditionalAssetsForAlbum.map((x) => x.toMap()).toList()
});
result.addAll({
'selectedAssetsInAlbumViewer':
selectedAssetsInAlbumViewer.map((x) => x.toMap()).toList()
});
result.addAll({'isMultiselectEnable': isMultiselectEnable});
result.addAll({'isAlbumExist': isAlbumExist});
@ -57,12 +68,14 @@ class AssetSelectionState {
factory AssetSelectionState.fromMap(Map<String, dynamic> map) {
return AssetSelectionState(
selectedMonths: Set<String>.from(map['selectedMonths']),
selectedNewAssetsForAlbum:
Set<ImmichAsset>.from(map['selectedNewAssetsForAlbum']?.map((x) => ImmichAsset.fromMap(x))),
selectedAdditionalAssetsForAlbum:
Set<ImmichAsset>.from(map['selectedAdditionalAssetsForAlbum']?.map((x) => ImmichAsset.fromMap(x))),
selectedAssetsInAlbumViewer:
Set<ImmichAsset>.from(map['selectedAssetsInAlbumViewer']?.map((x) => ImmichAsset.fromMap(x))),
selectedNewAssetsForAlbum: Set<ImmichAsset>.from(
map['selectedNewAssetsForAlbum']?.map((x) => ImmichAsset.fromMap(x))),
selectedAdditionalAssetsForAlbum: Set<ImmichAsset>.from(
map['selectedAdditionalAssetsForAlbum']
?.map((x) => ImmichAsset.fromMap(x))),
selectedAssetsInAlbumViewer: Set<ImmichAsset>.from(
map['selectedAssetsInAlbumViewer']
?.map((x) => ImmichAsset.fromMap(x))),
isMultiselectEnable: map['isMultiselectEnable'] ?? false,
isAlbumExist: map['isAlbumExist'] ?? false,
);
@ -70,7 +83,8 @@ class AssetSelectionState {
String toJson() => json.encode(toMap());
factory AssetSelectionState.fromJson(String source) => AssetSelectionState.fromMap(json.decode(source));
factory AssetSelectionState.fromJson(String source) =>
AssetSelectionState.fromMap(json.decode(source));
@override
String toString() {
@ -85,8 +99,10 @@ class AssetSelectionState {
return other is AssetSelectionState &&
setEquals(other.selectedMonths, selectedMonths) &&
setEquals(other.selectedNewAssetsForAlbum, selectedNewAssetsForAlbum) &&
setEquals(other.selectedAdditionalAssetsForAlbum, selectedAdditionalAssetsForAlbum) &&
setEquals(other.selectedAssetsInAlbumViewer, selectedAssetsInAlbumViewer) &&
setEquals(other.selectedAdditionalAssetsForAlbum,
selectedAdditionalAssetsForAlbum) &&
setEquals(
other.selectedAssetsInAlbumViewer, selectedAssetsInAlbumViewer) &&
other.isMultiselectEnable == isMultiselectEnable &&
other.isAlbumExist == isAlbumExist;
}

View file

@ -38,7 +38,8 @@ class SharedAlbum {
ownerId: ownerId ?? this.ownerId,
albumName: albumName ?? this.albumName,
createdAt: createdAt ?? this.createdAt,
albumThumbnailAssetId: albumThumbnailAssetId ?? this.albumThumbnailAssetId,
albumThumbnailAssetId:
albumThumbnailAssetId ?? this.albumThumbnailAssetId,
sharedUsers: sharedUsers ?? this.sharedUsers,
assets: assets ?? this.assets,
);
@ -69,16 +70,19 @@ class SharedAlbum {
albumName: map['albumName'] ?? '',
createdAt: map['createdAt'] ?? '',
albumThumbnailAssetId: map['albumThumbnailAssetId'],
sharedUsers: List<User>.from(map['sharedUsers']?.map((x) => User.fromMap(x))),
sharedUsers:
List<User>.from(map['sharedUsers']?.map((x) => User.fromMap(x))),
assets: map['assets'] != null
? List<ImmichAsset>.from(map['assets']?.map((x) => ImmichAsset.fromMap(x)))
? List<ImmichAsset>.from(
map['assets']?.map((x) => ImmichAsset.fromMap(x)))
: null,
);
}
String toJson() => json.encode(toMap());
factory SharedAlbum.fromJson(String source) => SharedAlbum.fromMap(json.decode(source));
factory SharedAlbum.fromJson(String source) =>
SharedAlbum.fromMap(json.decode(source));
@override
String toString() {

View file

@ -12,4 +12,5 @@ class AlbumTitleNotifier extends StateNotifier<String> {
}
}
final albumTitleProvider = StateNotifierProvider<AlbumTitleNotifier, String>((ref) => AlbumTitleNotifier());
final albumTitleProvider = StateNotifierProvider<AlbumTitleNotifier, String>(
(ref) => AlbumTitleNotifier());

View file

@ -18,35 +18,48 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
state = state.copyWith(isAlbumExist: isAlbumExist);
}
void removeAssetsInMonth(String removedMonth, List<ImmichAsset> assetsInMonth) {
void removeAssetsInMonth(
String removedMonth, List<ImmichAsset> assetsInMonth) {
Set<ImmichAsset> currentAssetList = state.selectedNewAssetsForAlbum;
Set<String> currentMonthList = state.selectedMonths;
currentMonthList.removeWhere((selectedMonth) => selectedMonth == removedMonth);
currentMonthList
.removeWhere((selectedMonth) => selectedMonth == removedMonth);
for (ImmichAsset asset in assetsInMonth) {
currentAssetList.removeWhere((e) => e.id == asset.id);
}
state = state.copyWith(selectedNewAssetsForAlbum: currentAssetList, selectedMonths: currentMonthList);
state = state.copyWith(
selectedNewAssetsForAlbum: currentAssetList,
selectedMonths: currentMonthList);
}
void addAdditionalAssets(List<ImmichAsset> assets) {
state = state.copyWith(
selectedAdditionalAssetsForAlbum: {...state.selectedAdditionalAssetsForAlbum, ...assets},
selectedAdditionalAssetsForAlbum: {
...state.selectedAdditionalAssetsForAlbum,
...assets
},
);
}
void addAllAssetsInMonth(String month, List<ImmichAsset> assetsInMonth) {
state = state.copyWith(
selectedMonths: {...state.selectedMonths, month},
selectedNewAssetsForAlbum: {...state.selectedNewAssetsForAlbum, ...assetsInMonth},
selectedNewAssetsForAlbum: {
...state.selectedNewAssetsForAlbum,
...assetsInMonth
},
);
}
void addNewAssets(List<ImmichAsset> assets) {
state = state.copyWith(
selectedNewAssetsForAlbum: {...state.selectedNewAssetsForAlbum, ...assets},
selectedNewAssetsForAlbum: {
...state.selectedNewAssetsForAlbum,
...assets
},
);
}
@ -93,7 +106,10 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
void addAssetsInAlbumViewer(List<ImmichAsset> assets) {
state = state.copyWith(
selectedAssetsInAlbumViewer: {...state.selectedAssetsInAlbumViewer, ...assets},
selectedAssetsInAlbumViewer: {
...state.selectedAssetsInAlbumViewer,
...assets
},
);
}
@ -108,6 +124,7 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
}
}
final assetSelectionProvider = StateNotifierProvider<AssetSelectionNotifier, AssetSelectionState>((ref) {
final assetSelectionProvider =
StateNotifierProvider<AssetSelectionNotifier, AssetSelectionState>((ref) {
return AssetSelectionNotifier();
});

View file

@ -5,7 +5,11 @@ class AlbumActionOutlinedButton extends StatelessWidget {
final String labelText;
final IconData iconData;
const AlbumActionOutlinedButton({Key? key, this.onPressed, required this.labelText, required this.iconData})
const AlbumActionOutlinedButton(
{Key? key,
this.onPressed,
required this.labelText,
required this.iconData})
: super(key: key);
@override
@ -26,7 +30,8 @@ class AlbumActionOutlinedButton extends StatelessWidget {
icon: Icon(iconData, size: 15),
label: Text(
labelText,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black87),
style: const TextStyle(
fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black87),
),
onPressed: onPressed,
),

View file

@ -29,7 +29,8 @@ class AlbumTitleTextField extends ConsumerWidget {
ref.watch(albumTitleProvider.notifier).setAlbumTitle(v);
},
focusNode: albumTitleTextFieldFocusNode,
style: TextStyle(fontSize: 28, color: Colors.grey[700], fontWeight: FontWeight.bold),
style: TextStyle(
fontSize: 28, color: Colors.grey[700], fontWeight: FontWeight.bold),
controller: albumTitleController,
onTap: () {
isAlbumTitleTextFieldFocus.value = true;

View file

@ -7,11 +7,14 @@ import 'package:immich_mobile/modules/sharing/providers/album_viewer.provider.da
class AlbumViewerEditableTitle extends HookConsumerWidget {
final SharedAlbum albumInfo;
final FocusNode titleFocusNode;
const AlbumViewerEditableTitle({Key? key, required this.albumInfo, required this.titleFocusNode}) : super(key: key);
const AlbumViewerEditableTitle(
{Key? key, required this.albumInfo, required this.titleFocusNode})
: super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final titleTextEditController = useTextEditingController(text: albumInfo.albumName);
final titleTextEditController =
useTextEditingController(text: albumInfo.albumName);
void onFocusModeChange() {
if (!titleFocusNode.hasFocus && titleTextEditController.text.isEmpty) {
@ -40,7 +43,9 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
onTap: () {
FocusScope.of(context).requestFocus(titleFocusNode);
ref.watch(albumViewerProvider.notifier).setEditTitleText(albumInfo.albumName);
ref
.watch(albumViewerProvider.notifier)
.setEditTitleText(albumInfo.albumName);
ref.watch(albumViewerProvider.notifier).enableEditAlbum();
if (titleTextEditController.text == 'Untitled') {

View file

@ -22,8 +22,10 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
var thumbnailRequestUrl =
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
var deviceId = ref.watch(authenticationProvider).deviceId;
final selectedAssetsInAlbumViewer = ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
final isMultiSelectionEnable = ref.watch(assetSelectionProvider).isMultiselectEnable;
final selectedAssetsInAlbumViewer =
ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
final isMultiSelectionEnable =
ref.watch(assetSelectionProvider).isMultiselectEnable;
_viewAsset() {
if (asset.type == 'IMAGE') {
@ -39,7 +41,8 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
} else {
AutoRouter.of(context).push(
VideoViewerRoute(
videoUrl: '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
videoUrl:
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
asset: asset),
);
}
@ -58,7 +61,9 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
_enableMultiSelection() {
ref.watch(assetSelectionProvider.notifier).enableMultiselection();
ref.watch(assetSelectionProvider.notifier).addAssetsInAlbumViewer([asset]);
ref
.watch(assetSelectionProvider.notifier)
.addAssetsInAlbumViewer([asset]);
}
_disableMultiSelection() {
@ -96,7 +101,9 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
right: 10,
bottom: 5,
child: Icon(
(deviceId != asset.deviceId) ? Icons.cloud_done_outlined : Icons.photo_library_rounded,
(deviceId != asset.deviceId)
? Icons.cloud_done_outlined
: Icons.photo_library_rounded,
color: Colors.white,
size: 18,
),
@ -136,7 +143,8 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
imageUrl: thumbnailRequestUrl,
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
fadeInDuration: const Duration(milliseconds: 250),
progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
progressIndicatorBuilder: (context, url, downloadProgress) =>
Transform.scale(
scale: 0.2,
child: CircularProgressIndicator(value: downloadProgress.progress),
),
@ -152,13 +160,17 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
_handleSelectionGesture() {
if (selectedAssetsInAlbumViewer.contains(asset)) {
ref.watch(assetSelectionProvider.notifier).removeAssetsInAlbumViewer([asset]);
ref
.watch(assetSelectionProvider.notifier)
.removeAssetsInAlbumViewer([asset]);
if (selectedAssetsInAlbumViewer.isEmpty) {
_disableMultiSelection();
}
} else {
ref.watch(assetSelectionProvider.notifier).addAssetsInAlbumViewer([asset]);
ref
.watch(assetSelectionProvider.notifier)
.addAssetsInAlbumViewer([asset]);
}
}

View file

@ -5,7 +5,8 @@ import 'package:immich_mobile/shared/models/immich_asset.model.dart';
class AssetGridByMonth extends HookConsumerWidget {
final List<ImmichAsset> assetGroup;
const AssetGridByMonth({Key? key, required this.assetGroup}) : super(key: key);
const AssetGridByMonth({Key? key, required this.assetGroup})
: super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return SliverGrid(

View file

@ -8,12 +8,15 @@ class MonthGroupTitle extends HookConsumerWidget {
final String month;
final List<ImmichAsset> assetGroup;
const MonthGroupTitle({Key? key, required this.month, required this.assetGroup}) : super(key: key);
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 selectedAssets =
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
_handleTitleIconClick() {
@ -21,10 +24,16 @@ class MonthGroupTitle extends HookConsumerWidget {
if (isAlbumExist) {
if (selectedDateGroup.contains(month)) {
ref.watch(assetSelectionProvider.notifier).removeAssetsInMonth(month, []);
ref.watch(assetSelectionProvider.notifier).removeSelectedAdditionalAssets(assetGroup);
ref
.watch(assetSelectionProvider.notifier)
.removeAssetsInMonth(month, []);
ref
.watch(assetSelectionProvider.notifier)
.removeSelectedAdditionalAssets(assetGroup);
} else {
ref.watch(assetSelectionProvider.notifier).addAllAssetsInMonth(month, []);
ref
.watch(assetSelectionProvider.notifier)
.addAllAssetsInMonth(month, []);
// Deep clone assetGroup
var assetGroupWithNewItems = [...assetGroup];
@ -33,13 +42,19 @@ class MonthGroupTitle extends HookConsumerWidget {
assetGroupWithNewItems.removeWhere((a) => a.id == selectedAsset.id);
}
ref.watch(assetSelectionProvider.notifier).addAdditionalAssets(assetGroupWithNewItems);
ref
.watch(assetSelectionProvider.notifier)
.addAdditionalAssets(assetGroupWithNewItems);
}
} else {
if (selectedDateGroup.contains(month)) {
ref.watch(assetSelectionProvider.notifier).removeAssetsInMonth(month, assetGroup);
ref
.watch(assetSelectionProvider.notifier)
.removeAssetsInMonth(month, assetGroup);
} else {
ref.watch(assetSelectionProvider.notifier).addAllAssetsInMonth(month, assetGroup);
ref
.watch(assetSelectionProvider.notifier)
.addAllAssetsInMonth(month, assetGroup);
}
}
}
@ -59,7 +74,8 @@ class MonthGroupTitle extends HookConsumerWidget {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 29.0, bottom: 29.0, left: 14.0, right: 8.0),
padding: const EdgeInsets.only(
top: 29.0, bottom: 29.0, left: 14.0, right: 8.0),
child: Row(
children: [
GestureDetector(

View file

@ -10,7 +10,8 @@ import 'package:immich_mobile/shared/models/immich_asset.model.dart';
class SelectionThumbnailImage extends HookConsumerWidget {
final ImmichAsset asset;
const SelectionThumbnailImage({Key? key, required this.asset}) : super(key: key);
const SelectionThumbnailImage({Key? key, required this.asset})
: super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -18,8 +19,10 @@ class SelectionThumbnailImage extends HookConsumerWidget {
var box = Hive.box(userInfoBox);
var thumbnailRequestUrl =
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
var selectedAsset = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
var newAssetsForAlbum = ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
var selectedAsset =
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
var newAssetsForAlbum =
ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
var isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
Widget _buildSelectionIcon(ImmichAsset asset) {
@ -72,15 +75,21 @@ class SelectionThumbnailImage extends HookConsumerWidget {
// Operation for existing album
if (!selectedAsset.contains(asset)) {
if (newAssetsForAlbum.contains(asset)) {
ref.watch(assetSelectionProvider.notifier).removeSelectedAdditionalAssets([asset]);
ref
.watch(assetSelectionProvider.notifier)
.removeSelectedAdditionalAssets([asset]);
} else {
ref.watch(assetSelectionProvider.notifier).addAdditionalAssets([asset]);
ref
.watch(assetSelectionProvider.notifier)
.addAdditionalAssets([asset]);
}
}
} else {
// Operation for new album
if (selectedAsset.contains(asset)) {
ref.watch(assetSelectionProvider.notifier).removeSelectedNewAssets([asset]);
ref
.watch(assetSelectionProvider.notifier)
.removeSelectedNewAssets([asset]);
} else {
ref.watch(assetSelectionProvider.notifier).addNewAssets([asset]);
}
@ -97,11 +106,15 @@ class SelectionThumbnailImage extends HookConsumerWidget {
memCacheHeight: asset.type == 'IMAGE' ? 150 : 150,
fit: BoxFit.cover,
imageUrl: thumbnailRequestUrl,
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
httpHeaders: {
"Authorization": "Bearer ${box.get(accessTokenKey)}"
},
fadeInDuration: const Duration(milliseconds: 250),
progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
progressIndicatorBuilder: (context, url, downloadProgress) =>
Transform.scale(
scale: 0.2,
child: CircularProgressIndicator(value: downloadProgress.progress),
child:
CircularProgressIndicator(value: downloadProgress.progress),
),
errorWidget: (context, url, error) {
return Icon(

View file

@ -9,7 +9,8 @@ import 'package:immich_mobile/shared/models/immich_asset.model.dart';
class SharedAlbumThumbnailImage extends HookConsumerWidget {
final ImmichAsset asset;
const SharedAlbumThumbnailImage({Key? key, required this.asset}) : super(key: key);
const SharedAlbumThumbnailImage({Key? key, required this.asset})
: super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -34,9 +35,11 @@ class SharedAlbumThumbnailImage extends HookConsumerWidget {
imageUrl: thumbnailRequestUrl,
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
fadeInDuration: const Duration(milliseconds: 250),
progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
progressIndicatorBuilder: (context, url, downloadProgress) =>
Transform.scale(
scale: 0.2,
child: CircularProgressIndicator(value: downloadProgress.progress),
child:
CircularProgressIndicator(value: downloadProgress.progress),
),
errorWidget: (context, url, error) {
return Icon(

View file

@ -37,11 +37,13 @@ class SharingSliverAppBar extends StatelessWidget {
padding: const EdgeInsets.only(right: 4.0),
child: TextButton.icon(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Theme.of(context).primaryColor.withAlpha(20)),
backgroundColor: MaterialStateProperty.all(
Theme.of(context).primaryColor.withAlpha(20)),
// foregroundColor: MaterialStateProperty.all(Colors.white),
),
onPressed: () {
AutoRouter.of(context).push(const CreateSharedAlbumRoute());
AutoRouter.of(context)
.push(const CreateSharedAlbumRoute());
},
icon: const Icon(
Icons.photo_album_outlined,
@ -49,7 +51,8 @@ class SharingSliverAppBar extends StatelessWidget {
),
label: const Text(
"Create shared album",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
),
),
),
@ -59,7 +62,8 @@ class SharingSliverAppBar extends StatelessWidget {
padding: const EdgeInsets.only(left: 4.0),
child: TextButton.icon(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Theme.of(context).primaryColor.withAlpha(20)),
backgroundColor: MaterialStateProperty.all(
Theme.of(context).primaryColor.withAlpha(20)),
// foregroundColor: MaterialStateProperty.all(Colors.white),
),
onPressed: null,
@ -69,7 +73,8 @@ class SharingSliverAppBar extends StatelessWidget {
),
label: const Text(
"Share with partner",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
),
),
),

View file

@ -10,15 +10,18 @@ import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
final SharedAlbum albumInfo;
const SelectAdditionalUserForSharingPage({Key? key, required this.albumInfo}) : super(key: key);
const SelectAdditionalUserForSharingPage({Key? key, required this.albumInfo})
: super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
AsyncValue<List<User>> suggestedShareUsers = ref.watch(suggestedSharedUsersProvider);
AsyncValue<List<User>> suggestedShareUsers =
ref.watch(suggestedSharedUsersProvider);
final sharedUsersList = useState<Set<User>>({});
_addNewUsersHandler() {
AutoRouter.of(context).pop(sharedUsersList.value.map((e) => e.id).toList());
AutoRouter.of(context)
.pop(sharedUsersList.value.map((e) => e.id).toList());
}
_buildTileIcon(User user) {
@ -32,7 +35,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
);
} else {
return CircleAvatar(
backgroundImage: const AssetImage('assets/immich-logo-no-outline.png'),
backgroundImage:
const AssetImage('assets/immich-logo-no-outline.png'),
backgroundColor: Theme.of(context).primaryColor.withAlpha(50),
);
}
@ -49,7 +53,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.15),
label: Text(
user.email,
style: const TextStyle(fontSize: 12, color: Colors.black87, fontWeight: FontWeight.bold),
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
fontWeight: FontWeight.bold),
),
),
),
@ -65,7 +72,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
padding: EdgeInsets.all(16.0),
child: Text(
'Suggestions',
style: TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold),
style: TextStyle(
fontSize: 14,
color: Colors.grey,
fontWeight: FontWeight.bold),
),
),
ListView.builder(
@ -75,14 +85,20 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
leading: _buildTileIcon(users[index]),
title: Text(
users[index].email,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.bold),
),
onTap: () {
if (sharedUsersList.value.contains(users[index])) {
sharedUsersList.value =
sharedUsersList.value.where((selectedUser) => selectedUser.id != users[index].id).toSet();
sharedUsersList.value = sharedUsersList.value
.where((selectedUser) =>
selectedUser.id != users[index].id)
.toSet();
} else {
sharedUsersList.value = {...sharedUsersList.value, users[index]};
sharedUsersList.value = {
...sharedUsersList.value,
users[index]
};
}
},
);
@ -109,7 +125,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
),
actions: [
TextButton(
onPressed: sharedUsersList.value.isEmpty ? null : _addNewUsersHandler,
onPressed:
sharedUsersList.value.isEmpty ? null : _addNewUsersHandler,
child: const Text(
"Add",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
@ -120,7 +137,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
body: suggestedShareUsers.when(
data: (users) {
for (var sharedUsers in albumInfo.sharedUsers) {
users.removeWhere((u) => u.id == sharedUsers.id || u.id == albumInfo.ownerId);
users.removeWhere(
(u) => u.id == sharedUsers.id || u.id == albumInfo.ownerId);
}
return _buildUserList(users);

View file

@ -29,12 +29,14 @@ class SharingPage extends HookConsumerWidget {
return SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
String thumbnailUrl = sharedAlbums[index].albumThumbnailAssetId != null
String thumbnailUrl = sharedAlbums[index].albumThumbnailAssetId !=
null
? "$thumbnailRequestUrl/${sharedAlbums[index].albumThumbnailAssetId}"
: "https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60";
return ListTile(
contentPadding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
contentPadding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
leading: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: FadeInImage(
@ -44,7 +46,9 @@ class SharingPage extends HookConsumerWidget {
placeholder: MemoryImage(kTransparentImage),
image: NetworkImage(
thumbnailUrl,
headers: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
headers: {
"Authorization": "Bearer ${box.get(accessTokenKey)}"
},
),
fadeInDuration: const Duration(milliseconds: 200),
fadeOutDuration: const Duration(milliseconds: 200),
@ -54,10 +58,14 @@ class SharingPage extends HookConsumerWidget {
sharedAlbums[index].albumName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey.shade800),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey.shade800),
),
onTap: () {
AutoRouter.of(context).push(AlbumViewerRoute(albumId: sharedAlbums[index].id));
AutoRouter.of(context)
.push(AlbumViewerRoute(albumId: sharedAlbums[index].id));
},
);
},
@ -134,7 +142,9 @@ class SharingPage extends HookConsumerWidget {
),
),
),
sharedAlbums.isNotEmpty ? _buildAlbumList() : _buildEmptyListIndication()
sharedAlbums.isNotEmpty
? _buildAlbumList()
: _buildEmptyListIndication()
],
),
);