Browse Source

Add screen to render detected duplicates

vishnukvmd 3 years ago
parent
commit
7f20fbb776
2 changed files with 239 additions and 0 deletions
  1. 158 0
      lib/ui/deduplicate_page.dart
  2. 81 0
      lib/ui/settings/backup_section_widget.dart

+ 158 - 0
lib/ui/deduplicate_page.dart

@@ -0,0 +1,158 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:photos/core/constants.dart';
+import 'package:photos/models/duplicate_files.dart';
+import 'package:photos/models/file.dart';
+import 'package:photos/ui/thumbnail_widget.dart';
+import 'package:photos/utils/data_util.dart';
+
+class DeduplicatePage extends StatelessWidget {
+  final List<DuplicateFiles> duplicates;
+
+  DeduplicatePage(this.duplicates, {Key key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Hero(
+          tag: "deduplicate",
+          child: Material(
+            type: MaterialType.transparency,
+            child: Text(
+              "deduplicate files",
+              style: TextStyle(
+                fontSize: 18,
+              ),
+            ),
+          ),
+        ),
+      ),
+      body: _getBody(),
+    );
+  }
+
+  Widget _getBody() {
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.center,
+      crossAxisAlignment: CrossAxisAlignment.center,
+      children: [
+        Expanded(
+          child: ListView.builder(
+            itemBuilder: (context, index) {
+              if (index == 0) {
+                return Padding(
+                  padding: EdgeInsets.fromLTRB(12, 4, 12, 4),
+                  child: Column(
+                    children: [
+                      Text(
+                        "we've clubbed the following files based on their sizes",
+                        style: TextStyle(
+                          color: Colors.white.withOpacity(0.6),
+                          height: 1.2,
+                        ),
+                      ),
+                      Padding(
+                        padding: EdgeInsets.all(4),
+                      ),
+                      Text(
+                        "please review and delete the items you believe are duplicates",
+                        style: TextStyle(
+                          color: Colors.white.withOpacity(0.6),
+                          height: 1.2,
+                        ),
+                      ),
+                    ],
+                  ),
+                );
+              }
+              return Padding(
+                padding: const EdgeInsets.only(top: 8, bottom: 8),
+                child: _getGridView(duplicates[index - 1]),
+              );
+            },
+            itemCount: duplicates.length,
+            shrinkWrap: true,
+          ),
+        ),
+        Padding(padding: EdgeInsets.all(4)),
+        Padding(
+          padding: EdgeInsets.all(8),
+          child: Text(
+            "delete",
+            style: TextStyle(
+              color: Colors.white.withOpacity(0.8),
+              height: 1.2,
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  Widget _getGridView(DuplicateFiles duplicates) {
+    return Column(
+      children: [
+        Padding(
+          padding: const EdgeInsets.fromLTRB(4, 8, 4, 4),
+          child: Text(
+            duplicates.files.length.toString() +
+                " files, " +
+                convertBytesToReadableFormat(duplicates.size) +
+                " each",
+            style: TextStyle(
+              color: Colors.white.withOpacity(0.8),
+            ),
+          ),
+        ),
+        GridView.builder(
+          shrinkWrap: true,
+          physics:
+              NeverScrollableScrollPhysics(), // to disable GridView's scrolling
+          itemBuilder: (context, index) {
+            return _buildFile(context, duplicates.files[index]);
+          },
+          itemCount: duplicates.files.length,
+          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+            crossAxisCount: 4,
+          ),
+          padding: EdgeInsets.all(0),
+        ),
+      ],
+    );
+  }
+
+  Widget _buildFile(BuildContext context, File file) {
+    return GestureDetector(
+      onTap: () {
+        // TODO
+      },
+      onLongPress: () {
+        HapticFeedback.lightImpact();
+        // TODO
+      },
+      child: Container(
+        margin: const EdgeInsets.all(2.0),
+        decoration: BoxDecoration(
+          border: true
+              ? Border.all(
+                  width: 4.0,
+                  color: Theme.of(context).buttonColor,
+                )
+              : null,
+        ),
+        child: Hero(
+          tag: "deduplicate_" + file.tag(),
+          child: ThumbnailWidget(
+            file,
+            diskLoadDeferDuration: kThumbnailDiskLoadDeferDuration,
+            serverLoadDeferDuration: kThumbnailServerLoadDeferDuration,
+            shouldShowLivePhotoOverlay: true,
+            key: Key("deduplicate_" + file.tag()),
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 81 - 0
lib/ui/settings/backup_section_widget.dart

@@ -1,10 +1,12 @@
 import 'dart:io';
 
 import 'package:flutter/material.dart';
+import 'package:logging/logging.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/models/backup_status.dart';
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/ui/backup_folder_selection_page.dart';
+import 'package:photos/ui/deduplicate_page.dart';
 import 'package:photos/ui/free_space_page.dart';
 import 'package:photos/ui/settings/settings_section_title.dart';
 import 'package:photos/ui/settings/settings_text_item.dart';
@@ -118,6 +120,36 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
             icon: Icons.navigate_next,
           ),
         ),
+        Platform.isIOS
+            ? Padding(padding: EdgeInsets.all(4))
+            : Padding(padding: EdgeInsets.all(2)),
+        Divider(height: 4),
+        Platform.isIOS
+            ? Padding(padding: EdgeInsets.all(2))
+            : Padding(padding: EdgeInsets.all(2)),
+        GestureDetector(
+          behavior: HitTestBehavior.translucent,
+          onTap: () async {
+            final dialog = createProgressDialog(context, "calculating...");
+            await dialog.show();
+            final duplicates = await SyncService.instance.getDuplicateFiles();
+            await dialog.hide();
+            if (duplicates.isEmpty) {
+              showErrorDialog(context, "✨ no duplicates",
+                  "you've no duplicate files that can be cleared");
+            } else {
+              bool result =
+                  await routeToPage(context, DeduplicatePage(duplicates));
+              if (result == true) {
+                _showDuplicateFilesDeletedDialog(0); // todo
+              }
+            }
+          },
+          child: SettingsTextItem(
+            text: "deduplicate files",
+            icon: Icons.navigate_next,
+          ),
+        ),
       ],
     );
   }
@@ -172,4 +204,53 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
       useRootNavigator: true,
     );
   }
+
+  void _showDuplicateFilesDeletedDialog(int spaceFreed) {
+    AlertDialog alert = AlertDialog(
+      title: Text("success"),
+      content: Text(
+          "you have successfully freed up " + formatBytes(spaceFreed) + "!"),
+      actions: [
+        TextButton(
+          child: Text(
+            "rate us",
+            style: TextStyle(
+              color: Theme.of(context).buttonColor,
+            ),
+          ),
+          onPressed: () {
+            Navigator.of(context, rootNavigator: true).pop('dialog');
+            if (Platform.isAndroid) {
+              launch(
+                  "https://play.google.com/store/apps/details?id=io.ente.photos");
+            } else {
+              launch("https://apps.apple.com/in/app/ente-photos/id1542026904");
+            }
+          },
+        ),
+        TextButton(
+          child: Text(
+            "ok",
+            style: TextStyle(
+              color: Colors.white,
+            ),
+          ),
+          onPressed: () {
+            showToast(
+                "also empty your \"Trash\" from 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,
+    );
+  }
 }