backup_controller_page.dart 12 KB

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