backup_controller_page.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. import 'package:auto_route/auto_route.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_hooks/flutter_hooks.dart';
  4. import 'package:hooks_riverpod/hooks_riverpod.dart';
  5. import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
  6. import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
  7. import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
  8. import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
  9. import 'package:immich_mobile/routing/router.dart';
  10. import 'package:immich_mobile/shared/providers/websocket.provider.dart';
  11. import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';
  12. import 'package:percent_indicator/linear_percent_indicator.dart';
  13. class BackupControllerPage extends HookConsumerWidget {
  14. const BackupControllerPage({Key? key}) : super(key: key);
  15. @override
  16. Widget build(BuildContext context, WidgetRef ref) {
  17. BackUpState backupState = ref.watch(backupProvider);
  18. AuthenticationState authenticationState = ref.watch(authenticationProvider);
  19. bool shouldBackup = backupState.allUniqueAssets.length -
  20. backupState.selectedAlbumsBackupAssetsIds.length ==
  21. 0
  22. ? false
  23. : true;
  24. useEffect(() {
  25. if (backupState.backupProgress != BackUpProgressEnum.inProgress) {
  26. ref.read(backupProvider.notifier).getBackupInfo();
  27. }
  28. ref
  29. .watch(websocketProvider.notifier)
  30. .stopListenToEvent('on_upload_success');
  31. return null;
  32. }, []);
  33. Widget _buildStorageInformation() {
  34. return ListTile(
  35. leading: Icon(
  36. Icons.storage_rounded,
  37. color: Theme.of(context).primaryColor,
  38. ),
  39. title: const Text(
  40. "Server Storage",
  41. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
  42. ),
  43. subtitle: Padding(
  44. padding: const EdgeInsets.only(top: 8.0),
  45. child: Column(
  46. crossAxisAlignment: CrossAxisAlignment.start,
  47. children: [
  48. Padding(
  49. padding: const EdgeInsets.only(top: 8.0),
  50. child: LinearPercentIndicator(
  51. padding:
  52. const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
  53. barRadius: const Radius.circular(2),
  54. lineHeight: 6.0,
  55. percent: backupState.serverInfo.diskUsagePercentage / 100.0,
  56. backgroundColor: Colors.grey,
  57. progressColor: Theme.of(context).primaryColor,
  58. ),
  59. ),
  60. Padding(
  61. padding: const EdgeInsets.only(top: 12.0),
  62. child: Text(
  63. '${backupState.serverInfo.diskUse} of ${backupState.serverInfo.diskSize} used'),
  64. ),
  65. ],
  66. ),
  67. ),
  68. );
  69. }
  70. ListTile _buildBackupController() {
  71. var backUpOption =
  72. authenticationState.deviceInfo.isAutoBackup ? "on" : "off";
  73. var isAutoBackup = authenticationState.deviceInfo.isAutoBackup;
  74. var backupBtnText =
  75. authenticationState.deviceInfo.isAutoBackup ? "off" : "on";
  76. return ListTile(
  77. isThreeLine: true,
  78. leading: isAutoBackup
  79. ? Icon(
  80. Icons.cloud_done_rounded,
  81. color: Theme.of(context).primaryColor,
  82. )
  83. : const Icon(Icons.cloud_off_rounded),
  84. title: Text(
  85. "Back up is $backUpOption",
  86. style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
  87. ),
  88. subtitle: Padding(
  89. padding: const EdgeInsets.symmetric(vertical: 8.0),
  90. child: Column(
  91. crossAxisAlignment: CrossAxisAlignment.start,
  92. children: [
  93. !isAutoBackup
  94. ? const Text(
  95. "Turn on backup to automatically upload new assets to the server.",
  96. style: TextStyle(fontSize: 14),
  97. )
  98. : Container(),
  99. Padding(
  100. padding: const EdgeInsets.only(top: 8.0),
  101. child: OutlinedButton(
  102. style: OutlinedButton.styleFrom(
  103. side: const BorderSide(
  104. width: 1,
  105. color: Color.fromARGB(255, 220, 220, 220),
  106. ),
  107. ),
  108. onPressed: () {
  109. isAutoBackup
  110. ? ref
  111. .watch(authenticationProvider.notifier)
  112. .setAutoBackup(false)
  113. : ref
  114. .watch(authenticationProvider.notifier)
  115. .setAutoBackup(true);
  116. },
  117. child: Text("Turn $backupBtnText Backup",
  118. style: const TextStyle(fontWeight: FontWeight.bold)),
  119. ),
  120. )
  121. ],
  122. ),
  123. ),
  124. );
  125. }
  126. Widget _buildSelectedAlbumName() {
  127. var text = "Selected: ";
  128. var albums = ref.watch(backupProvider).selectedBackupAlbums;
  129. if (albums.isNotEmpty) {
  130. for (var album in albums) {
  131. if (album.name == "Recent" || album.name == "Recents") {
  132. text += "${album.name} (All), ";
  133. } else {
  134. text += "${album.name}, ";
  135. }
  136. }
  137. return Padding(
  138. padding: const EdgeInsets.only(top: 8.0),
  139. child: Text(
  140. text.trim().substring(0, text.length - 2),
  141. style: TextStyle(
  142. color: Theme.of(context).primaryColor,
  143. fontSize: 12,
  144. fontWeight: FontWeight.bold),
  145. ),
  146. );
  147. } else {
  148. return Padding(
  149. padding: const EdgeInsets.only(top: 8.0),
  150. child: Text(
  151. "None selected",
  152. style: TextStyle(
  153. color: Theme.of(context).primaryColor,
  154. fontSize: 12,
  155. fontWeight: FontWeight.bold),
  156. ),
  157. );
  158. }
  159. }
  160. Widget _buildExcludedAlbumName() {
  161. var text = "Excluded: ";
  162. var albums = ref.watch(backupProvider).excludedBackupAlbums;
  163. if (albums.isNotEmpty) {
  164. for (var album in albums) {
  165. text += "${album.name}, ";
  166. }
  167. return Padding(
  168. padding: const EdgeInsets.only(top: 8.0),
  169. child: Text(
  170. text.trim().substring(0, text.length - 2),
  171. style: TextStyle(
  172. color: Colors.red[300],
  173. fontSize: 12,
  174. fontWeight: FontWeight.bold),
  175. ),
  176. );
  177. } else {
  178. return Container();
  179. }
  180. }
  181. _buildFolderSelectionTile() {
  182. return Card(
  183. shape: RoundedRectangleBorder(
  184. borderRadius: BorderRadius.circular(5), // if you need this
  185. side: const BorderSide(
  186. color: Colors.black12,
  187. width: 1,
  188. ),
  189. ),
  190. elevation: 0,
  191. borderOnForeground: false,
  192. child: ListTile(
  193. minVerticalPadding: 15,
  194. title: const Text("Backup Albums",
  195. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
  196. subtitle: Padding(
  197. padding: const EdgeInsets.only(top: 8.0),
  198. child: Column(
  199. crossAxisAlignment: CrossAxisAlignment.start,
  200. children: [
  201. const Text(
  202. "Albums to be backup",
  203. style: TextStyle(color: Color(0xFF808080), fontSize: 12),
  204. ),
  205. _buildSelectedAlbumName(),
  206. _buildExcludedAlbumName()
  207. ],
  208. ),
  209. ),
  210. trailing: OutlinedButton(
  211. style: OutlinedButton.styleFrom(
  212. enableFeedback: true,
  213. side: const BorderSide(
  214. width: 1,
  215. color: Color.fromARGB(255, 220, 220, 220),
  216. ),
  217. ),
  218. onPressed: () {
  219. AutoRouter.of(context).push(const BackupAlbumSelectionRoute());
  220. },
  221. child: const Padding(
  222. padding: EdgeInsets.symmetric(
  223. vertical: 16.0,
  224. ),
  225. child: Text(
  226. "Select",
  227. style: TextStyle(fontWeight: FontWeight.bold),
  228. ),
  229. ),
  230. ),
  231. ),
  232. );
  233. }
  234. return Scaffold(
  235. appBar: AppBar(
  236. elevation: 0,
  237. title: const Text(
  238. "Backup",
  239. style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
  240. ),
  241. leading: IconButton(
  242. onPressed: () {
  243. ref.watch(websocketProvider.notifier).listenUploadEvent();
  244. AutoRouter.of(context).pop(true);
  245. },
  246. splashRadius: 24,
  247. icon: const Icon(
  248. Icons.arrow_back_ios_rounded,
  249. )),
  250. ),
  251. body: Padding(
  252. padding: const EdgeInsets.all(16.0),
  253. child: ListView(
  254. // crossAxisAlignment: CrossAxisAlignment.start,
  255. children: [
  256. const Padding(
  257. padding: EdgeInsets.all(8.0),
  258. child: Text(
  259. "Backup Information",
  260. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
  261. ),
  262. ),
  263. _buildFolderSelectionTile(),
  264. BackupInfoCard(
  265. title: "Total",
  266. subtitle: "All unique photos and videos from selected albums",
  267. info: "${backupState.allUniqueAssets.length}",
  268. ),
  269. BackupInfoCard(
  270. title: "Backup",
  271. subtitle:
  272. "Photos and videos from selected albums that are backup",
  273. info: "${backupState.selectedAlbumsBackupAssetsIds.length}",
  274. ),
  275. BackupInfoCard(
  276. title: "Remainder",
  277. subtitle:
  278. "Photos and videos that has not been backing up from selected albums",
  279. info:
  280. "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
  281. ),
  282. const Divider(),
  283. _buildBackupController(),
  284. const Divider(),
  285. _buildStorageInformation(),
  286. const Divider(),
  287. Padding(
  288. padding: const EdgeInsets.all(8.0),
  289. child: Text(
  290. "Asset that were being backup: ${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length} [${backupState.progressInPercentage.toStringAsFixed(0)}%]"),
  291. ),
  292. Padding(
  293. padding: const EdgeInsets.only(left: 8.0),
  294. child: Row(children: [
  295. const Text("Backup Progress:"),
  296. const Padding(padding: EdgeInsets.symmetric(horizontal: 2)),
  297. backupState.backupProgress == BackUpProgressEnum.inProgress
  298. ? const CircularProgressIndicator.adaptive()
  299. : const Text("Done"),
  300. ]),
  301. ),
  302. Padding(
  303. padding: const EdgeInsets.all(8.0),
  304. child: Container(
  305. child:
  306. backupState.backupProgress == BackUpProgressEnum.inProgress
  307. ? ElevatedButton(
  308. style: ElevatedButton.styleFrom(
  309. primary: Colors.red[300],
  310. onPrimary: Colors.grey[50],
  311. ),
  312. onPressed: () {
  313. ref.read(backupProvider.notifier).cancelBackup();
  314. },
  315. child: const Text("Cancel"),
  316. )
  317. : ElevatedButton(
  318. style: ElevatedButton.styleFrom(
  319. primary: Theme.of(context).primaryColor,
  320. onPrimary: Colors.grey[50],
  321. ),
  322. onPressed: shouldBackup
  323. ? () {
  324. ref
  325. .read(backupProvider.notifier)
  326. .startBackupProcess();
  327. }
  328. : null,
  329. child: const Text("Start Backup"),
  330. ),
  331. ),
  332. )
  333. ],
  334. ),
  335. ),
  336. );
  337. }
  338. }