backup_controller_page.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. import 'package:auto_route/auto_route.dart';
  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/modules/backup/providers/error_backup_list.provider.dart';
  7. import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
  8. import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
  9. import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
  10. import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
  11. import 'package:immich_mobile/routing/router.dart';
  12. import 'package:immich_mobile/shared/providers/websocket.provider.dart';
  13. import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';
  14. import 'package:percent_indicator/linear_percent_indicator.dart';
  15. class BackupControllerPage extends HookConsumerWidget {
  16. const BackupControllerPage({Key? key}) : super(key: key);
  17. @override
  18. Widget build(BuildContext context, WidgetRef ref) {
  19. BackUpState backupState = ref.watch(backupProvider);
  20. AuthenticationState authenticationState = ref.watch(authenticationProvider);
  21. bool shouldBackup = backupState.allUniqueAssets.length -
  22. backupState.selectedAlbumsBackupAssetsIds.length ==
  23. 0
  24. ? false
  25. : true;
  26. useEffect(
  27. () {
  28. if (backupState.backupProgress != BackUpProgressEnum.inProgress) {
  29. ref.watch(backupProvider.notifier).getBackupInfo();
  30. }
  31. ref
  32. .watch(websocketProvider.notifier)
  33. .stopListenToEvent('on_upload_success');
  34. return null;
  35. },
  36. [],
  37. );
  38. Widget _buildStorageInformation() {
  39. return ListTile(
  40. leading: Icon(
  41. Icons.storage_rounded,
  42. color: Theme.of(context).primaryColor,
  43. ),
  44. title: const Text(
  45. "backup_controller_page_server_storage",
  46. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
  47. ).tr(),
  48. subtitle: Padding(
  49. padding: const EdgeInsets.only(top: 8.0),
  50. child: Column(
  51. crossAxisAlignment: CrossAxisAlignment.start,
  52. children: [
  53. Padding(
  54. padding: const EdgeInsets.only(top: 8.0),
  55. child: LinearPercentIndicator(
  56. padding:
  57. const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
  58. barRadius: const Radius.circular(2),
  59. lineHeight: 10.0,
  60. percent: backupState.serverInfo.diskUsagePercentage / 100.0,
  61. backgroundColor: Colors.grey,
  62. progressColor: Theme.of(context).primaryColor,
  63. ),
  64. ),
  65. Padding(
  66. padding: const EdgeInsets.only(top: 12.0),
  67. child: const Text('backup_controller_page_storage_format').tr(
  68. args: [
  69. backupState.serverInfo.diskUse,
  70. backupState.serverInfo.diskSize
  71. ],
  72. ),
  73. ),
  74. ],
  75. ),
  76. ),
  77. );
  78. }
  79. ListTile _buildBackupController() {
  80. var backUpOption = authenticationState.deviceInfo.isAutoBackup
  81. ? "backup_controller_page_status_on".tr()
  82. : "backup_controller_page_status_off".tr();
  83. var isAutoBackup = authenticationState.deviceInfo.isAutoBackup;
  84. var backupBtnText = authenticationState.deviceInfo.isAutoBackup
  85. ? "backup_controller_page_turn_off".tr()
  86. : "backup_controller_page_turn_on".tr();
  87. return ListTile(
  88. isThreeLine: true,
  89. leading: isAutoBackup
  90. ? Icon(
  91. Icons.cloud_done_rounded,
  92. color: Theme.of(context).primaryColor,
  93. )
  94. : const Icon(Icons.cloud_off_rounded),
  95. title: Text(
  96. backUpOption,
  97. style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
  98. ),
  99. subtitle: Padding(
  100. padding: const EdgeInsets.symmetric(vertical: 8.0),
  101. child: Column(
  102. crossAxisAlignment: CrossAxisAlignment.start,
  103. children: [
  104. if (!isAutoBackup)
  105. const Text(
  106. "backup_controller_page_desc_backup",
  107. style: TextStyle(fontSize: 14),
  108. ).tr(),
  109. Padding(
  110. padding: const EdgeInsets.only(top: 8.0),
  111. child: OutlinedButton(
  112. style: OutlinedButton.styleFrom(
  113. side: const BorderSide(
  114. width: 1,
  115. color: Color.fromARGB(255, 220, 220, 220),
  116. ),
  117. ),
  118. onPressed: () {
  119. if (isAutoBackup) {
  120. ref
  121. .read(authenticationProvider.notifier)
  122. .setAutoBackup(false);
  123. } else {
  124. ref
  125. .read(authenticationProvider.notifier)
  126. .setAutoBackup(true);
  127. }
  128. },
  129. child: Text(
  130. backupBtnText,
  131. style: const TextStyle(fontWeight: FontWeight.bold),
  132. ),
  133. ),
  134. )
  135. ],
  136. ),
  137. ),
  138. );
  139. }
  140. Widget _buildSelectedAlbumName() {
  141. var text = "backup_controller_page_backup_selected".tr();
  142. var albums = ref.watch(backupProvider).selectedBackupAlbums;
  143. if (albums.isNotEmpty) {
  144. for (var album in albums) {
  145. if (album.name == "Recent" || album.name == "Recents") {
  146. text += "${album.name} (${'backup_all'.tr()}), ";
  147. } else {
  148. text += "${album.name}, ";
  149. }
  150. }
  151. return Padding(
  152. padding: const EdgeInsets.only(top: 8.0),
  153. child: Text(
  154. text.trim().substring(0, text.length - 2),
  155. style: TextStyle(
  156. color: Theme.of(context).primaryColor,
  157. fontSize: 12,
  158. fontWeight: FontWeight.bold,
  159. ),
  160. ),
  161. );
  162. } else {
  163. return Padding(
  164. padding: const EdgeInsets.only(top: 8.0),
  165. child: Text(
  166. "backup_controller_page_none_selected".tr(),
  167. style: TextStyle(
  168. color: Theme.of(context).primaryColor,
  169. fontSize: 12,
  170. fontWeight: FontWeight.bold,
  171. ),
  172. ),
  173. );
  174. }
  175. }
  176. Widget _buildExcludedAlbumName() {
  177. var text = "backup_controller_page_excluded".tr();
  178. var albums = ref.watch(backupProvider).excludedBackupAlbums;
  179. if (albums.isNotEmpty) {
  180. for (var album in albums) {
  181. text += "${album.name}, ";
  182. }
  183. return Padding(
  184. padding: const EdgeInsets.only(top: 8.0),
  185. child: Text(
  186. text.trim().substring(0, text.length - 2),
  187. style: TextStyle(
  188. color: Colors.red[300],
  189. fontSize: 12,
  190. fontWeight: FontWeight.bold,
  191. ),
  192. ),
  193. );
  194. } else {
  195. return const SizedBox();
  196. }
  197. }
  198. _buildFolderSelectionTile() {
  199. return Card(
  200. shape: RoundedRectangleBorder(
  201. borderRadius: BorderRadius.circular(5), // if you need this
  202. side: const BorderSide(
  203. color: Colors.black12,
  204. width: 1,
  205. ),
  206. ),
  207. elevation: 0,
  208. borderOnForeground: false,
  209. child: ListTile(
  210. minVerticalPadding: 15,
  211. title: const Text(
  212. "backup_controller_page_albums",
  213. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
  214. ).tr(),
  215. subtitle: Padding(
  216. padding: const EdgeInsets.only(top: 8.0),
  217. child: Column(
  218. crossAxisAlignment: CrossAxisAlignment.start,
  219. children: [
  220. const Text(
  221. "backup_controller_page_to_backup",
  222. style: TextStyle(color: Color(0xFF808080), fontSize: 12),
  223. ).tr(),
  224. _buildSelectedAlbumName(),
  225. _buildExcludedAlbumName()
  226. ],
  227. ),
  228. ),
  229. trailing: OutlinedButton(
  230. style: OutlinedButton.styleFrom(
  231. enableFeedback: true,
  232. side: const BorderSide(
  233. width: 1,
  234. color: Color.fromARGB(255, 220, 220, 220),
  235. ),
  236. ),
  237. onPressed: () {
  238. AutoRouter.of(context).push(const BackupAlbumSelectionRoute());
  239. },
  240. child: Padding(
  241. padding: const EdgeInsets.symmetric(
  242. vertical: 16.0,
  243. ),
  244. child: const Text(
  245. "backup_controller_page_select",
  246. style: TextStyle(fontWeight: FontWeight.bold),
  247. ).tr(),
  248. ),
  249. ),
  250. ),
  251. );
  252. }
  253. _buildCurrentBackupAssetInfoCard() {
  254. return ListTile(
  255. leading: Icon(
  256. Icons.info_outline_rounded,
  257. color: Theme.of(context).primaryColor,
  258. ),
  259. title: Row(
  260. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  261. children: [
  262. const Text(
  263. "backup_controller_page_uploading_file_info",
  264. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
  265. ).tr(),
  266. if (ref.watch(errorBackupListProvider).isNotEmpty)
  267. ActionChip(
  268. avatar: Icon(
  269. Icons.info,
  270. size: 24,
  271. color: Colors.red[400],
  272. ),
  273. elevation: 1,
  274. visualDensity: VisualDensity.compact,
  275. label: Text(
  276. "backup_controller_page_failed",
  277. style: TextStyle(
  278. color: Colors.red[400],
  279. fontWeight: FontWeight.bold,
  280. fontSize: 11,
  281. ),
  282. ).tr(
  283. args: [ref.watch(errorBackupListProvider).length.toString()],
  284. ),
  285. backgroundColor: Colors.white,
  286. onPressed: () {
  287. AutoRouter.of(context).push(const FailedBackupStatusRoute());
  288. },
  289. ),
  290. ],
  291. ),
  292. subtitle: Column(
  293. children: [
  294. Padding(
  295. padding: const EdgeInsets.only(top: 8.0),
  296. child: LinearPercentIndicator(
  297. padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
  298. barRadius: const Radius.circular(2),
  299. lineHeight: 10.0,
  300. trailing: Text(
  301. " ${backupState.progressInPercentage.toStringAsFixed(0)}%",
  302. style: const TextStyle(fontSize: 12),
  303. ),
  304. percent: backupState.progressInPercentage / 100.0,
  305. backgroundColor: Colors.grey,
  306. progressColor: Theme.of(context).primaryColor,
  307. ),
  308. ),
  309. Padding(
  310. padding: const EdgeInsets.only(top: 8.0),
  311. child: Table(
  312. border: TableBorder.all(
  313. color: Colors.black12,
  314. width: 1,
  315. ),
  316. children: [
  317. TableRow(
  318. decoration: BoxDecoration(
  319. color: Colors.grey[100],
  320. ),
  321. children: [
  322. TableCell(
  323. verticalAlignment: TableCellVerticalAlignment.middle,
  324. child: Padding(
  325. padding: const EdgeInsets.all(6.0),
  326. child: const Text(
  327. 'backup_controller_page_filename',
  328. style: TextStyle(
  329. fontWeight: FontWeight.bold,
  330. fontSize: 10.0,
  331. ),
  332. ).tr(
  333. args: [
  334. backupState.currentUploadAsset.fileName,
  335. backupState.currentUploadAsset.fileType
  336. .toLowerCase()
  337. ],
  338. ),
  339. ),
  340. ),
  341. ],
  342. ),
  343. TableRow(
  344. decoration: BoxDecoration(
  345. color: Colors.grey[200],
  346. ),
  347. children: [
  348. TableCell(
  349. verticalAlignment: TableCellVerticalAlignment.middle,
  350. child: Padding(
  351. padding: const EdgeInsets.all(6.0),
  352. child: const Text(
  353. "backup_controller_page_created",
  354. style: TextStyle(
  355. fontWeight: FontWeight.bold,
  356. fontSize: 10.0,
  357. ),
  358. ).tr(
  359. args: [
  360. DateFormat.yMMMMd('en_US').format(
  361. DateTime.parse(
  362. backupState.currentUploadAsset.createdAt
  363. .toString(),
  364. ),
  365. )
  366. ],
  367. ),
  368. ),
  369. ),
  370. ],
  371. ),
  372. TableRow(
  373. decoration: BoxDecoration(
  374. color: Colors.grey[100],
  375. ),
  376. children: [
  377. TableCell(
  378. child: Padding(
  379. padding: const EdgeInsets.all(6.0),
  380. child: const Text(
  381. "backup_controller_page_id",
  382. style: TextStyle(
  383. fontWeight: FontWeight.bold,
  384. fontSize: 10.0,
  385. ),
  386. ).tr(args: [backupState.currentUploadAsset.id]),
  387. ),
  388. ),
  389. ],
  390. ),
  391. ],
  392. ),
  393. ),
  394. ],
  395. ),
  396. );
  397. }
  398. void startBackup() {
  399. ref.watch(errorBackupListProvider.notifier).empty();
  400. ref.watch(backupProvider.notifier).startBackupProcess();
  401. }
  402. return Scaffold(
  403. appBar: AppBar(
  404. elevation: 0,
  405. title: const Text(
  406. "backup_controller_page_backup",
  407. style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
  408. ).tr(),
  409. leading: IconButton(
  410. onPressed: () {
  411. ref.watch(websocketProvider.notifier).listenUploadEvent();
  412. AutoRouter.of(context).pop(true);
  413. },
  414. splashRadius: 24,
  415. icon: const Icon(
  416. Icons.arrow_back_ios_rounded,
  417. ),
  418. ),
  419. ),
  420. body: Padding(
  421. padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 32),
  422. child: ListView(
  423. // crossAxisAlignment: CrossAxisAlignment.start,
  424. children: [
  425. Padding(
  426. padding: const EdgeInsets.all(8.0),
  427. child: const Text(
  428. "backup_controller_page_info",
  429. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
  430. ).tr(),
  431. ),
  432. _buildFolderSelectionTile(),
  433. BackupInfoCard(
  434. title: "backup_controller_page_total".tr(),
  435. subtitle: "backup_controller_page_total_sub".tr(),
  436. info: "${backupState.allUniqueAssets.length}",
  437. ),
  438. BackupInfoCard(
  439. title: "backup_controller_page_backup".tr(),
  440. subtitle: "backup_controller_page_backup_sub".tr(),
  441. info: "${backupState.selectedAlbumsBackupAssetsIds.length}",
  442. ),
  443. BackupInfoCard(
  444. title: "backup_controller_page_remainder".tr(),
  445. subtitle: "backup_controller_page_remainder_sub".tr(),
  446. info:
  447. "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
  448. ),
  449. const Divider(),
  450. _buildBackupController(),
  451. const Divider(),
  452. _buildStorageInformation(),
  453. const Divider(),
  454. _buildCurrentBackupAssetInfoCard(),
  455. Padding(
  456. padding: const EdgeInsets.only(
  457. top: 24,
  458. ),
  459. child: Container(
  460. child:
  461. backupState.backupProgress == BackUpProgressEnum.inProgress
  462. ? ElevatedButton(
  463. style: ElevatedButton.styleFrom(
  464. primary: Colors.red[300],
  465. onPrimary: Colors.grey[50],
  466. padding: const EdgeInsets.all(14),
  467. ),
  468. onPressed: () {
  469. ref.read(backupProvider.notifier).cancelBackup();
  470. },
  471. child: const Text(
  472. "backup_controller_page_cancel",
  473. style: TextStyle(
  474. fontSize: 14,
  475. fontWeight: FontWeight.bold,
  476. ),
  477. ).tr(),
  478. )
  479. : ElevatedButton(
  480. style: ElevatedButton.styleFrom(
  481. primary: Theme.of(context).primaryColor,
  482. onPrimary: Colors.grey[50],
  483. padding: const EdgeInsets.all(14),
  484. ),
  485. onPressed: shouldBackup ? startBackup : null,
  486. child: const Text(
  487. "backup_controller_page_start_backup",
  488. style: TextStyle(
  489. fontSize: 14,
  490. fontWeight: FontWeight.bold,
  491. ),
  492. ).tr(),
  493. ),
  494. ),
  495. )
  496. ],
  497. ),
  498. ),
  499. );
  500. }
  501. }