backup_controller_page.dart 18 KB

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