backup_section_widget.dart 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import 'dart:io';
  2. import 'package:flutter/material.dart';
  3. import 'package:photos/core/configuration.dart';
  4. import 'package:photos/models/backup_status.dart';
  5. import 'package:photos/models/duplicate_files.dart';
  6. import 'package:photos/services/deduplication_service.dart';
  7. import 'package:photos/services/sync_service.dart';
  8. import 'package:photos/ui/backup_folder_selection_page.dart';
  9. import 'package:photos/ui/deduplicate_page.dart';
  10. import 'package:photos/ui/free_space_page.dart';
  11. import 'package:photos/ui/settings/settings_section_title.dart';
  12. import 'package:photos/ui/settings/settings_text_item.dart';
  13. import 'package:photos/utils/data_util.dart';
  14. import 'package:photos/utils/dialog_util.dart';
  15. import 'package:photos/utils/navigation_util.dart';
  16. import 'package:photos/utils/toast_util.dart';
  17. import 'package:url_launcher/url_launcher.dart';
  18. class BackupSectionWidget extends StatefulWidget {
  19. BackupSectionWidget({Key key}) : super(key: key);
  20. @override
  21. BackupSectionWidgetState createState() => BackupSectionWidgetState();
  22. }
  23. class BackupSectionWidgetState extends State<BackupSectionWidget> {
  24. @override
  25. Widget build(BuildContext context) {
  26. return Column(
  27. children: [
  28. SettingsSectionTitle("backup"),
  29. Padding(
  30. padding: EdgeInsets.all(4),
  31. ),
  32. GestureDetector(
  33. behavior: HitTestBehavior.translucent,
  34. onTap: () async {
  35. routeToPage(
  36. context,
  37. BackupFolderSelectionPage(
  38. buttonText: "backup",
  39. ),
  40. );
  41. },
  42. child: SettingsTextItem(
  43. text: "backed up folders", icon: Icons.navigate_next),
  44. ),
  45. Platform.isIOS
  46. ? Padding(padding: EdgeInsets.all(2))
  47. : Padding(padding: EdgeInsets.all(0)),
  48. Divider(height: 4),
  49. Platform.isIOS
  50. ? Padding(padding: EdgeInsets.all(2))
  51. : Padding(padding: EdgeInsets.all(4)),
  52. SizedBox(
  53. height: 36,
  54. child: Row(
  55. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  56. children: [
  57. Text("backup over mobile data"),
  58. Switch(
  59. value: Configuration.instance.shouldBackupOverMobileData(),
  60. onChanged: (value) async {
  61. Configuration.instance.setBackupOverMobileData(value);
  62. setState(() {});
  63. },
  64. ),
  65. ],
  66. ),
  67. ),
  68. Platform.isIOS
  69. ? Padding(padding: EdgeInsets.all(2))
  70. : Padding(padding: EdgeInsets.all(4)),
  71. Divider(height: 4),
  72. Platform.isIOS
  73. ? Padding(padding: EdgeInsets.all(2))
  74. : Padding(padding: EdgeInsets.all(4)),
  75. SizedBox(
  76. height: 36,
  77. child: Row(
  78. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  79. children: [
  80. Text("backup videos"),
  81. Switch(
  82. value: Configuration.instance.shouldBackupVideos(),
  83. onChanged: (value) async {
  84. Configuration.instance.setShouldBackupVideos(value);
  85. setState(() {});
  86. },
  87. ),
  88. ],
  89. ),
  90. ),
  91. Platform.isIOS
  92. ? Padding(padding: EdgeInsets.all(4))
  93. : Padding(padding: EdgeInsets.all(2)),
  94. Divider(height: 4),
  95. Platform.isIOS
  96. ? Padding(padding: EdgeInsets.all(2))
  97. : Padding(padding: EdgeInsets.all(2)),
  98. GestureDetector(
  99. behavior: HitTestBehavior.translucent,
  100. onTap: () async {
  101. final dialog = createProgressDialog(context, "calculating...");
  102. await dialog.show();
  103. BackupStatus status;
  104. try {
  105. status = await SyncService.instance.getBackupStatus();
  106. } catch (e, s) {
  107. await dialog.hide();
  108. showGenericErrorDialog(context);
  109. return;
  110. }
  111. await dialog.hide();
  112. if (status.localIDs.isEmpty) {
  113. showErrorDialog(context, "✨ all clear",
  114. "you've no files on this device that can be deleted");
  115. } else {
  116. bool result = await routeToPage(context, FreeSpacePage(status));
  117. if (result == true) {
  118. _showSpaceFreedDialog(status);
  119. }
  120. }
  121. },
  122. child: SettingsTextItem(
  123. text: "free up space",
  124. icon: Icons.navigate_next,
  125. ),
  126. ),
  127. Platform.isIOS
  128. ? Padding(padding: EdgeInsets.all(4))
  129. : Padding(padding: EdgeInsets.all(2)),
  130. Divider(height: 4),
  131. Platform.isIOS
  132. ? Padding(padding: EdgeInsets.all(2))
  133. : Padding(padding: EdgeInsets.all(2)),
  134. GestureDetector(
  135. behavior: HitTestBehavior.translucent,
  136. onTap: () async {
  137. final dialog = createProgressDialog(context, "calculating...");
  138. await dialog.show();
  139. List<DuplicateFiles> duplicates;
  140. try {
  141. duplicates =
  142. await DeduplicationService.instance.getDuplicateFiles();
  143. } catch (e) {
  144. await dialog.hide();
  145. showGenericErrorDialog(context);
  146. return;
  147. }
  148. await dialog.hide();
  149. if (duplicates.isEmpty) {
  150. showErrorDialog(context, "✨ no duplicates",
  151. "you've no duplicate files that can be cleared");
  152. } else {
  153. DeduplicationResult result =
  154. await routeToPage(context, DeduplicatePage(duplicates));
  155. if (result != null) {
  156. _showDuplicateFilesDeletedDialog(result);
  157. }
  158. }
  159. },
  160. child: SettingsTextItem(
  161. text: "deduplicate files",
  162. icon: Icons.navigate_next,
  163. ),
  164. ),
  165. ],
  166. );
  167. }
  168. void _showSpaceFreedDialog(BackupStatus status) {
  169. AlertDialog alert = AlertDialog(
  170. title: Text("success"),
  171. content: Text(
  172. "you have successfully freed up " + formatBytes(status.size) + "!"),
  173. actions: [
  174. TextButton(
  175. child: Text(
  176. "rate us",
  177. style: TextStyle(
  178. color: Theme.of(context).buttonColor,
  179. ),
  180. ),
  181. onPressed: () {
  182. Navigator.of(context, rootNavigator: true).pop('dialog');
  183. // TODO: Replace with https://pub.dev/packages/in_app_review
  184. if (Platform.isAndroid) {
  185. launch(
  186. "https://play.google.com/store/apps/details?id=io.ente.photos");
  187. } else {
  188. launch("https://apps.apple.com/in/app/ente-photos/id1542026904");
  189. }
  190. },
  191. ),
  192. TextButton(
  193. child: Text(
  194. "ok",
  195. style: TextStyle(
  196. color: Colors.white,
  197. ),
  198. ),
  199. onPressed: () {
  200. if (Platform.isIOS) {
  201. showToast(
  202. "also empty \"Recently Deleted\" from \"Settings\" -> \"Storage\" to claim the freed space");
  203. }
  204. Navigator.of(context, rootNavigator: true).pop('dialog');
  205. },
  206. ),
  207. ],
  208. );
  209. showConfettiDialog(
  210. context: context,
  211. builder: (BuildContext context) {
  212. return alert;
  213. },
  214. barrierColor: Colors.black87,
  215. confettiAlignment: Alignment.topCenter,
  216. useRootNavigator: true,
  217. );
  218. }
  219. void _showDuplicateFilesDeletedDialog(DeduplicationResult result) {
  220. String countText = result.count.toString() +
  221. " duplicate file" +
  222. (result.count == 1 ? "" : "s");
  223. AlertDialog alert = AlertDialog(
  224. title: Text("✨ success"),
  225. content: Text("you have cleaned up " +
  226. countText +
  227. ", saving " +
  228. formatBytes(result.size) +
  229. "!"),
  230. actions: [
  231. TextButton(
  232. child: Text(
  233. "rate us",
  234. style: TextStyle(
  235. color: Theme.of(context).buttonColor,
  236. ),
  237. ),
  238. onPressed: () {
  239. Navigator.of(context, rootNavigator: true).pop('dialog');
  240. // TODO: Replace with https://pub.dev/packages/in_app_review
  241. if (Platform.isAndroid) {
  242. launch(
  243. "https://play.google.com/store/apps/details?id=io.ente.photos");
  244. } else {
  245. launch("https://apps.apple.com/in/app/ente-photos/id1542026904");
  246. }
  247. },
  248. ),
  249. TextButton(
  250. child: Text(
  251. "ok",
  252. style: TextStyle(
  253. color: Colors.white,
  254. ),
  255. ),
  256. onPressed: () {
  257. showToast("also empty your \"Trash\" to claim the freed up space");
  258. Navigator.of(context, rootNavigator: true).pop('dialog');
  259. },
  260. ),
  261. ],
  262. );
  263. showConfettiDialog(
  264. context: context,
  265. builder: (BuildContext context) {
  266. return alert;
  267. },
  268. barrierColor: Colors.black87,
  269. confettiAlignment: Alignment.topCenter,
  270. useRootNavigator: true,
  271. );
  272. }
  273. }