backup_controller_page.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import 'dart:io';
  2. import 'package:easy_localization/easy_localization.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_hooks/flutter_hooks.dart';
  5. import 'package:hooks_riverpod/hooks_riverpod.dart';
  6. import 'package:immich_mobile/extensions/build_context_extensions.dart';
  7. import 'package:immich_mobile/modules/album/providers/album.provider.dart';
  8. import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
  9. import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart';
  10. import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
  11. import 'package:immich_mobile/modules/backup/ui/current_backup_asset_info_box.dart';
  12. import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
  13. import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
  14. import 'package:immich_mobile/routing/router.dart';
  15. import 'package:immich_mobile/shared/providers/websocket.provider.dart';
  16. import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';
  17. class BackupControllerPage extends HookConsumerWidget {
  18. const BackupControllerPage({Key? key}) : super(key: key);
  19. @override
  20. Widget build(BuildContext context, WidgetRef ref) {
  21. BackUpState backupState = ref.watch(backupProvider);
  22. final hasAnyAlbum = backupState.selectedBackupAlbums.isNotEmpty;
  23. bool hasExclusiveAccess =
  24. backupState.backupProgress != BackUpProgressEnum.inBackground;
  25. bool shouldBackup = backupState.allUniqueAssets.length -
  26. backupState.selectedAlbumsBackupAssetsIds.length ==
  27. 0 ||
  28. !hasExclusiveAccess
  29. ? false
  30. : true;
  31. useEffect(
  32. () {
  33. if (backupState.backupProgress != BackUpProgressEnum.inProgress &&
  34. backupState.backupProgress != BackUpProgressEnum.manualInProgress) {
  35. ref.watch(backupProvider.notifier).getBackupInfo();
  36. }
  37. // Update the background settings information just to make sure we
  38. // have the latest, since the platform channel will not update
  39. // automatically
  40. if (Platform.isIOS) {
  41. ref.watch(iOSBackgroundSettingsProvider.notifier).refresh();
  42. }
  43. ref
  44. .watch(websocketProvider.notifier)
  45. .stopListenToEvent('on_upload_success');
  46. return null;
  47. },
  48. [],
  49. );
  50. Widget buildSelectedAlbumName() {
  51. var text = "backup_controller_page_backup_selected".tr();
  52. var albums = ref.watch(backupProvider).selectedBackupAlbums;
  53. if (albums.isNotEmpty) {
  54. for (var album in albums) {
  55. if (album.name == "Recent" || album.name == "Recents") {
  56. text += "${album.name} (${'backup_all'.tr()}), ";
  57. } else {
  58. text += "${album.name}, ";
  59. }
  60. }
  61. return Padding(
  62. padding: const EdgeInsets.only(top: 8.0),
  63. child: Text(
  64. text.trim().substring(0, text.length - 2),
  65. style: context.textTheme.labelLarge?.copyWith(
  66. color: context.primaryColor,
  67. ),
  68. ),
  69. );
  70. } else {
  71. return Padding(
  72. padding: const EdgeInsets.only(top: 8.0),
  73. child: Text(
  74. "backup_controller_page_none_selected".tr(),
  75. style: context.textTheme.labelLarge?.copyWith(
  76. color: context.primaryColor,
  77. ),
  78. ),
  79. );
  80. }
  81. }
  82. Widget buildExcludedAlbumName() {
  83. var text = "backup_controller_page_excluded".tr();
  84. var albums = ref.watch(backupProvider).excludedBackupAlbums;
  85. if (albums.isNotEmpty) {
  86. for (var album in albums) {
  87. text += "${album.name}, ";
  88. }
  89. return Padding(
  90. padding: const EdgeInsets.only(top: 8.0),
  91. child: Text(
  92. text.trim().substring(0, text.length - 2),
  93. style: context.textTheme.labelLarge?.copyWith(
  94. color: Colors.red[300],
  95. ),
  96. ),
  97. );
  98. } else {
  99. return const SizedBox();
  100. }
  101. }
  102. buildFolderSelectionTile() {
  103. return Padding(
  104. padding: const EdgeInsets.only(top: 8.0),
  105. child: Card(
  106. shape: RoundedRectangleBorder(
  107. borderRadius: BorderRadius.circular(20),
  108. side: BorderSide(
  109. color: context.isDarkTheme
  110. ? const Color.fromARGB(255, 56, 56, 56)
  111. : Colors.black12,
  112. width: 1,
  113. ),
  114. ),
  115. elevation: 0,
  116. borderOnForeground: false,
  117. child: ListTile(
  118. minVerticalPadding: 18,
  119. title: Text(
  120. "backup_controller_page_albums",
  121. style: context.textTheme.titleMedium,
  122. ).tr(),
  123. subtitle: Padding(
  124. padding: const EdgeInsets.only(top: 8.0),
  125. child: Column(
  126. crossAxisAlignment: CrossAxisAlignment.start,
  127. children: [
  128. Text(
  129. "backup_controller_page_to_backup",
  130. style: context.textTheme.bodyMedium,
  131. ).tr(),
  132. buildSelectedAlbumName(),
  133. buildExcludedAlbumName(),
  134. ],
  135. ),
  136. ),
  137. trailing: ElevatedButton(
  138. onPressed: () async {
  139. await context.autoPush(const BackupAlbumSelectionRoute());
  140. // waited until returning from selection
  141. await ref
  142. .read(backupProvider.notifier)
  143. .backupAlbumSelectionDone();
  144. // waited until backup albums are stored in DB
  145. ref.read(albumProvider.notifier).getDeviceAlbums();
  146. },
  147. child: const Text(
  148. "backup_controller_page_select",
  149. style: TextStyle(
  150. fontWeight: FontWeight.bold,
  151. ),
  152. ).tr(),
  153. ),
  154. ),
  155. ),
  156. );
  157. }
  158. void startBackup() {
  159. ref.watch(errorBackupListProvider.notifier).empty();
  160. if (ref.watch(backupProvider).backupProgress !=
  161. BackUpProgressEnum.inBackground) {
  162. ref.watch(backupProvider.notifier).startBackupProcess();
  163. }
  164. }
  165. Widget buildBackupButton() {
  166. return Padding(
  167. padding: const EdgeInsets.only(
  168. top: 24,
  169. ),
  170. child: Container(
  171. child: backupState.backupProgress == BackUpProgressEnum.inProgress ||
  172. backupState.backupProgress ==
  173. BackUpProgressEnum.manualInProgress
  174. ? ElevatedButton(
  175. style: ElevatedButton.styleFrom(
  176. foregroundColor: Colors.grey[50],
  177. backgroundColor: Colors.red[300],
  178. // padding: const EdgeInsets.all(14),
  179. ),
  180. onPressed: () {
  181. if (backupState.backupProgress ==
  182. BackUpProgressEnum.manualInProgress) {
  183. ref.read(manualUploadProvider.notifier).cancelBackup();
  184. } else {
  185. ref.read(backupProvider.notifier).cancelBackup();
  186. }
  187. },
  188. child: const Text(
  189. "backup_controller_page_cancel",
  190. style: TextStyle(
  191. fontSize: 14,
  192. fontWeight: FontWeight.bold,
  193. ),
  194. ).tr(),
  195. )
  196. : ElevatedButton(
  197. onPressed: shouldBackup ? startBackup : null,
  198. child: const Text(
  199. "backup_controller_page_start_backup",
  200. style: TextStyle(
  201. fontSize: 16,
  202. fontWeight: FontWeight.bold,
  203. ),
  204. ).tr(),
  205. ),
  206. ),
  207. );
  208. }
  209. buildBackgroundBackupInfo() {
  210. return const ListTile(
  211. leading: Icon(Icons.info_outline_rounded),
  212. title: Text(
  213. "Background backup is currently running, cannot start manual backup",
  214. ),
  215. );
  216. }
  217. return Scaffold(
  218. appBar: AppBar(
  219. elevation: 0,
  220. title: const Text(
  221. "backup_controller_page_backup",
  222. ).tr(),
  223. leading: IconButton(
  224. onPressed: () {
  225. ref.watch(websocketProvider.notifier).listenUploadEvent();
  226. context.autoPop(true);
  227. },
  228. splashRadius: 24,
  229. icon: const Icon(
  230. Icons.arrow_back_ios_rounded,
  231. ),
  232. ),
  233. actions: [
  234. Padding(
  235. padding: const EdgeInsets.only(right: 8.0),
  236. child: IconButton(
  237. onPressed: () => context.autoPush(const BackupOptionsRoute()),
  238. splashRadius: 24,
  239. icon: const Icon(
  240. Icons.settings_outlined,
  241. ),
  242. ),
  243. ),
  244. ],
  245. ),
  246. body: Padding(
  247. padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 32),
  248. child: ListView(
  249. // crossAxisAlignment: CrossAxisAlignment.start,
  250. children: hasAnyAlbum
  251. ? [
  252. buildFolderSelectionTile(),
  253. BackupInfoCard(
  254. title: "backup_controller_page_total".tr(),
  255. subtitle: "backup_controller_page_total_sub".tr(),
  256. info: ref.watch(backupProvider).availableAlbums.isEmpty
  257. ? "..."
  258. : "${backupState.allUniqueAssets.length}",
  259. ),
  260. BackupInfoCard(
  261. title: "backup_controller_page_backup".tr(),
  262. subtitle: "backup_controller_page_backup_sub".tr(),
  263. info: ref.watch(backupProvider).availableAlbums.isEmpty
  264. ? "..."
  265. : "${backupState.selectedAlbumsBackupAssetsIds.length}",
  266. ),
  267. BackupInfoCard(
  268. title: "backup_controller_page_remainder".tr(),
  269. subtitle: "backup_controller_page_remainder_sub".tr(),
  270. info: ref.watch(backupProvider).availableAlbums.isEmpty
  271. ? "..."
  272. : "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
  273. ),
  274. const Divider(),
  275. const CurrentUploadingAssetInfoBox(),
  276. if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
  277. buildBackupButton(),
  278. ]
  279. : [buildFolderSelectionTile()],
  280. ),
  281. ),
  282. );
  283. }
  284. }