Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions mobile/lib/domain/utils/background_sync.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import 'package:immich_mobile/utils/isolate.dart';
import 'package:worker_manager/worker_manager.dart';

typedef SyncCallback = void Function();
typedef SyncCallbackWithResult<T> = void Function(T result);
typedef SyncErrorCallback = void Function(String error);

class BackgroundSyncManager {
final SyncCallback? onRemoteSyncStart;
final SyncCallback? onRemoteSyncComplete;
final SyncCallbackWithResult<bool?>? onRemoteSyncComplete;
final SyncErrorCallback? onRemoteSyncError;

final SyncCallback? onLocalSyncStart;
Expand Down Expand Up @@ -156,15 +157,18 @@ class BackgroundSyncManager {
debugLabel: 'remote-sync',
);
return _syncTask!
.then((result) => result ?? false)
.whenComplete(() {
onRemoteSyncComplete?.call();
_syncTask = null;
.then((result) {
final success = result ?? false;
onRemoteSyncComplete?.call(success);
return success;
})
.catchError((error) {
onRemoteSyncError?.call(error.toString());
_syncTask = null;
return false;
})
.whenComplete(() {
_syncTask = null;
});
}

Expand Down
4 changes: 0 additions & 4 deletions mobile/lib/pages/backup/drift_backup.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {

ref.read(driftBackupProvider.notifier).updateSyncing(true);
syncSuccess = await ref.read(backgroundSyncProvider).syncRemote();
ref
.read(driftBackupProvider.notifier)
.updateError(syncSuccess == true ? BackupError.none : BackupError.syncFailed);
ref.read(driftBackupProvider.notifier).updateSyncing(false);

if (mounted) {
Expand Down Expand Up @@ -94,7 +91,6 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {

if (syncSuccess == false) {
Logger("DriftBackupPage").warning("Remote sync did not complete successfully, skipping backup");
backupNotifier.updateError(BackupError.syncFailed);
return;
}
await backupNotifier.startBackup(currentUser.id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
return backupNotifier.startBackup(user.id);
} else {
Logger('DriftBackupAlbumSelectionPage').warning('Background sync failed, not starting backup');
backupNotifier.updateError(BackupError.syncFailed);
}
}),
),
Expand Down
1 change: 0 additions & 1 deletion mobile/lib/pages/backup/drift_backup_options.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ class DriftBackupOptionsPage extends ConsumerWidget {
return backupNotifier.startBackup(currentUser.id);
} else {
Logger('DriftBackupOptionsPage').warning('Background sync failed, not starting backup');
backupNotifier.updateError(BackupError.syncFailed);
}
}),
),
Expand Down
1 change: 0 additions & 1 deletion mobile/lib/pages/common/splash_screen.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
_resumeBackup(backupProvider),
]);
} else {
backupProvider.updateError(BackupError.syncFailed);
await backgroundManager.hashAssets();
}

Expand Down
1 change: 0 additions & 1 deletion mobile/lib/providers/app_life_cycle.provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
_resumeBackup(),
]);
} else {
_ref.read(driftBackupProvider.notifier).updateError(BackupError.syncFailed);
await _safeRun(backgroundManager.hashAssets(), "hashAssets");
}

Expand Down
13 changes: 11 additions & 2 deletions mobile/lib/providers/background_sync.provider.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/utils/background_sync.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/sync_status.provider.dart';

final backgroundSyncProvider = Provider<BackgroundSyncManager>((ref) {
final syncStatusNotifier = ref.read(syncStatusProvider.notifier);
final backupProvider = ref.read(driftBackupProvider.notifier);

final manager = BackgroundSyncManager(
onRemoteSyncStart: syncStatusNotifier.startRemoteSync,
onRemoteSyncComplete: syncStatusNotifier.completeRemoteSync,
onRemoteSyncStart: () {
syncStatusNotifier.startRemoteSync();
backupProvider.updateError(BackupError.none);
},
onRemoteSyncComplete: (isSuccess) {
syncStatusNotifier.completeRemoteSync();
backupProvider.updateError(isSuccess == true ? BackupError.none : BackupError.syncFailed);
},
onRemoteSyncError: syncStatusNotifier.errorRemoteSync,
onLocalSyncStart: syncStatusNotifier.startLocalSync,
onLocalSyncComplete: syncStatusNotifier.completeLocalSync,
Expand Down
104 changes: 61 additions & 43 deletions mobile/lib/widgets/common/immich_sliver_app_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -162,48 +162,32 @@ class _ProfileIndicator extends ConsumerWidget {
}
}

const double _kBadgeWidgetSize = 30.0;

class _BackupIndicator extends ConsumerWidget {
const _BackupIndicator();

@override
Widget build(BuildContext context, WidgetRef ref) {
const widgetSize = 30.0;
final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none));
final indicatorIcon = hasError
? Icon(
Icons.warning_rounded,
size: 12,
color: context.colorScheme.error,
semanticLabel: 'backup_controller_page_backup'.tr(),
)
: _getBackupBadgeIcon(context, ref);
final badgeBackground = hasError ? context.colorScheme.errorContainer : context.colorScheme.surfaceContainer;
final indicatorIcon = _getBackupBadgeIcon(context, ref);

return InkWell(
onTap: () => context.pushRoute(const DriftBackupRoute()),
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: Badge(
label: Container(
width: widgetSize / 2,
height: widgetSize / 2,
decoration: BoxDecoration(
color: badgeBackground,
border: Border.all(color: context.colorScheme.outline.withValues(alpha: .3)),
borderRadius: BorderRadius.circular(widgetSize / 2),
),
child: indicatorIcon,
),
label: indicatorIcon,
backgroundColor: Colors.transparent,
alignment: Alignment.bottomRight,
isLabelVisible: indicatorIcon != null,
offset: const Offset(-2, -12),
child: Icon(Icons.backup_rounded, size: widgetSize, color: context.primaryColor),
child: Icon(Icons.backup_rounded, size: _kBadgeWidgetSize, color: context.primaryColor),
),
);
}

Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) {
final backupStateStream = ref.watch(settingsProvider).watch(Setting.enableBackup);
final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none));
final isDarkTheme = context.isDarkTheme;
final iconColor = isDarkTheme ? Colors.white : Colors.black;
final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty));
Expand All @@ -215,42 +199,76 @@ class _BackupIndicator extends ConsumerWidget {
final backupEnabled = snapshot.data ?? false;

if (!backupEnabled) {
return Icon(
Icons.cloud_off_rounded,
size: 9,
color: iconColor,
semanticLabel: 'backup_controller_page_backup'.tr(),
return _BadgeLabel(
Icon(
Icons.cloud_off_rounded,
size: 9,
color: iconColor,
semanticLabel: 'backup_controller_page_backup'.tr(),
),
);
}

if (hasError) {
return _BadgeLabel(
Icon(
Icons.warning_rounded,
size: 12,
color: context.colorScheme.error,
semanticLabel: 'backup_controller_page_backup'.tr(),
),
backgroundColor: context.colorScheme.errorContainer,
);
}

if (isUploading) {
return Container(
padding: const EdgeInsets.all(3.5),
child: Theme(
data: context.themeData.copyWith(
progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true),
),
child: CircularProgressIndicator(
strokeWidth: 2,
strokeCap: StrokeCap.round,
valueColor: AlwaysStoppedAnimation<Color>(iconColor),
semanticsLabel: 'backup_controller_page_backup'.tr(),
return _BadgeLabel(
Container(
padding: const EdgeInsets.all(3.5),
child: Theme(
data: context.themeData.copyWith(
progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true),
),
child: CircularProgressIndicator(
strokeWidth: 2,
strokeCap: StrokeCap.round,
valueColor: AlwaysStoppedAnimation<Color>(iconColor),
semanticsLabel: 'backup_controller_page_backup'.tr(),
),
),
),
);
}

return Icon(
Icons.check_outlined,
size: 9,
color: iconColor,
semanticLabel: 'backup_controller_page_backup'.tr(),
return _BadgeLabel(
Icon(Icons.check_outlined, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()),
);
},
);
}
}

class _BadgeLabel extends StatelessWidget {
final Widget indicator;
final Color? backgroundColor;

const _BadgeLabel(this.indicator, {this.backgroundColor});

@override
Widget build(BuildContext context) {
return Container(
width: _kBadgeWidgetSize / 2,
height: _kBadgeWidgetSize / 2,
decoration: BoxDecoration(
color: backgroundColor ?? context.colorScheme.surfaceContainer,
border: Border.all(color: context.colorScheme.outline.withValues(alpha: .3)),
borderRadius: BorderRadius.circular(_kBadgeWidgetSize / 2),
),
child: indicator,
);
}
}

class _SyncStatusIndicator extends ConsumerStatefulWidget {
const _SyncStatusIndicator();

Expand Down
Loading