Prechádzať zdrojové kódy

Add option to free up space for a device folder

Neeraj Gupta 2 rokov pred
rodič
commit
2732b399d4

+ 26 - 0
lib/db/device_files_db.dart

@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
 import 'package:logging/logging.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:photos/db/files_db.dart';
+import 'package:photos/models/backup_status.dart';
 import 'package:photos/models/device_collection.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file_load_result.dart';
@@ -322,6 +323,31 @@ extension DeviceFiles on FilesDB {
     return FileLoadResult(dedupe, files.length == limit);
   }
 
+  Future<BackedUpFileIDs> getBackedUpForDeviceCollection(
+    String pathID,
+    int ownerID,
+  ) async {
+    final db = await database;
+    const String rawQuery = ''' 
+    SELECT ${FilesDB.columnLocalID}, ${FilesDB.columnUploadedFileID}
+          FROM ${FilesDB.filesTable}
+          WHERE ${FilesDB.columnLocalID} IS NOT NULL AND
+          (${FilesDB.columnOwnerID} IS NULL OR ${FilesDB.columnOwnerID} = ?)
+          AND (${FilesDB.columnUploadedFileID} IS NOT NULL AND ${FilesDB.columnUploadedFileID} IS NOT -1)
+          AND 
+          ${FilesDB.columnLocalID} IN 
+          (SELECT id FROM device_files where path_id = ?)
+          ''';
+    final results = await db.rawQuery(rawQuery, [ownerID, pathID]);
+    final localIDs = <String>{};
+    final uploadedIDs = <int>{};
+    for (final result in results) {
+      localIDs.add(result[FilesDB.columnLocalID]);
+      uploadedIDs.add(result[FilesDB.columnUploadedFileID]);
+    }
+    return BackedUpFileIDs(localIDs.toList(), uploadedIDs.toList());
+  }
+
   Future<List<DeviceCollection>> getDeviceCollections({
     bool includeCoverThumbnail = false,
   }) async {

+ 11 - 2
lib/services/sync_service.dart

@@ -12,6 +12,7 @@ import 'package:photos/core/constants.dart';
 import 'package:photos/core/errors.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/core/network.dart';
+import 'package:photos/db/device_files_db.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/events/permission_granted_event.dart';
 import 'package:photos/events/subscription_purchased_event.dart';
@@ -206,8 +207,16 @@ class SyncService {
     );
   }
 
-  Future<BackupStatus> getBackupStatus() async {
-    final ids = await FilesDB.instance.getBackedUpIDs();
+  Future<BackupStatus> getBackupStatus({String pathID}) async {
+    BackedUpFileIDs ids;
+    if (pathID == null) {
+      ids = await FilesDB.instance.getBackedUpIDs();
+    } else {
+      ids = await FilesDB.instance.getBackedUpForDeviceCollection(
+        pathID,
+        Configuration.instance.getUserID(),
+      );
+    }
     final size = await _getFileSize(ids.uploadedIDs);
     return BackupStatus(ids.localIDs, size);
   }

+ 112 - 0
lib/ui/viewer/gallery/gallery_app_bar_widget.dart

@@ -1,6 +1,7 @@
 // @dart=2.9
 
 import 'dart:async';
+import 'dart:io';
 
 import 'package:collection/collection.dart';
 import 'package:flutter/material.dart';
@@ -9,18 +10,24 @@ import 'package:photos/core/configuration.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/events/subscription_purchased_event.dart';
+import 'package:photos/models/backup_status.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/models/device_collection.dart';
 import 'package:photos/models/gallery_type.dart';
 import 'package:photos/models/magic_metadata.dart';
 import 'package:photos/models/selected_files.dart';
 import 'package:photos/services/collections_service.dart';
+import 'package:photos/services/sync_service.dart';
 import 'package:photos/ui/common/dialogs.dart';
 import 'package:photos/ui/common/rename_dialog.dart';
 import 'package:photos/ui/sharing/share_collection_widget.dart';
+import 'package:photos/ui/tools/free_space_page.dart';
+import 'package:photos/utils/data_util.dart';
 import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/magic_util.dart';
+import 'package:photos/utils/navigation_util.dart';
 import 'package:photos/utils/toast_util.dart';
+import 'package:url_launcher/url_launcher_string.dart';
 
 class GalleryAppBarWidget extends StatefulWidget {
   final GalleryType type;
@@ -154,6 +161,93 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
     }
   }
 
+  // todo: In the new design, clicking on free up space will directly open
+  // the free up space page and show loading indicator while calculating
+  // the space which can be claimed up. This code duplication should be removed
+  // whenever we move to the new design for free up space.
+  Future<dynamic> _deleteDeviceAlbum(BuildContext context) async {
+    final dialog = createProgressDialog(context, "Calculating...");
+    await dialog.show();
+    BackupStatus status;
+    try {
+      status = await SyncService.instance
+          .getBackupStatus(pathID: widget.deviceCollection.id);
+    } catch (e) {
+      await dialog.hide();
+      showGenericErrorDialog(context);
+      return;
+    }
+
+    await dialog.hide();
+    if (status.localIDs.isEmpty) {
+      showErrorDialog(
+        context,
+        "✨ All clear",
+        "You've no files in this folder that can be deleted",
+      );
+    } else {
+      final bool result = await routeToPage(context, FreeSpacePage(status));
+      if (result == true) {
+        _showSpaceFreedDialog(status);
+      }
+    }
+  }
+
+  void _showSpaceFreedDialog(BackupStatus status) {
+    final AlertDialog alert = AlertDialog(
+      title: const Text("Success"),
+      content: Text(
+        "You have successfully freed up " + formatBytes(status.size) + "!",
+      ),
+      actions: [
+        TextButton(
+          child: Text(
+            "Rate us",
+            style: TextStyle(
+              color: Theme.of(context).colorScheme.greenAlternative,
+            ),
+          ),
+          onPressed: () {
+            Navigator.of(context, rootNavigator: true).pop('dialog');
+            // TODO: Replace with https://pub.dev/packages/in_app_review
+            if (Platform.isAndroid) {
+              launchUrlString(
+                "https://play.google.com/store/apps/details?id=io.ente.photos",
+              );
+            } else {
+              launchUrlString(
+                "https://apps.apple.com/in/app/ente-photos/id1542026904",
+              );
+            }
+          },
+        ),
+        TextButton(
+          child: const Text(
+            "Ok",
+          ),
+          onPressed: () {
+            if (Platform.isIOS) {
+              showToast(
+                context,
+                "Also empty \"Recently Deleted\" from \"Settings\" -> \"Storage\" to claim the freed space",
+              );
+            }
+            Navigator.of(context, rootNavigator: true).pop('dialog');
+          },
+        ),
+      ],
+    );
+    showConfettiDialog(
+      context: context,
+      builder: (BuildContext context) {
+        return alert;
+      },
+      barrierColor: Colors.black87,
+      confettiAlignment: Alignment.topCenter,
+      useRootNavigator: true,
+    );
+  }
+
   List<Widget> _getDefaultActions(BuildContext context) {
     final List<Widget> actions = <Widget>[];
     if (Configuration.instance.hasConfiguredAccount() &&
@@ -242,6 +336,22 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
         ),
       );
     }
+    if (widget.type == GalleryType.localFolder) {
+      items.add(
+        PopupMenuItem(
+          value: 5,
+          child: Row(
+            children: const [
+              Icon(Icons.logout),
+              Padding(
+                padding: EdgeInsets.all(8),
+              ),
+              Text("Free up device space"),
+            ],
+          ),
+        ),
+      );
+    }
     if (items.isNotEmpty) {
       actions.add(
         PopupMenuButton(
@@ -263,6 +373,8 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
               await _trashCollection();
             } else if (value == 4) {
               await _leaveAlbum(context);
+            } else if (value == 5) {
+              await _deleteDeviceAlbum(context);
             } else {
               showToast(context, "Something went wrong");
             }

+ 2 - 2
lib/utils/delete_file_util.dart

@@ -338,7 +338,7 @@ Future<bool> deleteLocalFiles(
     final androidInfo = await DeviceInfoPlugin().androidInfo;
     if (androidInfo.version.sdkInt < android11SDKINT) {
       deletedIDs
-          .addAll(await _deleteLocalFilesInBatches(context, localAssetIDs));
+          .addAll(await deleteLocalFilesInBatches(context, localAssetIDs));
     } else {
       deletedIDs
           .addAll(await _deleteLocalFilesInOneShot(context, localAssetIDs));
@@ -384,7 +384,7 @@ Future<List<String>> _deleteLocalFilesInOneShot(
   return deletedIDs;
 }
 
-Future<List<String>> _deleteLocalFilesInBatches(
+Future<List<String>> deleteLocalFilesInBatches(
   BuildContext context,
   List<String> localIDs,
 ) async {