forked from Cutlery/immich
255 lines
8.2 KiB
Dart
255 lines
8.2 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:auto_route/auto_route.dart';
|
|
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:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
|
|
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
|
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
|
|
|
@RoutePage()
|
|
class OvernightUploadPage extends HookConsumerWidget {
|
|
const OvernightUploadPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final backupProgress =
|
|
ref.watch(backupProvider.select((value) => value.backupProgress));
|
|
final isInProgress = backupProgress == BackUpProgressEnum.inProgress ||
|
|
backupProgress == BackUpProgressEnum.manualInProgress;
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
useEffect(
|
|
() {
|
|
return () {
|
|
WakelockPlus.disable();
|
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
|
};
|
|
},
|
|
[],
|
|
);
|
|
|
|
return AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 500),
|
|
child: isInProgress
|
|
? _BackupInProgress(stopBackup)
|
|
: _OvernightInfo(startBackup),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _BackupInProgress extends HookWidget {
|
|
final Function() onClick;
|
|
|
|
const _BackupInProgress(this.onClick);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
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);
|
|
|
|
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: onClick,
|
|
child: const Text("overnight_upload_stop").tr(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _OvernightInfo extends StatelessWidget {
|
|
final Function() onClick;
|
|
|
|
const _OvernightInfo(this.onClick);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text("overnight_upload_title").tr()),
|
|
body: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Expanded(
|
|
flex: 1,
|
|
child: Align(
|
|
alignment: Alignment.bottomCenter,
|
|
child: Icon(
|
|
Icons.hotel_outlined,
|
|
size: context.height * (context.isMobile ? 0.15 : 0.20),
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
flex: 2,
|
|
child: Align(
|
|
alignment: Alignment.topCenter,
|
|
child: Column(
|
|
children: [
|
|
const Text(
|
|
textAlign: TextAlign.center,
|
|
"overnight_upload_description",
|
|
).tr(),
|
|
const SizedBox(height: 10),
|
|
const Text(
|
|
textAlign: TextAlign.center,
|
|
"overnight_upload_prereq",
|
|
maxLines: 5,
|
|
).tr(),
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.wifi_rounded,
|
|
size: context.width * (context.isMobile ? 0.07 : 0.03),
|
|
),
|
|
const SizedBox(width: 10),
|
|
const Text("overnight_upload_wifi").tr(),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.battery_charging_full_rounded,
|
|
size: context.width * (context.isMobile ? 0.07 : 0.03),
|
|
),
|
|
const SizedBox(width: 10),
|
|
const Text("overnight_upload_charger").tr(),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
bottomNavigationBar: Stack(
|
|
alignment: Alignment.topCenter,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.only(bottom: context.height * 0.1),
|
|
child: ElevatedButton(
|
|
onPressed: onClick,
|
|
child: const Text("overnight_upload_start").tr(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|