backup_section_widget.dart 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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. final status = await SyncService.instance.getBackupStatus();
  104. await dialog.hide();
  105. if (status.localIDs.isEmpty) {
  106. showErrorDialog(context, "✨ all clear",
  107. "you've no files on this device that can be deleted");
  108. } else {
  109. bool result = await routeToPage(context, FreeSpacePage(status));
  110. if (result == true) {
  111. _showSpaceFreedDialog(status);
  112. }
  113. }
  114. },
  115. child: SettingsTextItem(
  116. text: "free up space",
  117. icon: Icons.navigate_next,
  118. ),
  119. ),
  120. Platform.isIOS
  121. ? Padding(padding: EdgeInsets.all(4))
  122. : Padding(padding: EdgeInsets.all(2)),
  123. Divider(height: 4),
  124. Platform.isIOS
  125. ? Padding(padding: EdgeInsets.all(2))
  126. : Padding(padding: EdgeInsets.all(2)),
  127. GestureDetector(
  128. behavior: HitTestBehavior.translucent,
  129. onTap: () async {
  130. final dialog = createProgressDialog(context, "calculating...");
  131. await dialog.show();
  132. List<DuplicateFiles> duplicates;
  133. try {
  134. duplicates =
  135. await DeduplicationService.instance.getDuplicateFiles();
  136. } catch (e) {
  137. await dialog.hide();
  138. showGenericErrorDialog(context);
  139. return;
  140. }
  141. await dialog.hide();
  142. if (duplicates.isEmpty) {
  143. showErrorDialog(context, "✨ no duplicates",
  144. "you've no duplicate files that can be cleared");
  145. } else {
  146. DeduplicationResult result =
  147. await routeToPage(context, DeduplicatePage(duplicates));
  148. if (result != null) {
  149. _showDuplicateFilesDeletedDialog(result);
  150. }
  151. }
  152. },
  153. child: SettingsTextItem(
  154. text: "deduplicate files",
  155. icon: Icons.navigate_next,
  156. ),
  157. ),
  158. ],
  159. );
  160. }
  161. void _showSpaceFreedDialog(BackupStatus status) {
  162. AlertDialog alert = AlertDialog(
  163. title: Text("success"),
  164. content: Text(
  165. "you have successfully freed up " + formatBytes(status.size) + "!"),
  166. actions: [
  167. TextButton(
  168. child: Text(
  169. "rate us",
  170. style: TextStyle(
  171. color: Theme.of(context).buttonColor,
  172. ),
  173. ),
  174. onPressed: () {
  175. Navigator.of(context, rootNavigator: true).pop('dialog');
  176. // TODO: Replace with https://pub.dev/packages/in_app_review
  177. if (Platform.isAndroid) {
  178. launch(
  179. "https://play.google.com/store/apps/details?id=io.ente.photos");
  180. } else {
  181. launch("https://apps.apple.com/in/app/ente-photos/id1542026904");
  182. }
  183. },
  184. ),
  185. TextButton(
  186. child: Text(
  187. "ok",
  188. style: TextStyle(
  189. color: Colors.white,
  190. ),
  191. ),
  192. onPressed: () {
  193. if (Platform.isIOS) {
  194. showToast(
  195. "also empty \"Recently Deleted\" from \"Settings\" -> \"Storage\" to claim the freed space");
  196. }
  197. Navigator.of(context, rootNavigator: true).pop('dialog');
  198. },
  199. ),
  200. ],
  201. );
  202. showConfettiDialog(
  203. context: context,
  204. builder: (BuildContext context) {
  205. return alert;
  206. },
  207. barrierColor: Colors.black87,
  208. confettiAlignment: Alignment.topCenter,
  209. useRootNavigator: true,
  210. );
  211. }
  212. void _showDuplicateFilesDeletedDialog(DeduplicationResult result) {
  213. String countText = result.count.toString() +
  214. " duplicate file" +
  215. (result.count == 1 ? "" : "s");
  216. AlertDialog alert = AlertDialog(
  217. title: Text("✨ success"),
  218. content: Text("you have cleaned up " +
  219. countText +
  220. ", saving " +
  221. formatBytes(result.size) +
  222. "!"),
  223. actions: [
  224. TextButton(
  225. child: Text(
  226. "rate us",
  227. style: TextStyle(
  228. color: Theme.of(context).buttonColor,
  229. ),
  230. ),
  231. onPressed: () {
  232. Navigator.of(context, rootNavigator: true).pop('dialog');
  233. // TODO: Replace with https://pub.dev/packages/in_app_review
  234. if (Platform.isAndroid) {
  235. launch(
  236. "https://play.google.com/store/apps/details?id=io.ente.photos");
  237. } else {
  238. launch("https://apps.apple.com/in/app/ente-photos/id1542026904");
  239. }
  240. },
  241. ),
  242. TextButton(
  243. child: Text(
  244. "ok",
  245. style: TextStyle(
  246. color: Colors.white,
  247. ),
  248. ),
  249. onPressed: () {
  250. showToast("also empty your \"Trash\" to claim the freed up space");
  251. Navigator.of(context, rootNavigator: true).pop('dialog');
  252. },
  253. ),
  254. ],
  255. );
  256. showConfettiDialog(
  257. context: context,
  258. builder: (BuildContext context) {
  259. return alert;
  260. },
  261. barrierColor: Colors.black87,
  262. confettiAlignment: Alignment.topCenter,
  263. useRootNavigator: true,
  264. );
  265. }
  266. }