Browse Source

Merge pull request #697 from ente-io/enable_collect

Manage Link: Add option to enable collect
Neeraj Gupta 2 years ago
parent
commit
53f5afd8fa

+ 1 - 0
lib/core/constants.dart

@@ -44,6 +44,7 @@ const supportEmail = 'support@ente.io';
 class FFDefault {
   static const bool enableStripe = true;
   static const bool disableCFWorker = false;
+  static const bool enableCollect = false;
 }
 
 const kDefaultProductionEndpoint = 'https://api.ente.io';

+ 10 - 0
lib/models/collection.dart

@@ -241,6 +241,7 @@ class PublicURL {
   int deviceLimit;
   int validTill;
   bool enableDownload;
+  bool enableCollect;
   bool passwordEnabled;
 
   PublicURL({
@@ -249,6 +250,7 @@ class PublicURL {
     required this.validTill,
     this.enableDownload = true,
     this.passwordEnabled = false,
+    this.enableCollect = false,
   });
 
   Map<String, dynamic> toMap() {
@@ -258,9 +260,16 @@ class PublicURL {
       'validTill': validTill,
       'enableDownload': enableDownload,
       'passwordEnabled': passwordEnabled,
+      'enableCollect': enableCollect,
     };
   }
 
+  bool get hasExpiry => validTill != 0;
+
+  // isExpired indicates whether the link has expired or not
+  bool get isExpired =>
+      hasExpiry && validTill < DateTime.now().microsecondsSinceEpoch;
+
   static fromMap(Map<String, dynamic>? map) {
     if (map == null) return null;
 
@@ -270,6 +279,7 @@ class PublicURL {
       validTill: map['validTill'] ?? 0,
       enableDownload: map['enableDownload'] ?? true,
       passwordEnabled: map['passwordEnabled'] ?? false,
+      enableCollect: map['enableCollect'] ?? false,
     );
   }
 }

+ 17 - 0
lib/services/feature_flag_service.dart

@@ -62,6 +62,18 @@ class FeatureFlagService {
     }
   }
 
+  bool enableCollect() {
+    if (isInternalUserOrDebugBuild()) {
+      return true;
+    }
+    try {
+      return _getFeatureFlags().enableCollect;
+    } catch (e) {
+      _logger.severe(e);
+      return FFDefault.enableCollect;
+    }
+  }
+
   bool isInternalUserOrDebugBuild() {
     final String? email = Configuration.instance.getEmail();
     return (email != null && email.endsWith("@ente.io")) || kDebugMode;
@@ -85,20 +97,24 @@ class FeatureFlags {
   static FeatureFlags defaultFlags = FeatureFlags(
     disableCFWorker: FFDefault.disableCFWorker,
     enableStripe: FFDefault.enableStripe,
+    enableCollect: FFDefault.enableCollect,
   );
 
   final bool disableCFWorker;
   final bool enableStripe;
+  final bool enableCollect;
 
   FeatureFlags({
     required this.disableCFWorker,
     required this.enableStripe,
+    required this.enableCollect,
   });
 
   Map<String, dynamic> toMap() {
     return {
       "disableCFWorker": disableCFWorker,
       "enableStripe": enableStripe,
+      "enableCollect": enableCollect,
     };
   }
 
@@ -111,6 +127,7 @@ class FeatureFlags {
     return FeatureFlags(
       disableCFWorker: json["disableCFWorker"] ?? FFDefault.disableCFWorker,
       enableStripe: json["enableStripe"] ?? FFDefault.enableStripe,
+      enableCollect: json["enableCollect"] ?? FFDefault.enableCollect,
     );
   }
 }

+ 3 - 1
lib/ui/components/captioned_text_widget.dart

@@ -7,12 +7,14 @@ class CaptionedTextWidget extends StatelessWidget {
   final TextStyle? textStyle;
   final bool makeTextBold;
   final Color? textColor;
+  final Color? subTitleColor;
   const CaptionedTextWidget({
     required this.title,
     this.subTitle,
     this.textStyle,
     this.makeTextBold = false,
     this.textColor,
+    this.subTitleColor,
     Key? key,
   }) : super(key: key);
 
@@ -47,7 +49,7 @@ class CaptionedTextWidget extends StatelessWidget {
         Text(
           subTitle!,
           style: enteTextTheme.small.copyWith(
-            color: enteColorScheme.textMuted,
+            color: subTitleColor ?? enteColorScheme.textMuted,
           ),
         ),
       );

+ 169 - 130
lib/ui/sharing/manage_links_widget.dart

@@ -2,6 +2,7 @@
 
 import 'dart:convert';
 
+import 'package:collection/collection.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
@@ -9,7 +10,14 @@ import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/services/collections_service.dart';
+import 'package:photos/services/feature_flag_service.dart';
+import 'package:photos/theme/colors.dart';
+import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/common/dialogs.dart';
+import 'package:photos/ui/components/captioned_text_widget.dart';
+import 'package:photos/ui/components/divider_widget.dart';
+import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_section_description_widget.dart';
 import 'package:photos/utils/crypto_util.dart';
 import 'package:photos/utils/date_time_util.dart';
 import 'package:photos/utils/dialog_util.dart';
@@ -49,6 +57,43 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
 
   @override
   Widget build(BuildContext context) {
+    final enteColorScheme = getEnteColorScheme(context);
+    final PublicURL url = widget.collection?.publicURLs?.firstOrNull;
+    final enableCollectFeature = FeatureFlagService.instance.enableCollect();
+    final Widget collect = enableCollectFeature
+        ? Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              MenuItemWidget(
+                captionedTextWidget: const CaptionedTextWidget(
+                  title: "Allow adding photos",
+                ),
+                alignCaptionedTextToLeft: true,
+                menuItemColor: getEnteColorScheme(context).fillFaint,
+                pressedColor: getEnteColorScheme(context).fillFaint,
+                trailingWidget: Switch.adaptive(
+                  value: widget
+                          .collection.publicURLs?.firstOrNull?.enableCollect ??
+                      false,
+                  onChanged: (value) async {
+                    await _updateUrlSettings(
+                      context,
+                      {'enableCollect': value},
+                    );
+
+                    setState(() {});
+                  },
+                ),
+              ),
+              const MenuSectionDescriptionWidget(
+                content:
+                    "Allow people with the link to also add photos to the shared "
+                    "album.",
+              ),
+              const SizedBox(height: 24)
+            ],
+          )
+        : const SizedBox.shrink();
     return Scaffold(
       backgroundColor: Theme.of(context).backgroundColor,
       appBar: AppBar(
@@ -63,146 +108,138 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
             Padding(
               padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
               child: Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
                 children: [
-                  const Padding(padding: EdgeInsets.all(4)),
-                  GestureDetector(
-                    behavior: HitTestBehavior.translucent,
+                  collect,
+                  MenuItemWidget(
+                    alignCaptionedTextToLeft: true,
+                    captionedTextWidget: CaptionedTextWidget(
+                      title: "Link expiry",
+                      subTitle: (url.hasExpiry
+                          ? (url.isExpired ? "Expired" : "Enabled")
+                          : "Never"),
+                      subTitleColor: url.isExpired ? warning500 : null,
+                    ),
+                    trailingIcon: Icons.chevron_right,
+                    menuItemColor: enteColorScheme.fillFaint,
                     onTap: () async {
                       await showPicker();
                     },
-                    child: Row(
-                      crossAxisAlignment: CrossAxisAlignment.center,
-                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                      children: [
-                        Column(
-                          mainAxisAlignment: MainAxisAlignment.center,
-                          crossAxisAlignment: CrossAxisAlignment.start,
-                          children: [
-                            const Text("Link expiry"),
-                            const Padding(padding: EdgeInsets.all(4)),
-                            _getLinkExpiryTimeWidget(),
-                          ],
-                        ),
-                        const Icon(Icons.navigate_next),
-                      ],
-                    ),
                   ),
-                  const Padding(padding: EdgeInsets.all(4)),
-                  const Divider(height: 4),
-                  const Padding(padding: EdgeInsets.all(4)),
-                  GestureDetector(
-                    behavior: HitTestBehavior.translucent,
-                    onTap: () {
-                      _showDeviceLimitPicker();
-                    },
-                    child: Row(
-                      crossAxisAlignment: CrossAxisAlignment.center,
-                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                      children: [
-                        Column(
-                          mainAxisAlignment: MainAxisAlignment.center,
-                          crossAxisAlignment: CrossAxisAlignment.start,
-                          children: [
-                            const Text("Device limit"),
-                            const Padding(padding: EdgeInsets.all(4)),
-                            Text(
-                              widget.collection.publicURLs.first.deviceLimit
-                                  .toString(),
-                              style: const TextStyle(
-                                color: Colors.grey,
-                              ),
-                            ),
-                          ],
-                        ),
-                        const Icon(Icons.navigate_next),
-                      ],
+                  url.hasExpiry
+                      ? MenuSectionDescriptionWidget(
+                          content: url.isExpired
+                              ? "This link has expired. Please select a new expiry time or disable link expiry."
+                              : 'Link will expire on '
+                                  '${getFormattedTime(DateTime.fromMicrosecondsSinceEpoch(url.validTill))}',
+                        )
+                      : const SizedBox.shrink(),
+                  const Padding(padding: EdgeInsets.only(top: 24)),
+                  MenuItemWidget(
+                    captionedTextWidget: CaptionedTextWidget(
+                      title: "Device limit",
+                      subTitle: widget.collection.publicURLs.first.deviceLimit
+                          .toString(),
                     ),
+                    trailingIcon: Icons.chevron_right,
+                    menuItemColor: enteColorScheme.fillFaint,
+                    alignCaptionedTextToLeft: true,
+                    isBottomBorderRadiusRemoved: true,
+                    onTap: () async {
+                      await _showDeviceLimitPicker();
+                    },
                   ),
-                  const Padding(padding: EdgeInsets.all(4)),
-                  const Divider(height: 4),
-                  const Padding(padding: EdgeInsets.all(4)),
-                  SizedBox(
-                    height: 36,
-                    child: Row(
-                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                      children: [
-                        const Text("Password lock"),
-                        Switch.adaptive(
-                          value: widget.collection.publicURLs?.first
-                                  ?.passwordEnabled ??
-                              false,
-                          onChanged: (enablePassword) async {
-                            if (enablePassword) {
-                              final inputResult =
-                                  await _displayLinkPasswordInput(context);
-                              if (inputResult != null &&
-                                  inputResult == 'ok' &&
-                                  _textFieldController.text.trim().isNotEmpty) {
-                                final propToUpdate = await _getEncryptedPassword(
-                                  _textFieldController.text,
-                                );
-                                await _updateUrlSettings(context, propToUpdate);
-                              }
-                            } else {
-                              await _updateUrlSettings(
-                                context,
-                                {'disablePassword': true},
-                              );
-                            }
-                            setState(() {});
-                          },
-                        ),
-                      ],
+                  DividerWidget(
+                    dividerType: DividerType.menu,
+                    bgColor: getEnteColorScheme(context).blurStrokeFaint,
+                  ),
+                  MenuItemWidget(
+                    captionedTextWidget: const CaptionedTextWidget(
+                      title: "Allow downloads",
+                    ),
+                    alignCaptionedTextToLeft: true,
+                    isBottomBorderRadiusRemoved: true,
+                    isTopBorderRadiusRemoved: true,
+                    menuItemColor: getEnteColorScheme(context).fillFaint,
+                    pressedColor: getEnteColorScheme(context).fillFaint,
+                    trailingWidget: Switch.adaptive(
+                      value: widget.collection.publicURLs?.firstOrNull
+                              ?.enableDownload ??
+                          true,
+                      onChanged: (value) async {
+                        if (!value) {
+                          final choice = await showChoiceDialog(
+                            context,
+                            'Disable downloads',
+                            'Are you sure that you want to disable the download button for files?',
+                            firstAction: 'No',
+                            secondAction: 'Yes',
+                            firstActionColor:
+                                Theme.of(context).colorScheme.greenText,
+                            secondActionColor: Theme.of(context)
+                                .colorScheme
+                                .inverseBackgroundColor,
+                          );
+                          if (choice != DialogUserChoice.secondChoice) {
+                            return;
+                          }
+                        }
+                        await _updateUrlSettings(
+                          context,
+                          {'enableDownload': value},
+                        );
+                        if (!value) {
+                          showErrorDialog(
+                            context,
+                            "Please note",
+                            "Viewers can still take screenshots or save a copy of your photos using external tools",
+                          );
+                        }
+                        setState(() {});
+                      },
                     ),
                   ),
-                  const Padding(padding: EdgeInsets.all(4)),
-                  const Divider(height: 4),
-                  const Padding(padding: EdgeInsets.all(4)),
-                  SizedBox(
-                    height: 36,
-                    child: Row(
-                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                      children: [
-                        const Text("File download"),
-                        Switch.adaptive(
-                          value: widget.collection.publicURLs?.first
-                                  ?.enableDownload ??
-                              true,
-                          onChanged: (value) async {
-                            if (!value) {
-                              final choice = await showChoiceDialog(
-                                context,
-                                'Disable downloads',
-                                'Are you sure that you want to disable the download button for files?',
-                                firstAction: 'No',
-                                secondAction: 'Yes',
-                                firstActionColor:
-                                    Theme.of(context).colorScheme.greenText,
-                                secondActionColor: Theme.of(context)
-                                    .colorScheme
-                                    .inverseBackgroundColor,
-                              );
-                              if (choice != DialogUserChoice.secondChoice) {
-                                return;
-                              }
-                            }
-                            await _updateUrlSettings(
-                              context,
-                              {'enableDownload': value},
+                  DividerWidget(
+                    dividerType: DividerType.menu,
+                    bgColor: getEnteColorScheme(context).blurStrokeFaint,
+                  ),
+                  MenuItemWidget(
+                    captionedTextWidget: const CaptionedTextWidget(
+                      title: "Password lock",
+                    ),
+                    alignCaptionedTextToLeft: true,
+                    isTopBorderRadiusRemoved: true,
+                    menuItemColor: getEnteColorScheme(context).fillFaint,
+                    pressedColor: getEnteColorScheme(context).fillFaint,
+                    trailingWidget: Switch.adaptive(
+                      value: widget.collection.publicURLs?.firstOrNull
+                              ?.passwordEnabled ??
+                          false,
+                      onChanged: (enablePassword) async {
+                        if (enablePassword) {
+                          final inputResult =
+                              await _displayLinkPasswordInput(context);
+                          if (inputResult != null &&
+                              inputResult == 'ok' &&
+                              _textFieldController.text.trim().isNotEmpty) {
+                            final propToUpdate = await _getEncryptedPassword(
+                              _textFieldController.text,
                             );
-                            if (!value) {
-                              showErrorDialog(
-                                context,
-                                "Please note",
-                                "Viewers can still take screenshots or save a copy of your photos using external tools",
-                              );
-                            }
-                            setState(() {});
-                          },
-                        ),
-                      ],
+                            await _updateUrlSettings(context, propToUpdate);
+                          }
+                        } else {
+                          await _updateUrlSettings(
+                            context,
+                            {'disablePassword': true},
+                          );
+                        }
+                        setState(() {});
+                      },
                     ),
                   ),
+                  const SizedBox(
+                    height: 24,
+                  ),
                 ],
               ),
             ),
@@ -252,7 +289,8 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                   CupertinoButton(
                     onPressed: () async {
                       int newValidTill = -1;
-                      final int expireAfterInMicroseconds = _selectedExpiry.item3;
+                      final int expireAfterInMicroseconds =
+                          _selectedExpiry.item3;
                       // need to manually select time
                       if (expireAfterInMicroseconds < 0) {
                         final timeInMicrosecondsFromEpoch =
@@ -446,7 +484,8 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
   }
 
   Text _getLinkExpiryTimeWidget() {
-    final int validTill = widget.collection.publicURLs?.first?.validTill ?? 0;
+    final int validTill =
+        widget.collection.publicURLs?.firstOrNull?.validTill ?? 0;
     if (validTill == 0) {
       return const Text(
         'Never',