mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00: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
6 changed files with 280 additions and 97 deletions
|
|
@ -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/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/backup/backup_info_card.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftBackupPage extends ConsumerStatefulWidget {
|
||||
|
|
@ -29,6 +32,9 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WakelockPlus.enable();
|
||||
|
||||
final currentUser = ref.read(currentUserProvider);
|
||||
if (currentUser == null) {
|
||||
return;
|
||||
|
|
@ -44,6 +50,12 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||
});
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
super.dispose();
|
||||
WakelockPlus.disable();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedAlbum = ref
|
||||
|
|
@ -260,12 +272,205 @@ class _RemainderCard extends ConsumerWidget {
|
|||
final remainderCount = ref.watch(driftBackupProvider.select((p) => p.remainderCount));
|
||||
final syncStatus = ref.watch(syncStatusProvider);
|
||||
|
||||
return BackupInfoCard(
|
||||
title: "backup_controller_page_remainder".tr(),
|
||||
subtitle: "backup_controller_page_remainder_sub".tr(),
|
||||
info: remainderCount.toString(),
|
||||
isLoading: syncStatus.isRemoteSyncing,
|
||||
onTap: () => context.pushRoute(const DriftBackupAssetDetailRoute()),
|
||||
return Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
side: BorderSide(color: context.colorScheme.outlineVariant, width: 1),
|
||||
),
|
||||
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()),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue