backup_section_widget.dart 7.5 KB

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