浏览代码

Support for basic hidden functionality

Neeraj Gupta 2 年之前
父节点
当前提交
587c0b5cd9

+ 37 - 0
lib/db/files_db.dart

@@ -627,6 +627,43 @@ class FilesDB {
     return FileLoadResult(files, files.length == limit);
   }
 
+  Future<FileLoadResult> getFilesInCollections(
+    List<int> collectionIDs,
+    int startTime,
+    int endTime,
+    int userID, {
+    int limit,
+    bool asc,
+  }) async {
+    if (collectionIDs.isEmpty) {
+      return FileLoadResult(<File>[], false);
+    }
+    String inParam = "";
+    for (final id in collectionIDs) {
+      inParam += "'" + id.toString() + "',";
+    }
+    inParam = inParam.substring(0, inParam.length - 1);
+    final db = await instance.database;
+    final order = (asc ?? false ? 'ASC' : 'DESC');
+    String whereClause =
+        '$columnCollectionID  IN ($inParam) AND $columnCreationTime >= ? AND '
+        '$columnCreationTime <= ? AND $columnOwnerID = ?';
+    final List<Object> whereArgs = [startTime, endTime, userID];
+
+    final results = await db.query(
+      filesTable,
+      where: whereClause,
+      whereArgs: whereArgs,
+      orderBy:
+          '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
+      limit: limit,
+    );
+    final files = convertToFiles(results);
+    final dedupeResult = _deduplicatedAndFilterIgnoredFiles(files, {});
+    _logger.info("Fetched " + dedupeResult.length.toString() + " files");
+    return FileLoadResult(files, files.length == limit);
+  }
+
   Future<List<File>> getFilesCreatedWithinDurations(
     List<List<int>> durations,
     Set<int> ignoredCollectionIDs, {

+ 2 - 0
lib/events/files_updated_event.dart

@@ -20,4 +20,6 @@ enum EventType {
   deletedFromEverywhere,
   archived,
   unarchived,
+  hide,
+  unhide,
 }

+ 1 - 0
lib/models/gallery_type.dart

@@ -1,6 +1,7 @@
 enum GalleryType {
   homepage,
   archive,
+  hidden,
   trash,
   localFolder,
   // indicator for gallery view of collections shared with the user

+ 7 - 0
lib/services/hidden_service.dart

@@ -5,6 +5,10 @@ import 'package:collection/collection.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:logging/logging.dart';
+import 'package:photos/core/event_bus.dart';
+import 'package:photos/events/files_updated_event.dart';
+import 'package:photos/events/force_reload_home_gallery_event.dart';
+import 'package:photos/events/local_photos_updated_event.dart';
 import 'package:photos/models/api/collection/create_request.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/models/file.dart';
@@ -65,6 +69,9 @@ extension HiddenService on CollectionsService {
       for (MapEntry<int, List<File>> entry in collectionToFilesMap.entries) {
         await move(defaultHiddenCollection.id, entry.key, entry.value);
       }
+      Bus.instance.fire(ForceReloadHomeGalleryEvent());
+      Bus.instance.fire(
+          LocalPhotosUpdatedEvent(filesToHide, type: EventType.unarchived));
 
       await dialog.hide();
     } on AssertionError catch (e) {

+ 104 - 0
lib/ui/collections/archived_collections_button_widget.dart

@@ -0,0 +1,104 @@
+// @dart=2.9
+
+import 'package:flutter/material.dart';
+import 'package:photos/core/configuration.dart';
+import 'package:photos/db/files_db.dart';
+import 'package:photos/models/magic_metadata.dart';
+import 'package:photos/ui/viewer/gallery/archive_page.dart';
+import 'package:photos/utils/navigation_util.dart';
+
+class ArchivedCollectionsButtonWidget extends StatelessWidget {
+  final TextStyle textStyle;
+
+  const ArchivedCollectionsButtonWidget(
+    this.textStyle, {
+    Key key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return OutlinedButton(
+      style: OutlinedButton.styleFrom(
+        backgroundColor: Theme.of(context).backgroundColor,
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.circular(8),
+        ),
+        padding: const EdgeInsets.all(0),
+        side: BorderSide(
+          width: 0.5,
+          color: Theme.of(context).iconTheme.color.withOpacity(0.24),
+        ),
+      ),
+      child: SizedBox(
+        height: 48,
+        width: double.infinity,
+        child: Padding(
+          padding: const EdgeInsets.symmetric(horizontal: 16),
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              Row(
+                children: [
+                  Icon(
+                    Icons.visibility_off,
+                    color: Theme.of(context).iconTheme.color,
+                  ),
+                  const Padding(padding: EdgeInsets.all(6)),
+                  FutureBuilder<int>(
+                    future: FilesDB.instance.fileCountWithVisibility(
+                      visibilityArchive,
+                      Configuration.instance.getUserID(),
+                    ),
+                    builder: (context, snapshot) {
+                      if (snapshot.hasData && snapshot.data > 0) {
+                        return RichText(
+                          text: TextSpan(
+                            style: textStyle,
+                            children: [
+                              TextSpan(
+                                text: "Hidden",
+                                style: Theme.of(context).textTheme.subtitle1,
+                              ),
+                              const TextSpan(text: "  \u2022  "),
+                              TextSpan(
+                                text: snapshot.data.toString(),
+                              ),
+                              //need to query in db and bring this value
+                            ],
+                          ),
+                        );
+                      } else {
+                        return RichText(
+                          text: TextSpan(
+                            style: textStyle,
+                            children: [
+                              TextSpan(
+                                text: "Hidden",
+                                style: Theme.of(context).textTheme.subtitle1,
+                              ),
+                              //need to query in db and bring this value
+                            ],
+                          ),
+                        );
+                      }
+                    },
+                  ),
+                ],
+              ),
+              Icon(
+                Icons.chevron_right,
+                color: Theme.of(context).iconTheme.color,
+              ),
+            ],
+          ),
+        ),
+      ),
+      onPressed: () async {
+        routeToPage(
+          context,
+          ArchivePage(),
+        );
+      },
+    );
+  }
+}

+ 21 - 43
lib/ui/collections/hidden_collections_button_widget.dart

@@ -1,10 +1,8 @@
 // @dart=2.9
 
 import 'package:flutter/material.dart';
-import 'package:photos/core/configuration.dart';
-import 'package:photos/db/files_db.dart';
-import 'package:photos/models/magic_metadata.dart';
-import 'package:photos/ui/viewer/gallery/archive_page.dart';
+import 'package:photos/services/local_authentication_service.dart';
+import 'package:photos/ui/viewer/gallery/hidden_page.dart';
 import 'package:photos/utils/navigation_util.dart';
 
 class HiddenCollectionsButtonWidget extends StatelessWidget {
@@ -44,44 +42,17 @@ class HiddenCollectionsButtonWidget extends StatelessWidget {
                     color: Theme.of(context).iconTheme.color,
                   ),
                   const Padding(padding: EdgeInsets.all(6)),
-                  FutureBuilder<int>(
-                    future: FilesDB.instance.fileCountWithVisibility(
-                      visibilityArchive,
-                      Configuration.instance.getUserID(),
+                  RichText(
+                    text: TextSpan(
+                      style: textStyle,
+                      children: [
+                        TextSpan(
+                          text: "Hidden Stuff",
+                          style: Theme.of(context).textTheme.subtitle1,
+                        ),
+                        //need to query in db and bring this value
+                      ],
                     ),
-                    builder: (context, snapshot) {
-                      if (snapshot.hasData && snapshot.data > 0) {
-                        return RichText(
-                          text: TextSpan(
-                            style: textStyle,
-                            children: [
-                              TextSpan(
-                                text: "Hidden",
-                                style: Theme.of(context).textTheme.subtitle1,
-                              ),
-                              const TextSpan(text: "  \u2022  "),
-                              TextSpan(
-                                text: snapshot.data.toString(),
-                              ),
-                              //need to query in db and bring this value
-                            ],
-                          ),
-                        );
-                      } else {
-                        return RichText(
-                          text: TextSpan(
-                            style: textStyle,
-                            children: [
-                              TextSpan(
-                                text: "Hidden",
-                                style: Theme.of(context).textTheme.subtitle1,
-                              ),
-                              //need to query in db and bring this value
-                            ],
-                          ),
-                        );
-                      }
-                    },
                   ),
                 ],
               ),
@@ -94,10 +65,17 @@ class HiddenCollectionsButtonWidget extends StatelessWidget {
         ),
       ),
       onPressed: () async {
-        routeToPage(
+        final hasAuthenticated = await LocalAuthenticationService.instance
+            .requestLocalAuthentication(
           context,
-          ArchivePage(),
+          "Please authenticate to view your hidden stuff",
         );
+        if (hasAuthenticated) {
+          routeToPage(
+            context,
+            HiddenPage(),
+          );
+        }
       },
     );
   }

+ 4 - 1
lib/ui/collections_gallery_widget.dart

@@ -13,6 +13,7 @@ import 'package:photos/events/user_logged_out_event.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/models/collection_items.dart';
 import 'package:photos/services/collections_service.dart';
+import 'package:photos/ui/collections/archived_collections_button_widget.dart';
 import 'package:photos/ui/collections/device_folders_grid_view_widget.dart';
 import 'package:photos/ui/collections/hidden_collections_button_widget.dart';
 import 'package:photos/ui/collections/remote_collections_grid_view_widget.dart';
@@ -85,7 +86,7 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
         await collectionsService.getLatestCollectionFiles();
     for (final file in latestCollectionFiles) {
       final c = collectionsService.getCollectionByID(file.collectionID);
-      if (c.owner.id == userID) {
+      if (c.owner.id == userID && !c.isHidden()) {
         collectionsWithThumbnail.add(CollectionWithThumbnail(c, file));
       }
     }
@@ -157,6 +158,8 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
                 children: [
                   TrashButtonWidget(trashAndHiddenTextStyle),
                   const SizedBox(height: 12),
+                  ArchivedCollectionsButtonWidget(trashAndHiddenTextStyle),
+                  const SizedBox(height: 12),
                   HiddenCollectionsButtonWidget(trashAndHiddenTextStyle),
                 ],
               ),

+ 11 - 3
lib/ui/create_collection_page.dart

@@ -23,7 +23,7 @@ import 'package:photos/utils/share_util.dart';
 import 'package:photos/utils/toast_util.dart';
 import 'package:receive_sharing_intent/receive_sharing_intent.dart';
 
-enum CollectionActionType { addFiles, moveFiles, restoreFiles }
+enum CollectionActionType { addFiles, moveFiles, restoreFiles, unHide }
 
 String _actionName(CollectionActionType type, bool plural) {
   final titleSuffix = (plural ? "s" : "");
@@ -38,6 +38,9 @@ String _actionName(CollectionActionType type, bool plural) {
     case CollectionActionType.restoreFiles:
       text = "Restore file";
       break;
+    case CollectionActionType.unHide:
+      text = "Unhide file";
+      break;
   }
   return text + titleSuffix;
 }
@@ -193,7 +196,7 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
     for (final file in latestCollectionFiles) {
       final c =
           CollectionsService.instance.getCollectionByID(file.collectionID);
-      if (c.owner.id == Configuration.instance.getUserID()) {
+      if (c.owner.id == Configuration.instance.getUserID() && !c.isHidden()) {
         collectionsWithThumbnail.add(CollectionWithThumbnail(c, file));
       }
     }
@@ -279,6 +282,8 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
         return _addToCollection(collectionID);
       case CollectionActionType.moveFiles:
         return _moveFilesToCollection(collectionID);
+      case CollectionActionType.unHide:
+        return _moveFilesToCollection(collectionID);
       case CollectionActionType.restoreFiles:
         return _restoreFilesToCollection(collectionID);
     }
@@ -286,7 +291,10 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
   }
 
   Future<bool> _moveFilesToCollection(int toCollectionID) async {
-    final dialog = createProgressDialog(context, "Moving files to album...");
+    final String message = widget.actionType == CollectionActionType.moveFiles
+        ? "Moving files to album..."
+        : "Un hide files to album";
+    final dialog = createProgressDialog(context, message);
     await dialog.show();
     try {
       final int fromCollectionID =

+ 1 - 0
lib/ui/home/home_gallery_widget.dart

@@ -70,6 +70,7 @@ class HomeGalleryWidget extends StatelessWidget {
         EventType.deletedFromRemote,
         EventType.deletedFromEverywhere,
         EventType.archived,
+        EventType.hide,
       },
       forceReloadEvents: [
         Bus.instance.on<BackupFoldersUpdatedEvent>(),

+ 24 - 3
lib/ui/viewer/gallery/gallery_overlay_widget.dart

@@ -229,7 +229,7 @@ class _OverlayWidgetState extends State<OverlayWidget> {
     widget.selectedFiles.clearAll();
   }
 
-  Future<void> _createAlbum() async {
+  Future<void> _createCollectionAction(CollectionActionType type) async {
     Navigator.push(
       context,
       PageTransition(
@@ -237,6 +237,7 @@ class _OverlayWidgetState extends State<OverlayWidget> {
         child: CreateCollectionPage(
           widget.selectedFiles,
           null,
+          actionType: type,
         ),
       ),
     );
@@ -264,7 +265,8 @@ class _OverlayWidgetState extends State<OverlayWidget> {
     }
     // skip add button for incoming collection till this feature is implemented
     if (Configuration.instance.hasConfiguredAccount() &&
-        widget.type != GalleryType.sharedCollection) {
+        widget.type != GalleryType.sharedCollection &&
+        widget.type != GalleryType.hidden) {
       String msg = "Add";
       IconData iconData = Platform.isAndroid ? Icons.add : CupertinoIcons.add;
       // show upload icon instead of add for files selected in local gallery
@@ -279,7 +281,25 @@ class _OverlayWidgetState extends State<OverlayWidget> {
             color: Theme.of(context).colorScheme.iconColor,
             icon: Icon(iconData),
             onPressed: () {
-              _createAlbum();
+              _createCollectionAction(CollectionActionType.addFiles);
+            },
+          ),
+        ),
+      );
+    }
+
+    if (Configuration.instance.hasConfiguredAccount() &&
+        widget.type == GalleryType.hidden) {
+      String msg = "Unhide";
+      IconData iconData = Icons.visibility;
+      actions.add(
+        Tooltip(
+          message: msg,
+          child: IconButton(
+            color: Theme.of(context).colorScheme.iconColor,
+            icon: Icon(iconData),
+            onPressed: () {
+              _createCollectionAction(CollectionActionType.unHide);
             },
           ),
         ),
@@ -320,6 +340,7 @@ class _OverlayWidgetState extends State<OverlayWidget> {
     );
     if (widget.type == GalleryType.homepage ||
         widget.type == GalleryType.archive ||
+        widget.type == GalleryType.hidden ||
         widget.type == GalleryType.localFolder ||
         widget.type == GalleryType.searchResults) {
       actions.add(

+ 85 - 0
lib/ui/viewer/gallery/hidden_page.dart

@@ -0,0 +1,85 @@
+// @dart=2.9
+
+import 'package:flutter/material.dart';
+import 'package:photos/core/configuration.dart';
+import 'package:photos/core/event_bus.dart';
+import 'package:photos/db/files_db.dart';
+import 'package:photos/events/files_updated_event.dart';
+import 'package:photos/models/gallery_type.dart';
+import 'package:photos/models/selected_files.dart';
+import 'package:photos/services/collections_service.dart';
+import 'package:photos/ui/viewer/gallery/gallery.dart';
+import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart';
+import 'package:photos/ui/viewer/gallery/gallery_overlay_widget.dart';
+
+class HiddenPage extends StatelessWidget {
+  final String tagPrefix;
+  final GalleryType appBarType;
+  final GalleryType overlayType;
+  final _selectedFiles = SelectedFiles();
+
+  HiddenPage({
+    this.tagPrefix = "hidden_page",
+    this.appBarType = GalleryType.hidden,
+    this.overlayType = GalleryType.hidden,
+    Key key,
+  }) : super(key: key);
+
+  @override
+  Widget build(Object context) {
+    final gallery = Gallery(
+      asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) {
+        return FilesDB.instance.getFilesInCollections(
+          CollectionsService.instance.getHiddenCollections().toList(),
+          creationStartTime,
+          creationEndTime,
+          Configuration.instance.getUserID(),
+          limit: limit,
+          asc: asc,
+        );
+      },
+      reloadEvent: Bus.instance.on<FilesUpdatedEvent>().where(
+            (event) =>
+                event.updatedFiles.firstWhere(
+                  (element) => element.uploadedFileID != null,
+                  orElse: () => null,
+                ) !=
+                null,
+          ),
+      removalEventTypes: const {EventType.unhide},
+      forceReloadEvents: [
+        Bus.instance.on<FilesUpdatedEvent>().where(
+              (event) =>
+                  event.updatedFiles.firstWhere(
+                    (element) => element.uploadedFileID != null,
+                    orElse: () => null,
+                  ) !=
+                  null,
+            ),
+      ],
+      tagPrefix: tagPrefix,
+      selectedFiles: _selectedFiles,
+      initialFiles: null,
+    );
+    return Scaffold(
+      appBar: PreferredSize(
+        preferredSize: const Size.fromHeight(50.0),
+        child: GalleryAppBarWidget(
+          appBarType,
+          "Hidden Stuff",
+          _selectedFiles,
+        ),
+      ),
+      body: Stack(
+        alignment: Alignment.bottomCenter,
+        children: [
+          gallery,
+          GalleryOverlayWidget(
+            overlayType,
+            _selectedFiles,
+          )
+        ],
+      ),
+    );
+  }
+}