backup_section_widget.dart 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import "dart:async";
  2. import 'dart:io';
  3. import 'package:flutter/material.dart';
  4. import "package:photos/generated/l10n.dart";
  5. import 'package:photos/models/backup_status.dart';
  6. import 'package:photos/models/duplicate_files.dart';
  7. import 'package:photos/services/deduplication_service.dart';
  8. import 'package:photos/services/sync_service.dart';
  9. import 'package:photos/services/update_service.dart';
  10. import 'package:photos/theme/ente_theme.dart';
  11. import "package:photos/ui/components/buttons/button_widget.dart";
  12. import "package:photos/ui/components/captioned_text_widget.dart";
  13. import "package:photos/ui/components/dialog_widget.dart";
  14. import 'package:photos/ui/components/expandable_menu_item_widget.dart';
  15. import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
  16. import "package:photos/ui/components/models/button_type.dart";
  17. import 'package:photos/ui/settings/backup/backup_folder_selection_page.dart';
  18. import 'package:photos/ui/settings/backup/backup_settings_screen.dart';
  19. import 'package:photos/ui/settings/common_settings.dart';
  20. import 'package:photos/ui/tools/deduplicate_page.dart';
  21. import "package:photos/ui/tools/free_space_page.dart";
  22. import 'package:photos/utils/data_util.dart';
  23. import 'package:photos/utils/dialog_util.dart';
  24. import "package:photos/utils/local_settings.dart";
  25. import 'package:photos/utils/navigation_util.dart';
  26. import 'package:photos/utils/toast_util.dart';
  27. class BackupSectionWidget extends StatefulWidget {
  28. const BackupSectionWidget({Key? key}) : super(key: key);
  29. @override
  30. BackupSectionWidgetState createState() => BackupSectionWidgetState();
  31. }
  32. class BackupSectionWidgetState extends State<BackupSectionWidget> {
  33. @override
  34. Widget build(BuildContext context) {
  35. return ExpandableMenuItemWidget(
  36. title: S.of(context).backup,
  37. selectionOptionsWidget: _getSectionOptions(context),
  38. leadingIcon: Icons.backup_outlined,
  39. );
  40. }
  41. Widget _getSectionOptions(BuildContext context) {
  42. final List<Widget> sectionOptions = [
  43. sectionOptionSpacing,
  44. MenuItemWidget(
  45. captionedTextWidget: CaptionedTextWidget(
  46. title: S.of(context).backedUpFolders,
  47. ),
  48. pressedColor: getEnteColorScheme(context).fillFaint,
  49. trailingIcon: Icons.chevron_right_outlined,
  50. trailingIconIsMuted: true,
  51. onTap: () async {
  52. await routeToPage(
  53. context,
  54. BackupFolderSelectionPage(
  55. buttonText: S.of(context).backup,
  56. ),
  57. );
  58. },
  59. ),
  60. sectionOptionSpacing,
  61. MenuItemWidget(
  62. captionedTextWidget: CaptionedTextWidget(
  63. title: S.of(context).backupSettings,
  64. ),
  65. pressedColor: getEnteColorScheme(context).fillFaint,
  66. trailingIcon: Icons.chevron_right_outlined,
  67. trailingIconIsMuted: true,
  68. onTap: () async {
  69. await routeToPage(
  70. context,
  71. const BackupSettingsScreen(),
  72. );
  73. },
  74. ),
  75. sectionOptionSpacing,
  76. ];
  77. sectionOptions.addAll(
  78. [
  79. MenuItemWidget(
  80. captionedTextWidget: CaptionedTextWidget(
  81. title: S.of(context).freeUpDeviceSpace,
  82. ),
  83. pressedColor: getEnteColorScheme(context).fillFaint,
  84. trailingIcon: Icons.chevron_right_outlined,
  85. trailingIconIsMuted: true,
  86. showOnlyLoadingState: true,
  87. onTap: () async {
  88. BackupStatus status;
  89. try {
  90. status = await SyncService.instance.getBackupStatus();
  91. } catch (e) {
  92. await showGenericErrorDialog(context: context, error: e);
  93. return;
  94. }
  95. if (status.localIDs.isEmpty) {
  96. showErrorDialog(
  97. context,
  98. S.of(context).allClear,
  99. S.of(context).noDeviceThatCanBeDeleted,
  100. );
  101. } else {
  102. final bool? result =
  103. await routeToPage(context, FreeSpacePage(status));
  104. if (result == true) {
  105. _showSpaceFreedDialog(status);
  106. }
  107. }
  108. },
  109. ),
  110. sectionOptionSpacing,
  111. MenuItemWidget(
  112. captionedTextWidget: CaptionedTextWidget(
  113. title: S.of(context).removeDuplicates,
  114. ),
  115. pressedColor: getEnteColorScheme(context).fillFaint,
  116. trailingIcon: Icons.chevron_right_outlined,
  117. trailingIconIsMuted: true,
  118. showOnlyLoadingState: true,
  119. onTap: () async {
  120. List<DuplicateFiles> duplicates;
  121. try {
  122. duplicates =
  123. await DeduplicationService.instance.getDuplicateFiles();
  124. } catch (e) {
  125. await showGenericErrorDialog(context: context, error: e);
  126. return;
  127. }
  128. if (duplicates.isEmpty) {
  129. unawaited(
  130. showErrorDialog(
  131. context,
  132. S.of(context).noDuplicates,
  133. S.of(context).youveNoDuplicateFilesThatCanBeCleared,
  134. ),
  135. );
  136. } else {
  137. final DeduplicationResult? result =
  138. await routeToPage(context, DeduplicatePage(duplicates));
  139. if (result != null) {
  140. _showDuplicateFilesDeletedDialog(result);
  141. }
  142. }
  143. },
  144. ),
  145. sectionOptionSpacing,
  146. ],
  147. );
  148. return Column(
  149. children: sectionOptions,
  150. );
  151. }
  152. void _showSpaceFreedDialog(BackupStatus status) {
  153. if (LocalSettings.instance.shouldPromptToRateUs()) {
  154. LocalSettings.instance.setRateUsShownCount(
  155. LocalSettings.instance.getRateUsShownCount() + 1,
  156. );
  157. showChoiceDialog(
  158. context,
  159. title: S.of(context).success,
  160. body:
  161. S.of(context).youHaveSuccessfullyFreedUp(formatBytes(status.size)),
  162. firstButtonLabel: S.of(context).rateUs,
  163. firstButtonOnTap: () async {
  164. await UpdateService.instance.launchReviewUrl();
  165. },
  166. firstButtonType: ButtonType.primary,
  167. secondButtonLabel: S.of(context).ok,
  168. secondButtonOnTap: () async {
  169. if (Platform.isIOS) {
  170. unawaited(
  171. showToast(
  172. context,
  173. S.of(context).remindToEmptyDeviceTrash,
  174. ),
  175. );
  176. }
  177. },
  178. );
  179. } else {
  180. showDialogWidget(
  181. context: context,
  182. title: S.of(context).success,
  183. body:
  184. S.of(context).youHaveSuccessfullyFreedUp(formatBytes(status.size)),
  185. icon: Icons.download_done_rounded,
  186. isDismissible: true,
  187. buttons: [
  188. ButtonWidget(
  189. buttonType: ButtonType.neutral,
  190. labelText: S.of(context).ok,
  191. isInAlert: true,
  192. onTap: () async {
  193. if (Platform.isIOS) {
  194. unawaited(
  195. showToast(
  196. context,
  197. S.of(context).remindToEmptyDeviceTrash,
  198. ),
  199. );
  200. }
  201. },
  202. ),
  203. ],
  204. );
  205. }
  206. }
  207. void _showDuplicateFilesDeletedDialog(DeduplicationResult result) {
  208. showChoiceDialog(
  209. context,
  210. title: S.of(context).sparkleSuccess,
  211. body: S.of(context).duplicateFileCountWithStorageSaved(
  212. result.count,
  213. formatBytes(result.size),
  214. ),
  215. firstButtonLabel: S.of(context).rateUs,
  216. firstButtonOnTap: () async {
  217. await UpdateService.instance.launchReviewUrl();
  218. },
  219. firstButtonType: ButtonType.primary,
  220. secondButtonLabel: S.of(context).ok,
  221. secondButtonOnTap: () async {
  222. unawaited(
  223. showShortToast(
  224. context,
  225. S.of(context).remindToEmptyEnteTrash,
  226. ),
  227. );
  228. },
  229. );
  230. }
  231. }