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

@ -35,7 +35,8 @@ class DeleteAssetResponse {
String toJson() => json.encode(toMap());
factory DeleteAssetResponse.fromJson(String source) => DeleteAssetResponse.fromMap(json.decode(source));
factory DeleteAssetResponse.fromJson(String source) =>
DeleteAssetResponse.fromMap(json.decode(source));
@override
String toString() => 'DeleteAssetResponse(id: $id, status: $status)';
@ -44,7 +45,9 @@ class DeleteAssetResponse {
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is DeleteAssetResponse && other.id == id && other.status == status;
return other is DeleteAssetResponse &&
other.id == id &&
other.status == status;
}
@override

View file

@ -31,13 +31,15 @@ class ImmichAssetGroupByDate {
factory ImmichAssetGroupByDate.fromMap(Map<String, dynamic> map) {
return ImmichAssetGroupByDate(
date: map['date'] ?? '',
assets: List<ImmichAsset>.from(map['assets']?.map((x) => ImmichAsset.fromMap(x))),
assets: List<ImmichAsset>.from(
map['assets']?.map((x) => ImmichAsset.fromMap(x))),
);
}
String toJson() => json.encode(toMap());
factory ImmichAssetGroupByDate.fromJson(String source) => ImmichAssetGroupByDate.fromMap(json.decode(source));
factory ImmichAssetGroupByDate.fromJson(String source) =>
ImmichAssetGroupByDate.fromMap(json.decode(source));
@override
String toString() => 'ImmichAssetGroupByDate(date: $date, assets: $assets)';
@ -46,7 +48,9 @@ class ImmichAssetGroupByDate {
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ImmichAssetGroupByDate && other.date == date && listEquals(other.assets, assets);
return other is ImmichAssetGroupByDate &&
other.date == date &&
listEquals(other.assets, assets);
}
@override
@ -86,17 +90,20 @@ class GetAllAssetResponse {
factory GetAllAssetResponse.fromMap(Map<String, dynamic> map) {
return GetAllAssetResponse(
count: map['count']?.toInt() ?? 0,
data: List<ImmichAssetGroupByDate>.from(map['data']?.map((x) => ImmichAssetGroupByDate.fromMap(x))),
data: List<ImmichAssetGroupByDate>.from(
map['data']?.map((x) => ImmichAssetGroupByDate.fromMap(x))),
nextPageKey: map['nextPageKey'] ?? '',
);
}
String toJson() => json.encode(toMap());
factory GetAllAssetResponse.fromJson(String source) => GetAllAssetResponse.fromMap(json.decode(source));
factory GetAllAssetResponse.fromJson(String source) =>
GetAllAssetResponse.fromMap(json.decode(source));
@override
String toString() => 'GetAllAssetResponse(count: $count, data: $data, nextPageKey: $nextPageKey)';
String toString() =>
'GetAllAssetResponse(count: $count, data: $data, nextPageKey: $nextPageKey)';
@override
bool operator ==(Object other) {

View file

@ -37,14 +37,16 @@ class HomePageState {
factory HomePageState.fromMap(Map<String, dynamic> map) {
return HomePageState(
isMultiSelectEnable: map['isMultiSelectEnable'] ?? false,
selectedItems: Set<ImmichAsset>.from(map['selectedItems']?.map((x) => ImmichAsset.fromMap(x))),
selectedItems: Set<ImmichAsset>.from(
map['selectedItems']?.map((x) => ImmichAsset.fromMap(x))),
selectedDateGroup: Set<String>.from(map['selectedDateGroup']),
);
}
String toJson() => json.encode(toMap());
factory HomePageState.fromJson(String source) => HomePageState.fromMap(json.decode(source));
factory HomePageState.fromJson(String source) =>
HomePageState.fromMap(json.decode(source));
@override
String toString() =>
@ -62,5 +64,8 @@ class HomePageState {
}
@override
int get hashCode => isMultiSelectEnable.hashCode ^ selectedItems.hashCode ^ selectedDateGroup.hashCode;
int get hashCode =>
isMultiSelectEnable.hashCode ^
selectedItems.hashCode ^
selectedDateGroup.hashCode;
}

View file

@ -13,7 +13,8 @@ class HomePageStateNotifier extends StateNotifier<HomePageState> {
);
void addSelectedDateGroup(String dateGroupTitle) {
state = state.copyWith(selectedDateGroup: {...state.selectedDateGroup, dateGroupTitle});
state = state.copyWith(
selectedDateGroup: {...state.selectedDateGroup, dateGroupTitle});
}
void removeSelectedDateGroup(String dateGroupTitle) {
@ -25,11 +26,13 @@ class HomePageStateNotifier extends StateNotifier<HomePageState> {
}
void enableMultiSelect(Set<ImmichAsset> selectedItems) {
state = state.copyWith(isMultiSelectEnable: true, selectedItems: selectedItems);
state =
state.copyWith(isMultiSelectEnable: true, selectedItems: selectedItems);
}
void disableMultiSelect() {
state = state.copyWith(isMultiSelectEnable: false, selectedItems: {}, selectedDateGroup: {});
state = state.copyWith(
isMultiSelectEnable: false, selectedItems: {}, selectedDateGroup: {});
}
void addSingleSelectedItem(ImmichAsset asset) {
@ -60,4 +63,5 @@ class HomePageStateNotifier extends StateNotifier<HomePageState> {
}
final homePageStateProvider =
StateNotifierProvider<HomePageStateNotifier, HomePageState>(((ref) => HomePageStateNotifier()));
StateNotifierProvider<HomePageStateNotifier, HomePageState>(
((ref) => HomePageStateNotifier()));

View file

@ -13,7 +13,8 @@ class ControlBottomAppBar extends StatelessWidget {
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.15,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15), topRight: Radius.circular(15)),
color: Colors.grey[300]?.withOpacity(0.98),
),
child: Column(
@ -46,7 +47,11 @@ class ControlBottomAppBar extends StatelessWidget {
}
class ControlBoxButton extends StatelessWidget {
const ControlBoxButton({Key? key, required this.label, required this.iconData, required this.onPressed})
const ControlBoxButton(
{Key? key,
required this.label,
required this.iconData,
required this.onPressed})
: super(key: key);
final String label;

View file

@ -18,9 +18,12 @@ class DailyTitleText extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
var currentYear = DateTime.now().year;
var groupYear = DateTime.parse(isoDate).year;
var formatDateTemplate = currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy';
var dateText = DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
var isMultiSelectEnable = ref.watch(homePageStateProvider).isMultiSelectEnable;
var formatDateTemplate =
currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy';
var dateText =
DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
var isMultiSelectEnable =
ref.watch(homePageStateProvider).isMultiSelectEnable;
var selectedDateGroup = ref.watch(homePageStateProvider).selectedDateGroup;
var selectedItems = ref.watch(homePageStateProvider).selectedItems;
@ -35,23 +38,42 @@ class DailyTitleText extends ConsumerWidget {
selectedDateGroup.contains(dateText) &&
selectedItems.length != assetGroup.length) {
// Multi select is active - click again on the icon while it is not the only active group -> remove that group from selected group/items
ref.watch(homePageStateProvider.notifier).removeSelectedDateGroup(dateText);
ref.watch(homePageStateProvider.notifier).removeMultipleSelectedItem(assetGroup);
} else if (isMultiSelectEnable && selectedDateGroup.contains(dateText) && selectedDateGroup.length > 1) {
ref.watch(homePageStateProvider.notifier).removeSelectedDateGroup(dateText);
ref.watch(homePageStateProvider.notifier).removeMultipleSelectedItem(assetGroup);
ref
.watch(homePageStateProvider.notifier)
.removeSelectedDateGroup(dateText);
ref
.watch(homePageStateProvider.notifier)
.removeMultipleSelectedItem(assetGroup);
} else if (isMultiSelectEnable &&
selectedDateGroup.contains(dateText) &&
selectedDateGroup.length > 1) {
ref
.watch(homePageStateProvider.notifier)
.removeSelectedDateGroup(dateText);
ref
.watch(homePageStateProvider.notifier)
.removeMultipleSelectedItem(assetGroup);
} else if (isMultiSelectEnable && !selectedDateGroup.contains(dateText)) {
ref.watch(homePageStateProvider.notifier).addSelectedDateGroup(dateText);
ref.watch(homePageStateProvider.notifier).addMultipleSelectedItems(assetGroup);
ref
.watch(homePageStateProvider.notifier)
.addSelectedDateGroup(dateText);
ref
.watch(homePageStateProvider.notifier)
.addMultipleSelectedItems(assetGroup);
} else {
ref.watch(homePageStateProvider.notifier).enableMultiSelect(assetGroup.toSet());
ref.watch(homePageStateProvider.notifier).addSelectedDateGroup(dateText);
ref
.watch(homePageStateProvider.notifier)
.enableMultiSelect(assetGroup.toSet());
ref
.watch(homePageStateProvider.notifier)
.addSelectedDateGroup(dateText);
}
}
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 29.0, bottom: 29.0, left: 12.0, right: 12.0),
padding: const EdgeInsets.only(
top: 29.0, bottom: 29.0, left: 12.0, right: 12.0),
child: Row(
children: [
Text(

View file

@ -14,7 +14,8 @@ class DeleteDialog extends ConsumerWidget {
backgroundColor: Colors.grey[200],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
title: const Text("Delete Permanently"),
content: const Text("These items will be permanently deleted from Immich and from your device"),
content: const Text(
"These items will be permanently deleted from Immich and from your device"),
actions: [
TextButton(
onPressed: () {
@ -27,7 +28,9 @@ class DeleteDialog extends ConsumerWidget {
),
TextButton(
onPressed: () {
ref.watch(assetProvider.notifier).deleteAssets(homePageState.selectedItems);
ref
.watch(assetProvider.notifier)
.deleteAssets(homePageState.selectedItems);
ref.watch(homePageStateProvider.notifier).disableMultiSelect();
Navigator.of(context).pop();

View file

@ -33,7 +33,10 @@ class ImageGrid extends ConsumerWidget {
child: Row(
children: [
Text(
assetGroup[index].duration.toString().substring(0, 7),
assetGroup[index]
.duration
.toString()
.substring(0, 7),
style: const TextStyle(
color: Colors.white,
fontSize: 10,

View file

@ -25,7 +25,8 @@ class ThumbnailImage extends HookConsumerWidget {
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
var selectedAsset = ref.watch(homePageStateProvider).selectedItems;
var isMultiSelectEnable = ref.watch(homePageStateProvider).isMultiSelectEnable;
var isMultiSelectEnable =
ref.watch(homePageStateProvider).isMultiSelectEnable;
var deviceId = ref.watch(authenticationProvider).deviceId;
Widget _buildSelectionIcon(ImmichAsset asset) {
@ -45,12 +46,20 @@ class ThumbnailImage extends HookConsumerWidget {
return GestureDetector(
onTap: () {
debugPrint("View ${asset.id}");
if (isMultiSelectEnable && selectedAsset.contains(asset) && selectedAsset.length == 1) {
if (isMultiSelectEnable &&
selectedAsset.contains(asset) &&
selectedAsset.length == 1) {
ref.watch(homePageStateProvider.notifier).disableMultiSelect();
} else if (isMultiSelectEnable && selectedAsset.contains(asset) && selectedAsset.length > 1) {
ref.watch(homePageStateProvider.notifier).removeSingleSelectedItem(asset);
} else if (isMultiSelectEnable &&
selectedAsset.contains(asset) &&
selectedAsset.length > 1) {
ref
.watch(homePageStateProvider.notifier)
.removeSingleSelectedItem(asset);
} else if (isMultiSelectEnable && !selectedAsset.contains(asset)) {
ref.watch(homePageStateProvider.notifier).addSingleSelectedItem(asset);
ref
.watch(homePageStateProvider.notifier)
.addSingleSelectedItem(asset);
} else {
if (asset.type == 'IMAGE') {
AutoRouter.of(context).push(
@ -65,7 +74,8 @@ class ThumbnailImage 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),
);
}
@ -83,7 +93,8 @@ class ThumbnailImage extends HookConsumerWidget {
Container(
decoration: BoxDecoration(
border: isMultiSelectEnable && selectedAsset.contains(asset)
? Border.all(color: Theme.of(context).primaryColorLight, width: 10)
? Border.all(
color: Theme.of(context).primaryColorLight, width: 10)
: const Border(),
),
child: CachedNetworkImage(
@ -93,11 +104,15 @@ class ThumbnailImage extends HookConsumerWidget {
memCacheHeight: asset.type == 'IMAGE' ? 250 : 400,
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(
@ -122,7 +137,9 @@ class ThumbnailImage 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,
),