forked from Cutlery/immich
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			main
			...
			feat/mobil
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					6c8ac23ef8 | ||
| 
						 | 
					12a1e4c9c6 | ||
| 
						 | 
					5c3e29440f | ||
| 
						 | 
					a3a447a493 | 
@ -507,5 +507,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"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -268,6 +268,14 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        actions: [
 | 
			
		||||
          if (Platform.isIOS)
 | 
			
		||||
            IconButton(
 | 
			
		||||
              onPressed: () => context.pushRoute(const OvernightUploadRoute()),
 | 
			
		||||
              splashRadius: 24,
 | 
			
		||||
              icon: const Icon(
 | 
			
		||||
                Icons.bedtime_outlined,
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          Padding(
 | 
			
		||||
            padding: const EdgeInsets.only(right: 8.0),
 | 
			
		||||
            child: IconButton(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										251
									
								
								mobile/lib/modules/backup/views/overnight_upload_page.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								mobile/lib/modules/backup/views/overnight_upload_page.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,251 @@
 | 
			
		||||
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 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(),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -6,7 +6,7 @@ part of 'map_marker.provider.dart';
 | 
			
		||||
// RiverpodGenerator
 | 
			
		||||
// **************************************************************************
 | 
			
		||||
 | 
			
		||||
String _$mapMarkersHash() => r'90b00b7f85c54b19f56c7d55d3ad8575c09dab3c';
 | 
			
		||||
String _$mapMarkersHash() => r'737d52f3d02e6a458b11d730f2fe522c39ee1ebf';
 | 
			
		||||
 | 
			
		||||
/// See also [mapMarkers].
 | 
			
		||||
@ProviderFor(mapMarkers)
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ part of 'map_state.provider.dart';
 | 
			
		||||
// RiverpodGenerator
 | 
			
		||||
// **************************************************************************
 | 
			
		||||
 | 
			
		||||
String _$mapStateNotifierHash() => r'6408d616ec9fc0d1ff26e25692417c43504ff754';
 | 
			
		||||
String _$mapStateNotifierHash() => r'87a8623f726d438d115d5a15609c71372726ee2f';
 | 
			
		||||
 | 
			
		||||
/// See also [MapStateNotifier].
 | 
			
		||||
@ProviderFor(MapStateNotifier)
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import 'package:immich_mobile/modules/album/views/asset_selection_page.dart';
 | 
			
		||||
import 'package:immich_mobile/modules/album/views/create_album_page.dart';
 | 
			
		||||
import 'package:immich_mobile/modules/album/views/library_page.dart';
 | 
			
		||||
import 'package:immich_mobile/modules/backup/views/backup_options_page.dart';
 | 
			
		||||
import 'package:immich_mobile/modules/backup/views/overnight_upload_page.dart';
 | 
			
		||||
import 'package:immich_mobile/modules/map/views/map_location_picker_page.dart';
 | 
			
		||||
import 'package:immich_mobile/modules/map/views/map_page.dart';
 | 
			
		||||
import 'package:immich_mobile/modules/memories/models/memory.dart';
 | 
			
		||||
@ -212,7 +213,7 @@ class AppRouter extends _$AppRouter {
 | 
			
		||||
      transitionsBuilder: TransitionsBuilders.slideLeft,
 | 
			
		||||
      durationInMilliseconds: 200,
 | 
			
		||||
    ),
 | 
			
		||||
    CustomRoute(
 | 
			
		||||
    AutoRoute(
 | 
			
		||||
      page: MapLocationPickerRoute.page,
 | 
			
		||||
      guards: [_authGuard, _duplicateGuard],
 | 
			
		||||
    ),
 | 
			
		||||
@ -225,6 +226,10 @@ class AppRouter extends _$AppRouter {
 | 
			
		||||
      guards: [_authGuard, _duplicateGuard],
 | 
			
		||||
      transitionsBuilder: TransitionsBuilders.noTransition,
 | 
			
		||||
    ),
 | 
			
		||||
    AutoRoute(
 | 
			
		||||
      page: OvernightUploadRoute.page,
 | 
			
		||||
      guards: [_authGuard, _duplicateGuard],
 | 
			
		||||
    ),
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -216,6 +216,12 @@ abstract class _$AppRouter extends RootStackRouter {
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
    OvernightUploadRoute.name: (routeData) {
 | 
			
		||||
      return AutoRoutePage<dynamic>(
 | 
			
		||||
        routeData: routeData,
 | 
			
		||||
        child: const OvernightUploadPage(),
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
    PartnerDetailRoute.name: (routeData) {
 | 
			
		||||
      final args = routeData.argsAs<PartnerDetailRouteArgs>();
 | 
			
		||||
      return AutoRoutePage<dynamic>(
 | 
			
		||||
@ -988,6 +994,20 @@ class MemoryRouteArgs {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// generated route for
 | 
			
		||||
/// [OvernightUploadPage]
 | 
			
		||||
class OvernightUploadRoute extends PageRouteInfo<void> {
 | 
			
		||||
  const OvernightUploadRoute({List<PageRouteInfo>? children})
 | 
			
		||||
      : super(
 | 
			
		||||
          OvernightUploadRoute.name,
 | 
			
		||||
          initialChildren: children,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
  static const String name = 'OvernightUploadRoute';
 | 
			
		||||
 | 
			
		||||
  static const PageInfo<void> page = PageInfo<void>(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// generated route for
 | 
			
		||||
/// [PartnerDetailPage]
 | 
			
		||||
class PartnerDetailRoute extends PageRouteInfo<PartnerDetailRouteArgs> {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user