mirror of
https://github.com/immich-app/immich.git
synced 2024-11-28 09:33:27 +02:00
refactor(mobile): backup album selection (#8053)
* feat(mobile): include album with 0 assets as album option for backup * Show icon instead of thumbnail * Handle backupProgress state transition correctly to always load the backup info * remove todo comment
This commit is contained in:
parent
c6d2408517
commit
0bc773fd00
File diff suppressed because one or more lines are too long
@ -5,11 +5,9 @@ import 'package:photo_manager/photo_manager.dart';
|
|||||||
class AvailableAlbum {
|
class AvailableAlbum {
|
||||||
final AssetPathEntity albumEntity;
|
final AssetPathEntity albumEntity;
|
||||||
final DateTime? lastBackup;
|
final DateTime? lastBackup;
|
||||||
final Uint8List? thumbnailData;
|
|
||||||
AvailableAlbum({
|
AvailableAlbum({
|
||||||
required this.albumEntity,
|
required this.albumEntity,
|
||||||
this.lastBackup,
|
this.lastBackup,
|
||||||
this.thumbnailData,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AvailableAlbum copyWith({
|
AvailableAlbum copyWith({
|
||||||
@ -20,7 +18,6 @@ class AvailableAlbum {
|
|||||||
return AvailableAlbum(
|
return AvailableAlbum(
|
||||||
albumEntity: albumEntity ?? this.albumEntity,
|
albumEntity: albumEntity ?? this.albumEntity,
|
||||||
lastBackup: lastBackup ?? this.lastBackup,
|
lastBackup: lastBackup ?? this.lastBackup,
|
||||||
thumbnailData: thumbnailData ?? this.thumbnailData,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +31,7 @@ class AvailableAlbum {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'AvailableAlbum(albumEntity: $albumEntity, lastBackup: $lastBackup, thumbnailData: $thumbnailData)';
|
'AvailableAlbum(albumEntity: $albumEntity, lastBackup: $lastBackup)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
|
@ -234,33 +234,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
for (AssetPathEntity album in albums) {
|
for (AssetPathEntity album in albums) {
|
||||||
AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
|
AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
|
||||||
|
|
||||||
final assetCountInAlbum = await album.assetCountAsync;
|
availableAlbums.add(availableAlbum);
|
||||||
if (assetCountInAlbum > 0) {
|
|
||||||
final assetList = await album.getAssetListPaged(page: 0, size: 1);
|
|
||||||
|
|
||||||
// Even though we check assetCountInAlbum to make sure that there are assets in album
|
albumMap[album.id] = album;
|
||||||
// The `getAssetListPaged` method still return empty list and cause not assets get rendered
|
|
||||||
if (assetList.isEmpty) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final thumbnailAsset = assetList.first;
|
|
||||||
try {
|
|
||||||
final thumbnailData = await thumbnailAsset
|
|
||||||
.thumbnailDataWithSize(const ThumbnailSize(512, 512));
|
|
||||||
availableAlbum =
|
|
||||||
availableAlbum.copyWith(thumbnailData: thumbnailData);
|
|
||||||
} catch (e, stack) {
|
|
||||||
log.severe(
|
|
||||||
"Failed to get thumbnail for album ${album.name}",
|
|
||||||
e,
|
|
||||||
stack,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
availableAlbums.add(availableAlbum);
|
|
||||||
|
|
||||||
albumMap[album.id] = album;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
state = state.copyWith(availableAlbums: availableAlbums);
|
state = state.copyWith(availableAlbums: availableAlbums);
|
||||||
|
|
||||||
|
@ -11,17 +11,16 @@ import 'package:immich_mobile/routing/router.dart';
|
|||||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||||
|
|
||||||
class AlbumInfoCard extends HookConsumerWidget {
|
class AlbumInfoCard extends HookConsumerWidget {
|
||||||
final Uint8List? imageData;
|
final AvailableAlbum album;
|
||||||
final AvailableAlbum albumInfo;
|
|
||||||
|
|
||||||
const AlbumInfoCard({super.key, this.imageData, required this.albumInfo});
|
const AlbumInfoCard({super.key, required this.album});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final bool isSelected =
|
final bool isSelected =
|
||||||
ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo);
|
ref.watch(backupProvider).selectedBackupAlbums.contains(album);
|
||||||
final bool isExcluded =
|
final bool isExcluded =
|
||||||
ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo);
|
ref.watch(backupProvider).excludedBackupAlbums.contains(album);
|
||||||
final isDarkTheme = context.isDarkTheme;
|
final isDarkTheme = context.isDarkTheme;
|
||||||
|
|
||||||
ColorFilter selectedFilter = ColorFilter.mode(
|
ColorFilter selectedFilter = ColorFilter.mode(
|
||||||
@ -82,9 +81,9 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
ref.read(backupProvider.notifier).removeAlbumForBackup(albumInfo);
|
ref.read(backupProvider.notifier).removeAlbumForBackup(album);
|
||||||
} else {
|
} else {
|
||||||
ref.read(backupProvider.notifier).addAlbumForBackup(albumInfo);
|
ref.read(backupProvider.notifier).addAlbumForBackup(album);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDoubleTap: () {
|
onDoubleTap: () {
|
||||||
@ -92,13 +91,11 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
|
|
||||||
if (isExcluded) {
|
if (isExcluded) {
|
||||||
// Remove from exclude album list
|
// Remove from exclude album list
|
||||||
ref
|
ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album);
|
||||||
.read(backupProvider.notifier)
|
|
||||||
.removeExcludedAlbumForBackup(albumInfo);
|
|
||||||
} else {
|
} else {
|
||||||
// Add to exclude album list
|
// Add to exclude album list
|
||||||
|
|
||||||
if (albumInfo.id == 'isAll' || albumInfo.name == 'Recents') {
|
if (album.id == 'isAll' || album.name == 'Recents') {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
msg: 'Cannot exclude album contains all assets',
|
msg: 'Cannot exclude album contains all assets',
|
||||||
@ -108,9 +105,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref
|
ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album);
|
||||||
.read(backupProvider.notifier)
|
|
||||||
.addExcludedAlbumForBackup(albumInfo);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Card(
|
child: Card(
|
||||||
@ -136,14 +131,12 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
ColorFiltered(
|
ColorFiltered(
|
||||||
colorFilter: buildImageFilter(),
|
colorFilter: buildImageFilter(),
|
||||||
child: Image(
|
child: const Image(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
image: imageData != null
|
image: AssetImage(
|
||||||
? MemoryImage(imageData!)
|
'assets/immich-logo.png',
|
||||||
: const AssetImage(
|
),
|
||||||
'assets/immich-logo.png',
|
|
||||||
) as ImageProvider,
|
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -168,7 +161,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
albumInfo.name,
|
album.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: context.primaryColor,
|
color: context.primaryColor,
|
||||||
@ -182,7 +175,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
return Text(
|
return Text(
|
||||||
snapshot.data.toString() +
|
snapshot.data.toString() +
|
||||||
(albumInfo.isAll
|
(album.isAll
|
||||||
? " (${'backup_all'.tr()})"
|
? " (${'backup_all'.tr()})"
|
||||||
: ""),
|
: ""),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -193,7 +186,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
return const Text("0");
|
return const Text("0");
|
||||||
}),
|
}),
|
||||||
future: albumInfo.assetCount,
|
future: album.assetCount,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -202,7 +195,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushRoute(
|
context.pushRoute(
|
||||||
AlbumPreviewRoute(album: albumInfo.albumEntity),
|
AlbumPreviewRoute(album: album.albumEntity),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
|
@ -11,47 +11,26 @@ import 'package:immich_mobile/routing/router.dart';
|
|||||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||||
|
|
||||||
class AlbumInfoListTile extends HookConsumerWidget {
|
class AlbumInfoListTile extends HookConsumerWidget {
|
||||||
final Uint8List? imageData;
|
final AvailableAlbum album;
|
||||||
final AvailableAlbum albumInfo;
|
|
||||||
|
|
||||||
const AlbumInfoListTile({super.key, this.imageData, required this.albumInfo});
|
const AlbumInfoListTile({super.key, required this.album});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final bool isSelected =
|
final bool isSelected =
|
||||||
ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo);
|
ref.watch(backupProvider).selectedBackupAlbums.contains(album);
|
||||||
final bool isExcluded =
|
final bool isExcluded =
|
||||||
ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo);
|
ref.watch(backupProvider).excludedBackupAlbums.contains(album);
|
||||||
|
|
||||||
ColorFilter selectedFilter = ColorFilter.mode(
|
|
||||||
context.primaryColor.withAlpha(100),
|
|
||||||
BlendMode.darken,
|
|
||||||
);
|
|
||||||
ColorFilter excludedFilter =
|
|
||||||
ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken);
|
|
||||||
ColorFilter unselectedFilter =
|
|
||||||
const ColorFilter.mode(Colors.black, BlendMode.color);
|
|
||||||
|
|
||||||
var assetCount = useState(0);
|
var assetCount = useState(0);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
albumInfo.assetCount.then((value) => assetCount.value = value);
|
album.assetCount.then((value) => assetCount.value = value);
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[albumInfo],
|
[album],
|
||||||
);
|
);
|
||||||
|
|
||||||
buildImageFilter() {
|
|
||||||
if (isSelected) {
|
|
||||||
return selectedFilter;
|
|
||||||
} else if (isExcluded) {
|
|
||||||
return excludedFilter;
|
|
||||||
} else {
|
|
||||||
return unselectedFilter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTileColor() {
|
buildTileColor() {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
return context.isDarkTheme
|
return context.isDarkTheme
|
||||||
@ -66,19 +45,38 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildIcon() {
|
||||||
|
if (isSelected) {
|
||||||
|
return const Icon(
|
||||||
|
Icons.check_circle_rounded,
|
||||||
|
color: Colors.green,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExcluded) {
|
||||||
|
return const Icon(
|
||||||
|
Icons.remove_circle_rounded,
|
||||||
|
color: Colors.red,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Icon(
|
||||||
|
Icons.circle,
|
||||||
|
color: context.isDarkTheme ? Colors.grey[400] : Colors.black45,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onDoubleTap: () {
|
onDoubleTap: () {
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
|
|
||||||
if (isExcluded) {
|
if (isExcluded) {
|
||||||
// Remove from exclude album list
|
// Remove from exclude album list
|
||||||
ref
|
ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album);
|
||||||
.read(backupProvider.notifier)
|
|
||||||
.removeExcludedAlbumForBackup(albumInfo);
|
|
||||||
} else {
|
} else {
|
||||||
// Add to exclude album list
|
// Add to exclude album list
|
||||||
|
|
||||||
if (albumInfo.id == 'isAll' || albumInfo.name == 'Recents') {
|
if (album.id == 'isAll' || album.name == 'Recents') {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
msg: 'Cannot exclude album contains all assets',
|
msg: 'Cannot exclude album contains all assets',
|
||||||
@ -88,9 +86,7 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref
|
ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album);
|
||||||
.read(backupProvider.notifier)
|
|
||||||
.addExcludedAlbumForBackup(albumInfo);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
@ -99,33 +95,14 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
ref.read(backupProvider.notifier).removeAlbumForBackup(albumInfo);
|
ref.read(backupProvider.notifier).removeAlbumForBackup(album);
|
||||||
} else {
|
} else {
|
||||||
ref.read(backupProvider.notifier).addAlbumForBackup(albumInfo);
|
ref.read(backupProvider.notifier).addAlbumForBackup(album);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
leading: ClipRRect(
|
leading: buildIcon(),
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 80,
|
|
||||||
width: 80,
|
|
||||||
child: ColorFiltered(
|
|
||||||
colorFilter: buildImageFilter(),
|
|
||||||
child: Image(
|
|
||||||
width: double.infinity,
|
|
||||||
height: double.infinity,
|
|
||||||
image: imageData != null
|
|
||||||
? MemoryImage(imageData!)
|
|
||||||
: const AssetImage(
|
|
||||||
'assets/immich-logo.png',
|
|
||||||
) as ImageProvider,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(
|
title: Text(
|
||||||
albumInfo.name,
|
album.name,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -135,7 +112,7 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
|||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushRoute(
|
context.pushRoute(
|
||||||
AlbumPreviewRoute(album: albumInfo.albumEntity),
|
AlbumPreviewRoute(album: album.albumEntity),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
|
@ -43,10 +43,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
sliver: SliverList(
|
sliver: SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
((context, index) {
|
((context, index) {
|
||||||
var thumbnailData = albums[index].thumbnailData;
|
|
||||||
return AlbumInfoListTile(
|
return AlbumInfoListTile(
|
||||||
imageData: thumbnailData,
|
album: albums[index],
|
||||||
albumInfo: albums[index],
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
childCount: albums.length,
|
childCount: albums.length,
|
||||||
@ -74,10 +72,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
itemCount: albums.length,
|
itemCount: albums.length,
|
||||||
itemBuilder: ((context, index) {
|
itemBuilder: ((context, index) {
|
||||||
var thumbnailData = albums[index].thumbnailData;
|
|
||||||
return AlbumInfoCard(
|
return AlbumInfoCard(
|
||||||
imageData: thumbnailData,
|
album: albums[index],
|
||||||
albumInfo: albums[index],
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -26,7 +26,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
BackUpState backupState = ref.watch(backupProvider);
|
BackUpState backupState = ref.watch(backupProvider);
|
||||||
final hasAnyAlbum = backupState.selectedBackupAlbums.isNotEmpty;
|
final hasAnyAlbum = backupState.selectedBackupAlbums.isNotEmpty;
|
||||||
|
final didGetBackupInfo = useState(false);
|
||||||
bool hasExclusiveAccess =
|
bool hasExclusiveAccess =
|
||||||
backupState.backupProgress != BackUpProgressEnum.inBackground;
|
backupState.backupProgress != BackUpProgressEnum.inBackground;
|
||||||
bool shouldBackup = backupState.allUniqueAssets.length -
|
bool shouldBackup = backupState.allUniqueAssets.length -
|
||||||
@ -38,11 +38,6 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
if (backupState.backupProgress != BackUpProgressEnum.inProgress &&
|
|
||||||
backupState.backupProgress != BackUpProgressEnum.manualInProgress) {
|
|
||||||
ref.watch(backupProvider.notifier).getBackupInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the background settings information just to make sure we
|
// Update the background settings information just to make sure we
|
||||||
// have the latest, since the platform channel will not update
|
// have the latest, since the platform channel will not update
|
||||||
// automatically
|
// automatically
|
||||||
@ -58,6 +53,18 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() {
|
||||||
|
if (backupState.backupProgress == BackUpProgressEnum.idle &&
|
||||||
|
!didGetBackupInfo.value) {
|
||||||
|
ref.watch(backupProvider.notifier).getBackupInfo();
|
||||||
|
didGetBackupInfo.value = true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
[backupState.backupProgress],
|
||||||
|
);
|
||||||
|
|
||||||
Widget buildSelectedAlbumName() {
|
Widget buildSelectedAlbumName() {
|
||||||
var text = "backup_controller_page_backup_selected".tr();
|
var text = "backup_controller_page_backup_selected".tr();
|
||||||
var albums = ref.watch(backupProvider).selectedBackupAlbums;
|
var albums = ref.watch(backupProvider).selectedBackupAlbums;
|
||||||
@ -235,6 +242,15 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildLoadingIndicator() {
|
||||||
|
return const Padding(
|
||||||
|
padding: EdgeInsets.only(top: 42.0),
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
@ -297,7 +313,10 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
|
if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
|
||||||
buildBackupButton(),
|
buildBackupButton(),
|
||||||
]
|
]
|
||||||
: [buildFolderSelectionTile()],
|
: [
|
||||||
|
buildFolderSelectionTile(),
|
||||||
|
if (!didGetBackupInfo.value) buildLoadingIndicator(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user