Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mobile): ios overnight upload #8385

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 10 additions & 2 deletions mobile/assets/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -511,5 +511,13 @@
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
"viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Use as Main Asset",
"viewer_unstack": "Un-Stack"
}
"viewer_unstack": "Un-Stack",
"overnight_upload_inprogress": "Uploading",
"overnight_upload_stop": "Stop Upload",
"overnight_upload_title": "Over Night Upload",
"overnight_upload_description": "Immich will run background backup with a darkened screen",
"overnight_upload_prereq": "Make sure the device is:",
"overnight_upload_wifi": "Connected to WiFi",
"overnight_upload_charger": "Connected to Charger",
"overnight_upload_start": "Start Overnight Upload"
}
129 changes: 129 additions & 0 deletions mobile/lib/modules/backup/ui/overnight_backup.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import 'dart:math';

import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:wakelock_plus/wakelock_plus.dart';

class OvernightBackup extends HookConsumerWidget {
final Function() stopOvernightBackup;

const OvernightBackup({required this.stopOvernightBackup, super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final animationController =
useAnimationController(duration: const Duration(hours: 1));
final reset = useState(false);
final from = useRef<Alignment>(Alignment.center);
final to = useRef<Alignment>(Alignment.center);
final tween = AlignmentTween(begin: from.value, end: to.value);

ref.listen(
backupProvider.select((value) => value.backupProgress),
(prev, next) {
if (prev == BackUpProgressEnum.inProgress &&
next != BackUpProgressEnum.inProgress) {
stopOvernightBackup();
}
},
);

void randomizeAlignment() {
final random = Random();
from.value = to.value;
final currentAlign = to.value;
var newAlignment = currentAlign;
do {
newAlignment = switch (random.nextInt(9)) {
0 => Alignment.bottomCenter,
1 => Alignment.bottomLeft,
2 => Alignment.bottomRight,
3 => Alignment.center,
4 => Alignment.centerLeft,
5 => Alignment.centerRight,
6 => Alignment.topCenter,
7 => Alignment.topLeft,
8 => Alignment.topRight,
_ => Alignment.center,
};
} while (newAlignment == currentAlign);
to.value = newAlignment;

animationController.reset();
animationController.forward();
WakelockPlus.enable();
}

void onAnimationStateChange(AnimationStatus status) {
if (status == AnimationStatus.completed) {
/// This is used to force a rebuild of the widget to call the randomizeAlignment() method
/// through the useEffect hook which takes care of animating the icon to the new alignment
reset.value = !reset.value;
}
}

useEffect(
() {
WidgetsBinding.instance.addPostFrameCallback((_) {
animationController.addStatusListener(onAnimationStateChange);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
// Start animating
reset.value = !reset.value;
});
return () {
WakelockPlus.disable();
animationController.removeStatusListener(onAnimationStateChange);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
};
},
[],
);

/// The following effect is called on each rebuild of the widget and handles the starts the animation
/// This is also called on screen orientation change and handles updating the alignment and size of the icon
/// accordingly
useEffect(() {
randomizeAlignment();
return null;
});

return Stack(
children: [
Positioned.fill(
child: AlignTransition(
alignment: tween.animate(animationController),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.upload_rounded,
size: context.height / 4,
color: Colors.grey[850],
),
Text(
"overnight_upload_inprogress",
style: context.textTheme.titleLarge
?.copyWith(color: Colors.grey[800]),
).tr(),
const SizedBox(height: 10),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStatePropertyAll(Colors.grey[850]),
),
onPressed: stopOvernightBackup,
child: const Text("overnight_upload_stop").tr(),
),
],
),
),
),
],
);
}
}
123 changes: 98 additions & 25 deletions mobile/lib/modules/backup/views/backup_controller_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,73 @@ import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.da
import 'package:immich_mobile/modules/backup/ui/current_backup_asset_info_box.dart';
import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/modules/backup/ui/overnight_backup.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';

@RoutePage()
class BackupControllerPage extends HookConsumerWidget {
const BackupControllerPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final backupProgress =
ref.watch(backupProvider.select((value) => value.backupProgress));
final showOvernightBackup = useState(false);

void startBackup() {
ref.watch(errorBackupListProvider.notifier).empty();
if (ref.watch(backupProvider).backupProgress !=
BackUpProgressEnum.inBackground) {
ref.watch(backupProvider.notifier).startBackupProcess();
}
}

void stopBackup() {
if (backupProgress == BackUpProgressEnum.manualInProgress) {
ref.read(manualUploadProvider.notifier).cancelBackup();
} else {
ref.read(backupProvider.notifier).cancelBackup();
}
}

void startOvernightBackup() {
showOvernightBackup.value = true;
startBackup();
}

void stopOvernightBackup() {
showOvernightBackup.value = false;
stopBackup();
}

return ValueListenableBuilder(
valueListenable: showOvernightBackup,
child: PopScope(
canPop: false,
child: OvernightBackup(stopOvernightBackup: stopOvernightBackup),
),
builder: (_, show, child) => show
? child!
: _BackupController(
startBackup: startBackup,
stopBackup: stopBackup,
startOvernightBackup: startOvernightBackup,
),
);
}
}

class _BackupController extends HookConsumerWidget {
final void Function() startBackup;
final void Function() stopBackup;
final void Function() startOvernightBackup;

const _BackupController({
required this.startBackup,
required this.stopBackup,
required this.startOvernightBackup,
});

@override
Widget build(BuildContext context, WidgetRef ref) {
Expand Down Expand Up @@ -128,7 +188,7 @@ class BackupControllerPage extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 8.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
borderRadius: const BorderRadius.all(Radius.circular(20)),
side: BorderSide(
color: context.isDarkTheme
? const Color.fromARGB(255, 56, 56, 56)
Expand Down Expand Up @@ -180,14 +240,6 @@ class BackupControllerPage extends HookConsumerWidget {
);
}

void startBackup() {
ref.watch(errorBackupListProvider.notifier).empty();
if (ref.watch(backupProvider).backupProgress !=
BackUpProgressEnum.inBackground) {
ref.watch(backupProvider.notifier).startBackupProcess();
}
}

Widget buildBackupButton() {
return Padding(
padding: const EdgeInsets.only(
Expand All @@ -203,14 +255,7 @@ class BackupControllerPage extends HookConsumerWidget {
backgroundColor: Colors.red[300],
// padding: const EdgeInsets.all(14),
),
onPressed: () {
if (backupState.backupProgress ==
BackUpProgressEnum.manualInProgress) {
ref.read(manualUploadProvider.notifier).cancelBackup();
} else {
ref.read(backupProvider.notifier).cancelBackup();
}
},
onPressed: stopBackup,
child: const Text(
"backup_controller_page_cancel",
style: TextStyle(
Expand All @@ -219,15 +264,43 @@ class BackupControllerPage extends HookConsumerWidget {
),
).tr(),
)
: ElevatedButton(
onPressed: shouldBackup ? startBackup : null,
child: const Text(
"backup_controller_page_start_backup",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (Platform.isIOS)
ElevatedButton(
onPressed: shouldBackup ? startOvernightBackup : null,
style: context.themeData.elevatedButtonTheme.style
?.copyWith(
backgroundColor:
MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (!states.contains(MaterialState.disabled)) {
return context.colorScheme.secondary;
}
return null;
},
),
),
child: const Text(
"overnight_upload_start",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
).tr(),
),
ElevatedButton(
onPressed: shouldBackup ? startBackup : null,
child: const Text(
"backup_controller_page_start_backup",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
).tr(),
),
).tr(),
],
),
),
);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion mobile/lib/modules/map/providers/map_state.provider.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion mobile/lib/routing/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ class AppRouter extends _$AppRouter {
transitionsBuilder: TransitionsBuilders.slideLeft,
durationInMilliseconds: 200,
),
CustomRoute(
AutoRoute(
page: MapLocationPickerRoute.page,
guards: [_authGuard, _duplicateGuard],
),
Expand Down