mirror of
https://github.com/immich-app/immich.git
synced 2025-09-29 15:31:13 -04:00
feat: show preparing/hashing status in backup page (#22222)
* only show preparing information while hashing * pr feedback * use count * use a single query for count * use Mert's query
This commit is contained in:
parent
0bbeb20595
commit
7a0107fc79
@ -1527,6 +1527,7 @@
|
|||||||
"port": "Port",
|
"port": "Port",
|
||||||
"preferences_settings_subtitle": "Manage the app's preferences",
|
"preferences_settings_subtitle": "Manage the app's preferences",
|
||||||
"preferences_settings_title": "Preferences",
|
"preferences_settings_title": "Preferences",
|
||||||
|
"preparing": "Preparing",
|
||||||
"preset": "Preset",
|
"preset": "Preset",
|
||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
@ -1592,6 +1593,7 @@
|
|||||||
"read_changelog": "Read Changelog",
|
"read_changelog": "Read Changelog",
|
||||||
"readonly_mode_disabled": "Read-only mode disabled",
|
"readonly_mode_disabled": "Read-only mode disabled",
|
||||||
"readonly_mode_enabled": "Read-only mode enabled",
|
"readonly_mode_enabled": "Read-only mode enabled",
|
||||||
|
"ready_for_upload": "Ready for upload",
|
||||||
"reassign": "Reassign",
|
"reassign": "Reassign",
|
||||||
"reassigned_assets_to_existing_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to {name, select, null {an existing person} other {{name}}}",
|
"reassigned_assets_to_existing_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to {name, select, null {an existing person} other {{name}}}",
|
||||||
"reassigned_assets_to_new_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to a new person",
|
"reassigned_assets_to_new_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to a new person",
|
||||||
|
@ -29,82 +29,56 @@ class DriftBackupRepository extends DriftDatabaseRepository {
|
|||||||
..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.excluded));
|
..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.excluded));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getTotalCount() async {
|
/// Returns all backup-related counts in a single query.
|
||||||
final query = _db.localAlbumAssetEntity.selectOnly(distinct: true)
|
///
|
||||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
/// - total: number of distinct assets in selected albums, excluding those that are also in excluded albums
|
||||||
..join([
|
/// - backup: number of those assets that already exist on the server for [userId]
|
||||||
innerJoin(
|
/// - remainder: number of those assets that do not yet exist on the server for [userId]
|
||||||
_db.localAlbumEntity,
|
/// (includes processing)
|
||||||
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
|
/// - processing: number of those assets that are still preparing/have a null checksum
|
||||||
useColumns: false,
|
Future<({int total, int remainder, int processing})> getAllCounts(String userId) async {
|
||||||
),
|
const sql = '''
|
||||||
])
|
SELECT
|
||||||
..where(
|
COUNT(*) AS total_count,
|
||||||
_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) &
|
COUNT(*) FILTER (WHERE lae.checksum IS NULL) AS processing_count,
|
||||||
_db.localAlbumAssetEntity.assetId.isNotInQuery(_getExcludedSubquery()),
|
COUNT(*) FILTER (WHERE rae.id IS NULL) AS remainder_count
|
||||||
|
FROM local_asset_entity lae
|
||||||
|
LEFT JOIN main.remote_asset_entity rae
|
||||||
|
ON lae.checksum = rae.checksum AND rae.owner_id = ?1
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM local_album_asset_entity laa
|
||||||
|
INNER JOIN main.local_album_entity la on laa.album_id = la.id
|
||||||
|
WHERE laa.asset_id = lae.id
|
||||||
|
AND la.backup_selection = ?2
|
||||||
|
)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM local_album_asset_entity laa
|
||||||
|
INNER JOIN main.local_album_entity la on laa.album_id = la.id
|
||||||
|
WHERE laa.asset_id = lae.id
|
||||||
|
AND la.backup_selection = ?3
|
||||||
);
|
);
|
||||||
|
''';
|
||||||
|
|
||||||
return query.get().then((rows) => rows.length);
|
final row = await _db
|
||||||
}
|
.customSelect(
|
||||||
|
sql,
|
||||||
|
variables: [
|
||||||
|
Variable.withString(userId),
|
||||||
|
Variable.withInt(BackupSelection.selected.index),
|
||||||
|
Variable.withInt(BackupSelection.excluded.index),
|
||||||
|
],
|
||||||
|
readsFrom: {_db.localAlbumAssetEntity, _db.localAlbumEntity, _db.localAssetEntity, _db.remoteAssetEntity},
|
||||||
|
)
|
||||||
|
.getSingle();
|
||||||
|
|
||||||
Future<int> getRemainderCount(String userId) async {
|
final data = row.data;
|
||||||
final query = _db.localAlbumAssetEntity.selectOnly(distinct: true)
|
return (
|
||||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
total: (data['total_count'] as int?) ?? 0,
|
||||||
..join([
|
remainder: (data['remainder_count'] as int?) ?? 0,
|
||||||
innerJoin(
|
processing: (data['processing_count'] as int?) ?? 0,
|
||||||
_db.localAlbumEntity,
|
|
||||||
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
innerJoin(
|
|
||||||
_db.localAssetEntity,
|
|
||||||
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
leftOuterJoin(
|
|
||||||
_db.remoteAssetEntity,
|
|
||||||
_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum) &
|
|
||||||
_db.remoteAssetEntity.ownerId.equals(userId),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
])
|
|
||||||
..where(
|
|
||||||
_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) &
|
|
||||||
_db.remoteAssetEntity.id.isNull() &
|
|
||||||
_db.localAlbumAssetEntity.assetId.isNotInQuery(_getExcludedSubquery()),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return query.get().then((rows) => rows.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> getBackupCount(String userId) async {
|
|
||||||
final query = _db.localAlbumAssetEntity.selectOnly(distinct: true)
|
|
||||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
|
||||||
..join([
|
|
||||||
innerJoin(
|
|
||||||
_db.localAlbumEntity,
|
|
||||||
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
innerJoin(
|
|
||||||
_db.localAssetEntity,
|
|
||||||
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
innerJoin(
|
|
||||||
_db.remoteAssetEntity,
|
|
||||||
_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
])
|
|
||||||
..where(
|
|
||||||
_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) &
|
|
||||||
_db.remoteAssetEntity.id.isNotNull() &
|
|
||||||
_db.remoteAssetEntity.ownerId.equals(userId) &
|
|
||||||
_db.localAlbumAssetEntity.assetId.isNotInQuery(_getExcludedSubquery()),
|
|
||||||
);
|
|
||||||
|
|
||||||
return query.get().then((rows) => rows.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<LocalAsset>> getCandidates(String userId) async {
|
Future<List<LocalAsset>> getCandidates(String userId) async {
|
||||||
|
@ -16,6 +16,9 @@ import 'package:immich_mobile/providers/sync_status.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/backup_info_card.dart';
|
import 'package:immich_mobile/widgets/backup/backup_info_card.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class DriftBackupPage extends ConsumerStatefulWidget {
|
class DriftBackupPage extends ConsumerStatefulWidget {
|
||||||
@ -29,6 +32,9 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
WakelockPlus.enable();
|
||||||
|
|
||||||
final currentUser = ref.read(currentUserProvider);
|
final currentUser = ref.read(currentUserProvider);
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
return;
|
return;
|
||||||
@ -44,6 +50,12 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dispose() {
|
||||||
|
super.dispose();
|
||||||
|
WakelockPlus.disable();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selectedAlbum = ref
|
final selectedAlbum = ref
|
||||||
@ -260,12 +272,205 @@ class _RemainderCard extends ConsumerWidget {
|
|||||||
final remainderCount = ref.watch(driftBackupProvider.select((p) => p.remainderCount));
|
final remainderCount = ref.watch(driftBackupProvider.select((p) => p.remainderCount));
|
||||||
final syncStatus = ref.watch(syncStatusProvider);
|
final syncStatus = ref.watch(syncStatusProvider);
|
||||||
|
|
||||||
return BackupInfoCard(
|
return Card(
|
||||||
title: "backup_controller_page_remainder".tr(),
|
shape: RoundedRectangleBorder(
|
||||||
subtitle: "backup_controller_page_remainder_sub".tr(),
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
info: remainderCount.toString(),
|
side: BorderSide(color: context.colorScheme.outlineVariant, width: 1),
|
||||||
isLoading: syncStatus.isRemoteSyncing,
|
),
|
||||||
|
elevation: 0,
|
||||||
|
borderOnForeground: false,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
minVerticalPadding: 18,
|
||||||
|
isThreeLine: true,
|
||||||
|
title: Text("backup_controller_page_remainder".t(context: context), style: context.textTheme.titleMedium),
|
||||||
|
subtitle: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4.0, right: 18.0),
|
||||||
|
child: Text(
|
||||||
|
"backup_controller_page_remainder_sub".t(context: context),
|
||||||
|
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
remainderCount.toString(),
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
|
color: context.colorScheme.onSurface.withAlpha(syncStatus.isRemoteSyncing ? 50 : 255),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (syncStatus.isRemoteSyncing)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: context.colorScheme.onSurface.withAlpha(150),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"backup_info_card_assets",
|
||||||
|
style: context.textTheme.labelLarge?.copyWith(
|
||||||
|
color: context.colorScheme.onSurface.withAlpha(syncStatus.isRemoteSyncing ? 50 : 255),
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 0),
|
||||||
|
const _PreparingStatus(),
|
||||||
|
const Divider(height: 0),
|
||||||
|
|
||||||
|
ListTile(
|
||||||
|
enableFeedback: true,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 0.0),
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
|
||||||
|
),
|
||||||
onTap: () => context.pushRoute(const DriftBackupAssetDetailRoute()),
|
onTap: () => context.pushRoute(const DriftBackupAssetDetailRoute()),
|
||||||
|
title: Text(
|
||||||
|
"view_details".t(context: context),
|
||||||
|
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)),
|
||||||
|
),
|
||||||
|
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: context.colorScheme.onSurfaceVariant),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreparingStatus extends ConsumerStatefulWidget {
|
||||||
|
const _PreparingStatus();
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PreparingStatusState createState() => _PreparingStatusState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreparingStatusState extends ConsumerState {
|
||||||
|
Timer? _pollingTimer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pollingTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startPollingIfNeeded() {
|
||||||
|
if (_pollingTimer != null) return;
|
||||||
|
|
||||||
|
_pollingTimer = Timer.periodic(const Duration(seconds: 3), (timer) async {
|
||||||
|
final currentUser = ref.read(currentUserProvider);
|
||||||
|
if (currentUser != null && mounted) {
|
||||||
|
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||||
|
|
||||||
|
// Stop polling if processing count reaches 0
|
||||||
|
final updatedProcessingCount = ref.read(driftBackupProvider.select((p) => p.processingCount));
|
||||||
|
if (updatedProcessingCount == 0) {
|
||||||
|
timer.cancel();
|
||||||
|
_pollingTimer = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timer.cancel();
|
||||||
|
_pollingTimer = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final syncStatus = ref.watch(syncStatusProvider);
|
||||||
|
final remainderCount = ref.watch(driftBackupProvider.select((p) => p.remainderCount));
|
||||||
|
final processingCount = ref.watch(driftBackupProvider.select((p) => p.processingCount));
|
||||||
|
final readyForUploadCount = remainderCount - processingCount;
|
||||||
|
|
||||||
|
ref.listen<int>(driftBackupProvider.select((p) => p.processingCount), (previous, next) {
|
||||||
|
if (next > 0 && _pollingTimer == null) {
|
||||||
|
_startPollingIfNeeded();
|
||||||
|
} else if (next == 0 && _pollingTimer != null) {
|
||||||
|
_pollingTimer?.cancel();
|
||||||
|
_pollingTimer = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!syncStatus.isHashing) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 1.0),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.colorScheme.surfaceContainerHigh.withValues(alpha: 0.5),
|
||||||
|
shape: BoxShape.rectangle,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"preparing".t(context: context),
|
||||||
|
style: context.textTheme.labelLarge?.copyWith(
|
||||||
|
color: context.colorScheme.onSurface.withAlpha(200),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 1.5)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
processingCount.toString(),
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
color: context.colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||||
|
decoration: BoxDecoration(color: context.colorScheme.primary.withValues(alpha: 0.1)),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"ready_for_upload".t(context: context),
|
||||||
|
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
readyForUploadCount.toString(),
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
color: context.primaryColor,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,6 +123,7 @@ class DriftBackupState {
|
|||||||
final int totalCount;
|
final int totalCount;
|
||||||
final int backupCount;
|
final int backupCount;
|
||||||
final int remainderCount;
|
final int remainderCount;
|
||||||
|
final int processingCount;
|
||||||
|
|
||||||
final int enqueueCount;
|
final int enqueueCount;
|
||||||
final int enqueueTotalCount;
|
final int enqueueTotalCount;
|
||||||
@ -135,6 +136,7 @@ class DriftBackupState {
|
|||||||
required this.totalCount,
|
required this.totalCount,
|
||||||
required this.backupCount,
|
required this.backupCount,
|
||||||
required this.remainderCount,
|
required this.remainderCount,
|
||||||
|
required this.processingCount,
|
||||||
required this.enqueueCount,
|
required this.enqueueCount,
|
||||||
required this.enqueueTotalCount,
|
required this.enqueueTotalCount,
|
||||||
required this.isCanceling,
|
required this.isCanceling,
|
||||||
@ -145,6 +147,7 @@ class DriftBackupState {
|
|||||||
int? totalCount,
|
int? totalCount,
|
||||||
int? backupCount,
|
int? backupCount,
|
||||||
int? remainderCount,
|
int? remainderCount,
|
||||||
|
int? processingCount,
|
||||||
int? enqueueCount,
|
int? enqueueCount,
|
||||||
int? enqueueTotalCount,
|
int? enqueueTotalCount,
|
||||||
bool? isCanceling,
|
bool? isCanceling,
|
||||||
@ -154,6 +157,7 @@ class DriftBackupState {
|
|||||||
totalCount: totalCount ?? this.totalCount,
|
totalCount: totalCount ?? this.totalCount,
|
||||||
backupCount: backupCount ?? this.backupCount,
|
backupCount: backupCount ?? this.backupCount,
|
||||||
remainderCount: remainderCount ?? this.remainderCount,
|
remainderCount: remainderCount ?? this.remainderCount,
|
||||||
|
processingCount: processingCount ?? this.processingCount,
|
||||||
enqueueCount: enqueueCount ?? this.enqueueCount,
|
enqueueCount: enqueueCount ?? this.enqueueCount,
|
||||||
enqueueTotalCount: enqueueTotalCount ?? this.enqueueTotalCount,
|
enqueueTotalCount: enqueueTotalCount ?? this.enqueueTotalCount,
|
||||||
isCanceling: isCanceling ?? this.isCanceling,
|
isCanceling: isCanceling ?? this.isCanceling,
|
||||||
@ -163,7 +167,7 @@ class DriftBackupState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'DriftBackupState(totalCount: $totalCount, backupCount: $backupCount, remainderCount: $remainderCount, enqueueCount: $enqueueCount, enqueueTotalCount: $enqueueTotalCount, isCanceling: $isCanceling, uploadItems: $uploadItems)';
|
return 'DriftBackupState(totalCount: $totalCount, backupCount: $backupCount, remainderCount: $remainderCount, processingCount: $processingCount, enqueueCount: $enqueueCount, enqueueTotalCount: $enqueueTotalCount, isCanceling: $isCanceling, uploadItems: $uploadItems)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -174,6 +178,7 @@ class DriftBackupState {
|
|||||||
return other.totalCount == totalCount &&
|
return other.totalCount == totalCount &&
|
||||||
other.backupCount == backupCount &&
|
other.backupCount == backupCount &&
|
||||||
other.remainderCount == remainderCount &&
|
other.remainderCount == remainderCount &&
|
||||||
|
other.processingCount == processingCount &&
|
||||||
other.enqueueCount == enqueueCount &&
|
other.enqueueCount == enqueueCount &&
|
||||||
other.enqueueTotalCount == enqueueTotalCount &&
|
other.enqueueTotalCount == enqueueTotalCount &&
|
||||||
other.isCanceling == isCanceling &&
|
other.isCanceling == isCanceling &&
|
||||||
@ -185,6 +190,7 @@ class DriftBackupState {
|
|||||||
return totalCount.hashCode ^
|
return totalCount.hashCode ^
|
||||||
backupCount.hashCode ^
|
backupCount.hashCode ^
|
||||||
remainderCount.hashCode ^
|
remainderCount.hashCode ^
|
||||||
|
processingCount.hashCode ^
|
||||||
enqueueCount.hashCode ^
|
enqueueCount.hashCode ^
|
||||||
enqueueTotalCount.hashCode ^
|
enqueueTotalCount.hashCode ^
|
||||||
isCanceling.hashCode ^
|
isCanceling.hashCode ^
|
||||||
@ -203,6 +209,7 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
|||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
backupCount: 0,
|
backupCount: 0,
|
||||||
remainderCount: 0,
|
remainderCount: 0,
|
||||||
|
processingCount: 0,
|
||||||
enqueueCount: 0,
|
enqueueCount: 0,
|
||||||
enqueueTotalCount: 0,
|
enqueueTotalCount: 0,
|
||||||
isCanceling: false,
|
isCanceling: false,
|
||||||
@ -313,13 +320,14 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getBackupStatus(String userId) async {
|
Future<void> getBackupStatus(String userId) async {
|
||||||
final [totalCount, backupCount, remainderCount] = await Future.wait([
|
final counts = await _uploadService.getBackupCounts(userId);
|
||||||
_uploadService.getBackupTotalCount(),
|
|
||||||
_uploadService.getBackupFinishedCount(userId),
|
|
||||||
_uploadService.getBackupRemainderCount(userId),
|
|
||||||
]);
|
|
||||||
|
|
||||||
state = state.copyWith(totalCount: totalCount, backupCount: backupCount, remainderCount: remainderCount);
|
state = state.copyWith(
|
||||||
|
totalCount: counts.total,
|
||||||
|
backupCount: counts.total - counts.remainder,
|
||||||
|
remainderCount: counts.remainder,
|
||||||
|
processingCount: counts.processing,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startBackup(String userId) {
|
Future<void> startBackup(String userId) {
|
||||||
|
@ -89,16 +89,8 @@ class UploadService {
|
|||||||
return _uploadRepository.getActiveTasks(group);
|
return _uploadRepository.getActiveTasks(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getBackupTotalCount() {
|
Future<({int total, int remainder, int processing})> getBackupCounts(String userId) {
|
||||||
return _backupRepository.getTotalCount();
|
return _backupRepository.getAllCounts(userId);
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> getBackupRemainderCount(String userId) {
|
|
||||||
return _backupRepository.getRemainderCount(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> getBackupFinishedCount(String userId) {
|
|
||||||
return _backupRepository.getBackupCount(userId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> manualBackup(List<LocalAsset> localAssets) async {
|
Future<void> manualBackup(List<LocalAsset> localAssets) async {
|
||||||
|
@ -19,6 +19,7 @@ import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/oauth.provider.dart';
|
import 'package:immich_mobile/providers/oauth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/utils/provider_utils.dart';
|
import 'package:immich_mobile/utils/provider_utils.dart';
|
||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
@ -193,6 +194,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
if (isBeta) {
|
if (isBeta) {
|
||||||
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
||||||
handleSyncFlow();
|
handleSyncFlow();
|
||||||
|
ref.read(websocketProvider.notifier).connect();
|
||||||
context.replaceRoute(const TabShellRoute());
|
context.replaceRoute(const TabShellRoute());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user