From 36247884a4ee2a474ae551de631e81eafe5dfee4 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 17 Sep 2022 10:01:28 +0530 Subject: [PATCH 01/15] Minor UI fix for recovery page --- lib/ui/account/recovery_key_page.dart | 260 ++++++++++++++------------ 1 file changed, 137 insertions(+), 123 deletions(-) diff --git a/lib/ui/account/recovery_key_page.dart b/lib/ui/account/recovery_key_page.dart index 84d097a57..9314c86b2 100644 --- a/lib/ui/account/recovery_key_page.dart +++ b/lib/ui/account/recovery_key_page.dart @@ -56,135 +56,149 @@ class _RecoveryKeyPageState extends State { 'recovery code should have $kMnemonicKeyWordCount words', ); } + final double topPadding = widget.showAppBar + ? 40 + : widget.showProgressBar + ? 32 + : 120; return Scaffold( - appBar: widget.showProgressBar - ? AppBar( - elevation: 0, - title: Hero( - tag: "recovery_key", - child: StepProgressIndicator( - totalSteps: 4, - currentStep: 3, - selectedColor: Theme.of(context).colorScheme.greenAlternative, - roundedEdges: const Radius.circular(10), - unselectedColor: - Theme.of(context).colorScheme.stepProgressUnselectedColor, - ), - ), - ) - : widget.showAppBar - ? AppBar( - elevation: 0, - title: Text(widget.title ?? "Recovery key"), - ) - : null, - body: Padding( - padding: EdgeInsets.fromLTRB( - 20, - widget.showAppBar - ? 40 - : widget.showProgressBar - ? 32 - : 120, - 20, - 20, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.max, - children: [ - widget.showAppBar - ? const SizedBox.shrink() - : Text( - widget.title ?? "Recovery key", - style: Theme.of(context).textTheme.headline4, + appBar: widget.showProgressBar + ? AppBar( + elevation: 0, + title: Hero( + tag: "recovery_key", + child: StepProgressIndicator( + totalSteps: 4, + currentStep: 3, + selectedColor: + Theme.of(context).colorScheme.greenAlternative, + roundedEdges: const Radius.circular(10), + unselectedColor: Theme.of(context) + .colorScheme + .stepProgressUnselectedColor, ), - Padding(padding: EdgeInsets.all(widget.showAppBar ? 0 : 12)), - Text( - widget.text ?? - "If you forget your password, the only way you can recover your data is with this key.", - style: Theme.of(context).textTheme.subtitle1, - ), - const Padding(padding: EdgeInsets.only(top: 24)), - DottedBorder( - color: const Color.fromRGBO(17, 127, 56, 1), - //color of dotted/dash line - strokeWidth: 1, - //thickness of dash/dots - dashPattern: const [6, 6], - radius: const Radius.circular(8), - //dash patterns, 10 is dash width, 6 is space width - child: SizedBox( - //inner container - height: 120, //height of inner container - width: - double.infinity, //width to 100% match to parent container. - // ignore: prefer_const_literals_to_create_immutables - child: Column( - children: [ - GestureDetector( - onTap: () async { - await Clipboard.setData( - ClipboardData(text: recoveryKey), - ); - showToast(context, "Recovery key copied to clipboard"); - setState(() { - _hasTriedToSave = true; - }); - }, - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: const Color.fromRGBO(49, 155, 86, .2), - ), - borderRadius: const BorderRadius.all( - Radius.circular(2), - ), - color: - Theme.of(context).colorScheme.recoveryKeyBoxColor, + ), + ) + : widget.showAppBar + ? AppBar( + elevation: 0, + title: Text(widget.title ?? "Recovery key"), + ) + : null, + body: Padding( + padding: EdgeInsets.fromLTRB(20, topPadding, 20, 20), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: constraints.maxWidth, + minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + widget.showAppBar + ? const SizedBox.shrink() + : Text( + widget.title ?? "Recovery key", + style: Theme.of(context).textTheme.headline4, + ), + Padding( + padding: + EdgeInsets.all(widget.showAppBar ? 0 : 12)), + Text( + widget.text ?? + "If you forget your password, the only way you can recover your data is with this key.", + style: Theme.of(context).textTheme.subtitle1, ), - height: 120, - padding: const EdgeInsets.all(20), - width: double.infinity, - child: Text( - recoveryKey, - style: Theme.of(context).textTheme.bodyText1, + const Padding(padding: EdgeInsets.only(top: 24)), + DottedBorder( + color: const Color.fromRGBO(17, 127, 56, 1), + //color of dotted/dash line + strokeWidth: 1, + //thickness of dash/dots + dashPattern: const [6, 6], + radius: const Radius.circular(8), + //dash patterns, 10 is dash width, 6 is space width + child: SizedBox( + //inner container + // height: 120, //height of inner container + width: double + .infinity, //width to 100% match to parent container. + // ignore: prefer_const_literals_to_create_immutables + child: Column( + children: [ + GestureDetector( + onTap: () async { + await Clipboard.setData( + ClipboardData(text: recoveryKey), + ); + showToast(context, + "Recovery key copied to clipboard"); + setState(() { + _hasTriedToSave = true; + }); + }, + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: const Color.fromRGBO( + 49, 155, 86, .2), + ), + borderRadius: const BorderRadius.all( + Radius.circular(2), + ), + color: Theme.of(context) + .colorScheme + .recoveryKeyBoxColor, + ), + padding: const EdgeInsets.all(20), + width: double.infinity, + child: Text( + recoveryKey, + style: + Theme.of(context).textTheme.bodyText1, + ), + ), + ), + ], + ), + ), ), - ), - ), - ], + SizedBox( + height: 80, + width: double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Text( + widget.subText ?? + "We don’t store this key, please save this in a safe place.", + style: Theme.of(context).textTheme.bodyText1, + ), + ), + ), + Expanded( + child: Container( + alignment: Alignment.bottomCenter, + width: double.infinity, + padding: const EdgeInsets.fromLTRB(10, 10, 10, 42), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _saveOptions(context, recoveryKey), + ), + ), + ) + ], + ), // columnEnds + ), ), - ), - ), - SizedBox( - height: 80, - width: double.infinity, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: Text( - widget.subText ?? - "We don’t store this key, please save this in a safe place.", - style: Theme.of(context).textTheme.bodyText1, - ), - ), - ), - Expanded( - child: Container( - alignment: Alignment.bottomCenter, - width: double.infinity, - padding: const EdgeInsets.fromLTRB(10, 10, 10, 24), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _saveOptions(context, recoveryKey), - ), - ), - ) - ], - ), - ), - ); + ); + }, + ), + )); } List _saveOptions(BuildContext context, String recoveryKey) { From 43becc9be056b5fae61cb1f7a0808240b1e3e4be Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Sat, 17 Sep 2022 14:32:58 +0530 Subject: [PATCH 02/15] Remove open_file from PlayStore --- lib/ui/settings/app_update_dialog.dart | 4 ++-- pubspec.lock | 2 +- pubspec.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ui/settings/app_update_dialog.dart b/lib/ui/settings/app_update_dialog.dart index af6f5f939..b21f0dcd9 100644 --- a/lib/ui/settings/app_update_dialog.dart +++ b/lib/ui/settings/app_update_dialog.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; -import 'package:open_file/open_file.dart'; +// import 'package:open_file/open_file.dart'; import 'package:photos/core/configuration.dart'; import 'package:photos/core/network.dart'; import 'package:photos/ente_theme_data.dart'; @@ -152,7 +152,7 @@ class _ApkDownloaderDialogState extends State { }, ); Navigator.of(context, rootNavigator: true).pop('dialog'); - OpenFile.open(_saveUrl); + // OpenFile.open(_saveUrl); } catch (e) { Logger("ApkDownloader").severe(e); final AlertDialog alert = AlertDialog( diff --git a/pubspec.lock b/pubspec.lock index 7a3001ae9..061b3b37d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -796,7 +796,7 @@ packages: source: hosted version: "1.0.2" open_file: - dependency: "direct main" + dependency: transitive description: name: open_file url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 1cc5459d4..f17caf6ee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -82,7 +82,7 @@ dependencies: git: "https://github.com/ente-io/motionphoto.git" move_to_background: ^1.0.2 - open_file: ^3.2.1 + # open_file: ^3.2.1 open_mail_app: ^0.4.5 package_info_plus: ^1.0.1 page_transition: ^2.0.2 From 43cbb96e0c7ff8ee47a15ceae27a3b6b09ea6b74 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Sat, 17 Sep 2022 16:02:13 +0530 Subject: [PATCH 03/15] Remove alice --- lib/app.dart | 3 - lib/core/network.dart | 8 --- lib/ui/settings/app_version_widget.dart | 2 - lib/ui/settings/debug_section_widget.dart | 11 ---- pubspec.lock | 72 ----------------------- pubspec.yaml | 2 - 6 files changed, 98 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 919b0df23..27623b087 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -10,7 +10,6 @@ import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:logging/logging.dart'; -import 'package:photos/core/network.dart'; import 'package:photos/ente_theme_data.dart'; import 'package:photos/l10n/l10n.dart'; import 'package:photos/services/app_lifecycle_service.dart'; @@ -58,7 +57,6 @@ class _EnteAppState extends State with WidgetsBindingObserver { darkTheme: dartTheme, home: EnteApp._homeWidget, debugShowCheckedModeBanner: false, - navigatorKey: Network.instance.getAlice().getNavigatorKey(), builder: EasyLoading.init(), supportedLocales: L10n.all, localizationsDelegates: const [ @@ -77,7 +75,6 @@ class _EnteAppState extends State with WidgetsBindingObserver { darkTheme: darkThemeData, home: EnteApp._homeWidget, debugShowCheckedModeBanner: false, - navigatorKey: Network.instance.getAlice().getNavigatorKey(), builder: EasyLoading.init(), supportedLocales: L10n.all, localizationsDelegates: const [ diff --git a/lib/core/network.dart b/lib/core/network.dart index a6a7d4d2a..aef529db3 100644 --- a/lib/core/network.dart +++ b/lib/core/network.dart @@ -1,11 +1,8 @@ // @dart=2.9 import 'dart:io'; - -import 'package:alice/alice.dart'; import 'package:dio/dio.dart'; import 'package:fk_user_agent/fk_user_agent.dart'; -import 'package:flutter/foundation.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:uuid/uuid.dart'; @@ -13,10 +10,8 @@ int kConnectTimeout = 15000; class Network { Dio _dio; - Alice _alice; Future init() async { - _alice = Alice(darkTheme: true, showNotification: kDebugMode); await FkUserAgent.init(); final packageInfo = await PackageInfo.fromPlatform(); _dio = Dio( @@ -30,7 +25,6 @@ class Network { ), ); _dio.interceptors.add(RequestIdInterceptor()); - _dio.interceptors.add(_alice.getDioInterceptor()); } Network._privateConstructor(); @@ -38,8 +32,6 @@ class Network { static Network instance = Network._privateConstructor(); Dio getDio() => _dio; - - Alice getAlice() => _alice; } class RequestIdInterceptor extends Interceptor { diff --git a/lib/ui/settings/app_version_widget.dart b/lib/ui/settings/app_version_widget.dart index 25ea8a931..757b4dc8e 100644 --- a/lib/ui/settings/app_version_widget.dart +++ b/lib/ui/settings/app_version_widget.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:photos/core/network.dart'; import 'package:photos/utils/dialog_util.dart'; class AppVersionWidget extends StatefulWidget { @@ -38,7 +37,6 @@ class _AppVersionWidgetState extends State { const Duration(milliseconds: kDummyDelayDurationInMilliseconds), ); await dialog.hide(); - Network.instance.getAlice().showInspector(); } } else { _consecutiveTaps = 1; diff --git a/lib/ui/settings/debug_section_widget.dart b/lib/ui/settings/debug_section_widget.dart index 2c9d48815..0743cf60c 100644 --- a/lib/ui/settings/debug_section_widget.dart +++ b/lib/ui/settings/debug_section_widget.dart @@ -4,7 +4,6 @@ import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_sodium/flutter_sodium.dart'; import 'package:photos/core/configuration.dart'; -import 'package:photos/core/network.dart'; import 'package:photos/ui/settings/common_settings.dart'; import 'package:photos/ui/settings/settings_section_title.dart'; import 'package:photos/ui/settings/settings_text_item.dart'; @@ -34,16 +33,6 @@ class DebugSectionWidget extends StatelessWidget { icon: Icons.navigate_next, ), ), - GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () async { - Network.instance.getAlice().showInspector(); - }, - child: const SettingsTextItem( - text: "Network requests", - icon: Icons.navigate_next, - ), - ) ], ); } diff --git a/pubspec.lock b/pubspec.lock index 061b3b37d..1a855fc11 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -8,15 +8,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.0" - alice: - dependency: "direct main" - description: - path: "." - ref: HEAD - resolved-ref: "42c97aebbea93d1944a199b453b41bbed1f3013a" - url: "https://github.com/jhomlala/alice.git" - source: git - version: "0.2.5" animate_do: dependency: "direct main" description: @@ -52,13 +43,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" - better_player: - dependency: transitive - description: - name: better_player - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.81" bip39: dependency: "direct main" description: @@ -115,13 +99,6 @@ packages: relative: true source: path version: "1.2.2" - chopper: - dependency: transitive - description: - name: chopper - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.5" clock: dependency: transitive description: @@ -562,13 +539,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_widget_from_html_core: - dependency: transitive - description: - name: flutter_widget_from_html_core - url: "https://pub.dartlang.org" - source: hosted - version: "0.8.5+3" flutter_windowmanager: dependency: "direct main" description: @@ -590,13 +560,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "8.0.9" - fwfh_text_style: - dependency: transitive - description: - name: fwfh_text_style - url: "https://pub.dartlang.org" - source: hosted - version: "2.7.3+2" google_nav_bar: dependency: "direct main" description: @@ -795,13 +758,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" - open_file: - dependency: transitive - description: - name: open_file - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.1" open_mail_app: dependency: "direct main" description: @@ -942,20 +898,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.11.1" - permission_handler: - dependency: transitive - description: - name: permission_handler - url: "https://pub.dartlang.org" - source: hosted - version: "8.3.0" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "3.7.0" petitparser: dependency: transitive description: @@ -1047,13 +989,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.3" - sensors: - dependency: transitive - description: - name: sensors - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.3" sentry: dependency: "direct main" description: @@ -1068,13 +1003,6 @@ packages: relative: true source: path version: "6.5.1" - share: - dependency: transitive - description: - name: share - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" share_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f17caf6ee..4c63d2c90 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,8 +18,6 @@ environment: dependencies: adaptive_theme: ^3.1.0 - alice: - git: "https://github.com/jhomlala/alice.git" animate_do: ^2.0.0 archive: ^3.1.2 background_fetch: ^1.0.1 From 9dd2ab5eb13f9fb8a8afd85b5a43a6645fbdb8b3 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Sat, 17 Sep 2022 16:09:44 +0530 Subject: [PATCH 04/15] v0.6.32 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 4c63d2c90..472d33c03 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: ente photos application # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.31+361 +version: 0.6.32+362 environment: sdk: '>=2.12.0 <3.0.0' From c45881a170c4a95e1e676771f52e7a288b59d601 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 18 Sep 2022 19:13:01 +0530 Subject: [PATCH 05/15] iOS proj update after pubspec.yaml changes --- ios/Podfile.lock | 59 ---------------------------- ios/Runner.xcodeproj/project.pbxproj | 18 --------- 2 files changed, 77 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 381a73b8d..29a79037f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,13 +1,6 @@ PODS: - background_fetch (1.1.0): - Flutter - - better_player (0.0.1): - - Cache (~> 6.0.0) - - Flutter - - GCDWebServer - - HLSCachingReverseProxyServer - - PINCache - - Cache (6.0.0) - connectivity (0.0.1): - Flutter - Reachability @@ -83,9 +76,6 @@ PODS: - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) - - GCDWebServer (3.5.4): - - GCDWebServer/Core (= 3.5.4) - - GCDWebServer/Core (3.5.4) - GoogleDataTransport (9.1.4): - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) @@ -107,9 +97,6 @@ PODS: - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (7.7.0): - GoogleUtilities/Logger - - HLSCachingReverseProxyServer (0.1.0): - - GCDWebServer (~> 3.5) - - PINCache (>= 3.0.1-beta.3) - image_editor (0.0.1): - Flutter - in_app_purchase (0.0.1): @@ -137,8 +124,6 @@ PODS: - nanopb/encode (= 2.30908.0) - nanopb/decode (2.30908.0) - nanopb/encode (2.30908.0) - - open_file (0.0.1): - - Flutter - open_mail_app (0.0.1): - Flutter - OrderedSet (5.0.0) @@ -146,19 +131,9 @@ PODS: - Flutter - path_provider_ios (0.0.1): - Flutter - - "permission_handler (5.1.0+2)": - - Flutter - photo_manager (2.0.0): - Flutter - FlutterMacOS - - PINCache (3.0.3): - - PINCache/Arc-exception-safe (= 3.0.3) - - PINCache/Core (= 3.0.3) - - PINCache/Arc-exception-safe (3.0.3): - - PINCache/Core - - PINCache/Core (3.0.3): - - PINOperation (~> 1.2.1) - - PINOperation (1.2.1) - PromisesObjC (2.1.0) - Reachability (3.2) - receive_sharing_intent (0.0.1): @@ -169,8 +144,6 @@ PODS: - SDWebImageWebPCoder (0.8.4): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.10) - - sensors (0.0.1): - - Flutter - Sentry (7.11.0): - Sentry/Core (= 7.11.0) - Sentry/Core (7.11.0) @@ -178,8 +151,6 @@ PODS: - Flutter - FlutterMacOS - Sentry (~> 7.11.0) - - share (0.0.1): - - Flutter - share_plus (0.0.1): - Flutter - shared_preferences_ios (0.0.1): @@ -202,7 +173,6 @@ PODS: DEPENDENCIES: - background_fetch (from `.symlinks/plugins/background_fetch/ios`) - - better_player (from `.symlinks/plugins/better_player/ios`) - connectivity (from `.symlinks/plugins/connectivity/ios`) - device_info (from `.symlinks/plugins/device_info/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) @@ -224,16 +194,12 @@ DEPENDENCIES: - local_auth (from `.symlinks/plugins/local_auth/ios`) - motionphoto (from `.symlinks/plugins/motionphoto/ios`) - move_to_background (from `.symlinks/plugins/move_to_background/ios`) - - open_file (from `.symlinks/plugins/open_file/ios`) - open_mail_app (from `.symlinks/plugins/open_mail_app/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - - permission_handler (from `.symlinks/plugins/permission_handler/ios`) - photo_manager (from `.symlinks/plugins/photo_manager/ios`) - receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`) - - sensors (from `.symlinks/plugins/sensors/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - - share (from `.symlinks/plugins/share/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) @@ -245,23 +211,18 @@ DEPENDENCIES: SPEC REPOS: trunk: - - Cache - Firebase - FirebaseCore - FirebaseCoreDiagnostics - FirebaseInstallations - FirebaseMessaging - FMDB - - GCDWebServer - GoogleDataTransport - GoogleUtilities - - HLSCachingReverseProxyServer - libwebp - Mantle - nanopb - OrderedSet - - PINCache - - PINOperation - PromisesObjC - Reachability - SDWebImage @@ -272,8 +233,6 @@ SPEC REPOS: EXTERNAL SOURCES: background_fetch: :path: ".symlinks/plugins/background_fetch/ios" - better_player: - :path: ".symlinks/plugins/better_player/ios" connectivity: :path: ".symlinks/plugins/connectivity/ios" device_info: @@ -316,26 +275,18 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/motionphoto/ios" move_to_background: :path: ".symlinks/plugins/move_to_background/ios" - open_file: - :path: ".symlinks/plugins/open_file/ios" open_mail_app: :path: ".symlinks/plugins/open_mail_app/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_ios: :path: ".symlinks/plugins/path_provider_ios/ios" - permission_handler: - :path: ".symlinks/plugins/permission_handler/ios" photo_manager: :path: ".symlinks/plugins/photo_manager/ios" receive_sharing_intent: :path: ".symlinks/plugins/receive_sharing_intent/ios" - sensors: - :path: ".symlinks/plugins/sensors/ios" sentry_flutter: :path: ".symlinks/plugins/sentry_flutter/ios" - share: - :path: ".symlinks/plugins/share/ios" share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_ios: @@ -355,8 +306,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: background_fetch: 3795af8a49054dc526477cc2f60d2ed41de60587 - better_player: 2406bfe8175203c7a46fa15f9d778d73b12e1646 - Cache: 4ca7e00363fca5455f26534e5607634c820ffc2d connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d @@ -379,10 +328,8 @@ SPEC CHECKSUMS: fluttercontactpicker: d582836dea6b5d489f3d259f35d7817ae82ee5e6 fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4 GoogleDataTransport: 5fffe35792f8b96ec8d6775f5eccd83c998d5a3b GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 - HLSCachingReverseProxyServer: 59935e1e0244ad7f3375d75b5ef46e8eb26ab181 image_editor: c1d038630eedea60d2dee9c14f36aa66c7f9cfab in_app_purchase: 3e2155afa9d03d4fa32d9e62d567885080ce97d6 libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc @@ -391,24 +338,18 @@ SPEC CHECKSUMS: motionphoto: d4a432b8c8f22fb3ad966258597c0103c9c5ff16 move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 - open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d open_mail_app: 794172f6a22cd16319d3ddaf45e945b2f74952b0 OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 - permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0 photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604 - PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086 - PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20 PromisesObjC: 99b6f43f9e1044bd87a95a60beff28c2c44ddb72 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1 SDWebImage: 0905f1b7760fc8ac4198cae0036600d67478751e SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815 - sensors: 84eb7a30e47a649e4172b71d6e81be614c280336 Sentry: 0c5cd63d714187b4a39c331c1f0eb04ba7868341 sentry_flutter: efb3df2c203cd03aad255892a8d628a458656d14 - share: 0b2c3e82132f5888bccca3351c504d0003b3b410 share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 2364db665..8e04b259b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -256,20 +256,15 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Cache/Cache.framework", "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework", "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework", "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework", "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", "${BUILT_PRODUCTS_DIR}/FirebaseMessaging/FirebaseMessaging.framework", - "${BUILT_PRODUCTS_DIR}/GCDWebServer/GCDWebServer.framework", "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", - "${BUILT_PRODUCTS_DIR}/HLSCachingReverseProxyServer/HLSCachingReverseProxyServer.framework", "${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework", "${BUILT_PRODUCTS_DIR}/OrderedSet/OrderedSet.framework", - "${BUILT_PRODUCTS_DIR}/PINCache/PINCache.framework", - "${BUILT_PRODUCTS_DIR}/PINOperation/PINOperation.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", @@ -277,7 +272,6 @@ "${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework", "${BUILT_PRODUCTS_DIR}/Toast/Toast.framework", "${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework", - "${BUILT_PRODUCTS_DIR}/better_player/better_player.framework", "${BUILT_PRODUCTS_DIR}/connectivity/connectivity.framework", "${BUILT_PRODUCTS_DIR}/device_info/device_info.framework", "${BUILT_PRODUCTS_DIR}/fk_user_agent/fk_user_agent.framework", @@ -298,15 +292,12 @@ "${BUILT_PRODUCTS_DIR}/motionphoto/motionphoto.framework", "${BUILT_PRODUCTS_DIR}/move_to_background/move_to_background.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", - "${BUILT_PRODUCTS_DIR}/open_file/open_file.framework", "${BUILT_PRODUCTS_DIR}/open_mail_app/open_mail_app.framework", "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", "${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework", "${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework", "${BUILT_PRODUCTS_DIR}/receive_sharing_intent/receive_sharing_intent.framework", - "${BUILT_PRODUCTS_DIR}/sensors/sensors.framework", "${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework", - "${BUILT_PRODUCTS_DIR}/share/share.framework", "${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences_ios/shared_preferences_ios.framework", "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", @@ -318,20 +309,15 @@ ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Cache.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseMessaging.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GCDWebServer.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HLSCachingReverseProxyServer.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mantle.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OrderedSet.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PINCache.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PINOperation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", @@ -339,7 +325,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Toast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/better_player.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fk_user_agent.framework", @@ -360,15 +345,12 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/motionphoto.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/move_to_background.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_file.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_mail_app.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/receive_sharing_intent.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sensors.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", From 144e86dcbf27eb997715f5d31d723b7982800648 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 19 Sep 2022 15:46:52 +0530 Subject: [PATCH 06/15] Add UserRemoteFlag service --- lib/services/user_remote_flag_service.dart | 116 +++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 lib/services/user_remote_flag_service.dart diff --git a/lib/services/user_remote_flag_service.dart b/lib/services/user_remote_flag_service.dart new file mode 100644 index 000000000..28a7b965e --- /dev/null +++ b/lib/services/user_remote_flag_service.dart @@ -0,0 +1,116 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:logging/logging.dart'; +import 'package:photos/core/configuration.dart'; +import 'package:photos/core/network.dart'; +import 'package:photos/services/user_service.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class UserRemoteFlagService { + final _dio = Network.instance.getDio(); + final _logger = Logger((UserRemoteFlagService).toString()); + final _config = Configuration.instance; + late SharedPreferences _prefs; + + UserRemoteFlagService._privateConstructor(); + + static final UserRemoteFlagService instance = + UserRemoteFlagService._privateConstructor(); + + static const String recoveryVerificationFlag = "recoveryKeyVerified"; + static const String needRecoveryKeyVerification = + "needRecoveryKeyVerification"; + + Future init() async { + _prefs = await SharedPreferences.getInstance(); + } + + bool shouldShowRecoveryVerification() { + if (!_prefs.containsKey(needRecoveryKeyVerification)) { + // fetch the status from remote + unawaited(_refreshRecoveryVerificationFlag()); + return false; + } + return _prefs.getBool(needRecoveryKeyVerification)!; + } + + Future markRecoveryVerificationAsDone() async { + await _setBooleanFlag(recoveryVerificationFlag, true); + await _prefs.setBool(needRecoveryKeyVerification, false); + } + + Future _refreshRecoveryVerificationFlag() async { + final remoteStatusValue = await _getBooleanFlag(recoveryVerificationFlag); + if (remoteStatusValue) { + await _prefs.setBool(needRecoveryKeyVerification, false); + } else { + // check the session creationTime. If any active session is older than + // 1 day, set the need to verification as true + final activeSessions = await UserService.instance.getActiveSessions(); + final int microSecondsInADay = const Duration(days: 1).inMicroseconds; + final bool anyActiveSessionOlderThanADay = + activeSessions.sessions.firstWhere( + (e) => + (e.creationTime + microSecondsInADay) < + DateTime.now().microsecondsSinceEpoch, + orElse: () => null, + ) != + null; + if (anyActiveSessionOlderThanADay) { + await _prefs.setBool(needRecoveryKeyVerification, true); + } else { + // continue defaulting to no verification prompt + } + } + } + + Future _getBooleanFlag(String key, {bool defaultVal = false}) async { + try { + final response = await _dio.get( + _config.getHttpEndpoint() + "/users/remote-store/bool/$key", + options: Options( + headers: { + "X-Auth-Token": _config.getToken(), + }, + ), + ); + if (response.statusCode != HttpStatus.ok) { + throw Exception("Unexpected status code"); + } + return response.data["status"] ?? defaultVal; + } on DioError catch (e) { + _logger.info(e); + rethrow; + } catch (e) { + _logger.warning("marking recovery key status failed", e); + rethrow; + } + } + + // markRecoveryVerificationAsCompleted sets the corresponding flag on remote + // to mark recovery as completed + Future _setBooleanFlag(String key, bool value) async { + try { + final response = await _dio.post( + _config.getHttpEndpoint() + "/users/remote-store/bool/$key/$value", + options: Options( + headers: { + "X-Auth-Token": _config.getToken(), + }, + ), + ); + if (response.statusCode != HttpStatus.ok) { + throw Exception("Unexpected state"); + } + return true; + } on DioError catch (e) { + _logger.info(e); + return false; + } catch (e) { + _logger.warning("marking recovery key status failed", e); + return false; + } + } +} From 6b24997d93fdf62508a40baa309e8b9a0e23159d Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 20 Sep 2022 11:32:18 +0530 Subject: [PATCH 07/15] [ui-components] make warning notification widget --- lib/ente_theme_data.dart | 23 ++++++ .../notification_warning_widget.dart | 76 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 lib/ui/components/notification_warning_widget.dart diff --git a/lib/ente_theme_data.dart b/lib/ente_theme_data.dart index f70c53352..425f52517 100644 --- a/lib/ente_theme_data.dart +++ b/lib/ente_theme_data.dart @@ -363,6 +363,29 @@ extension CustomColorScheme on ColorScheme { Color get searchResultsBackgroundColor => brightness == Brightness.light ? Colors.black.withOpacity(0.32) : Colors.black.withOpacity(0.64); + + Color get fillFaint => brightness == Brightness.light + ? Colors.white.withOpacity(0.04) + : Colors.black.withOpacity(0.12); + Color get warning500 => const Color.fromRGBO(255, 101, 101, 1); + + List get shadowMenu => brightness == Brightness.light + ? [ + BoxShadow(blurRadius: 6, color: Colors.white.withOpacity(0.16)), + BoxShadow( + blurRadius: 6, + color: Colors.white.withOpacity(0.12), + offset: const Offset(0, 3), + ), + ] + : [ + BoxShadow(blurRadius: 6, color: Colors.black.withOpacity(0.50)), + BoxShadow( + blurRadius: 6, + color: Colors.black.withOpacity(0.25), + offset: const Offset(0, 3), + ), + ]; } OutlinedButtonThemeData buildOutlinedButtonThemeData({ diff --git a/lib/ui/components/notification_warning_widget.dart b/lib/ui/components/notification_warning_widget.dart new file mode 100644 index 000000000..69e59db51 --- /dev/null +++ b/lib/ui/components/notification_warning_widget.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:photos/ente_theme_data.dart'; + +class NotificationWarningWidget extends StatelessWidget { + final IconData warningIcon; + final IconData actionIcon; + final String text; + final GestureTapCallback onTap; + + const NotificationWarningWidget({ + Key? key, + required this.warningIcon, + required this.actionIcon, + required this.text, + required this.onTap, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: GestureDetector( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(8), + ), + boxShadow: Theme.of(context).colorScheme.shadowMenu, + color: Theme.of(context).colorScheme.warning500, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: Row( + children: [ + Icon( + warningIcon, + size: 36, + color: Colors.white, + ), + const SizedBox(width: 10), + Flexible( + child: Text( + text, + style: const TextStyle(height: 1.4, color: Colors.white), + textAlign: TextAlign.left, + ), + ), + const SizedBox(width: 10), + ClipOval( + child: Material( + color: Theme.of(context).colorScheme.fillFaint, + child: InkWell( + splashColor: Colors.red, // Splash color + onTap: onTap, + child: SizedBox( + width: 40, + height: 40, + child: Icon( + actionIcon, + color: Colors.white, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} From ca940e9c64bb5fc4f82f2c46b2f22737c0f9e254 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 20 Sep 2022 11:35:34 +0530 Subject: [PATCH 08/15] Add UserRemoteFlag service --- lib/main.dart | 2 ++ lib/services/user_remote_flag_service.dart | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index f2600a7e4..3a78a1f7f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -31,6 +31,7 @@ import 'package:photos/services/search_service.dart'; import 'package:photos/services/sync_service.dart'; import 'package:photos/services/trash_sync_service.dart'; import 'package:photos/services/update_service.dart'; +import 'package:photos/services/user_remote_flag_service.dart'; import 'package:photos/services/user_service.dart'; import 'package:photos/ui/tools/app_lock.dart'; import 'package:photos/ui/tools/lock_screen.dart'; @@ -133,6 +134,7 @@ Future _init(bool isBackground, {String via = ''}) async { await Network.instance.init(); await Configuration.instance.init(); await UserService.instance.init(); + await UserRemoteFlagService.instance.init(); await UpdateService.instance.init(); await BillingService.instance.init(); await CollectionsService.instance.init(); diff --git a/lib/services/user_remote_flag_service.dart b/lib/services/user_remote_flag_service.dart index 28a7b965e..1f1016efb 100644 --- a/lib/services/user_remote_flag_service.dart +++ b/lib/services/user_remote_flag_service.dart @@ -36,6 +36,9 @@ class UserRemoteFlagService { return _prefs.getBool(needRecoveryKeyVerification)!; } + // markRecoveryVerificationAsDone is used to track if user has verified their + // recovery key in the past or not. This helps in avoid showing the same + // prompt to the user on re-install or signing into a different device Future markRecoveryVerificationAsDone() async { await _setBooleanFlag(recoveryVerificationFlag, true); await _prefs.setBool(needRecoveryKeyVerification, false); @@ -107,10 +110,10 @@ class UserRemoteFlagService { return true; } on DioError catch (e) { _logger.info(e); - return false; + rethrow; } catch (e) { _logger.warning("marking recovery key status failed", e); - return false; + rethrow; } } } From 3d94b723c343c1cb3b1b2f1dd47c2199dba376af Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 20 Sep 2022 11:38:12 +0530 Subject: [PATCH 09/15] Show prompt to verify recovery key --- lib/ui/account/verify_recovery_page.dart | 232 +++++++++++++++++++++++ lib/ui/status_bar_widget.dart | 18 ++ 2 files changed, 250 insertions(+) create mode 100644 lib/ui/account/verify_recovery_page.dart diff --git a/lib/ui/account/verify_recovery_page.dart b/lib/ui/account/verify_recovery_page.dart new file mode 100644 index 000000000..28cf4d540 --- /dev/null +++ b/lib/ui/account/verify_recovery_page.dart @@ -0,0 +1,232 @@ +import 'dart:ui'; + +import 'package:bip39/bip39.dart' as bip39; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_sodium/flutter_sodium.dart'; +import 'package:logging/logging.dart'; +import 'package:photos/ente_theme_data.dart'; +import 'package:photos/services/local_authentication_service.dart'; +import 'package:photos/services/user_remote_flag_service.dart'; +import 'package:photos/services/user_service.dart'; +import 'package:photos/ui/account/recovery_key_page.dart'; +import 'package:photos/ui/common/dialogs.dart'; +import 'package:photos/ui/common/gradient_button.dart'; +import 'package:photos/utils/dialog_util.dart'; +import 'package:photos/utils/navigation_util.dart'; +import 'package:photos/utils/toast_util.dart'; + +class VerifyRecoveryPage extends StatefulWidget { + const VerifyRecoveryPage({Key? key}) : super(key: key); + + @override + State createState() => _VerifyRecoveryPageState(); +} + +class _VerifyRecoveryPageState extends State { + final _recoveryKey = TextEditingController(); + final Logger _logger = Logger((_VerifyRecoveryPageState).toString()); + + void _verifyRecoveryKey() async { + final dialog = createProgressDialog(context, "Verifying recovery key..."); + await dialog.show(); + try { + final String inputKey = _recoveryKey.text.trim(); + final String recoveryKey = Sodium.bin2hex( + await UserService.instance.getOrCreateRecoveryKey(context), + ); + final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey); + if (inputKey == recoveryKey || inputKey == recoveryKeyWords) { + try { + await UserRemoteFlagService.instance.markRecoveryVerificationAsDone(); + } catch (e) { + await dialog.hide(); + if (e is DioError && e.type == DioErrorType.other) { + await showErrorDialog( + context, + "No internet connection", + "Please check your internet connection and try again.", + ); + } else { + await showGenericErrorDialog(context); + } + return; + } + showToast(context, "Verification successful!"); + await dialog.hide(); + Navigator.of(context).pop(); + } else { + throw Exception("recovery key didn't match"); + } + } catch (e, s) { + _logger.severe("failed to verify recovery key", e, s); + await dialog.hide(); + const String errMessage = + "The recovery key you entered is not valid. Please make sure it " + "contains 24 words, and check the spelling of each.\n\nIf you " + "entered an older recovery code, make sure it is 64 characters long, and check each of them."; + final result = await showChoiceDialog( + context, + "Invalid key", + errMessage, + firstAction: "Try again", + secondAction: "View recovery key", + ); + if (result == DialogUserChoice.secondChoice) { + await _onViewRecoveryKeyClick(); + } + } + } + + Future _onViewRecoveryKeyClick() async { + final hasAuthenticated = + await LocalAuthenticationService.instance.requestLocalAuthentication( + context, + "Please authenticate to view your recovery key", + ); + if (hasAuthenticated) { + String recoveryKey; + try { + recoveryKey = Sodium.bin2hex( + await UserService.instance.getOrCreateRecoveryKey(context), + ); + routeToPage( + context, + RecoveryKeyPage( + recoveryKey, + "OK", + showAppBar: true, + onDone: () { + Navigator.of(context).pop(); + }, + ), + ); + } catch (e) { + showGenericErrorDialog(context); + return; + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + color: Theme.of(context).iconTheme.color, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: constraints.maxWidth, + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + children: [ + const SizedBox(height: 12), + SizedBox( + width: double.infinity, + child: Text( + 'Verify recovery key', + style: Theme.of(context).textTheme.headline5, + textAlign: TextAlign.left, + ), + ), + const SizedBox(height: 12), + Text( + "If you forget your password, your recovery key is the " + "only way to recover your photos.\n\nPlease verify that " + "you have safely backed up your 24 word recovery key by re-entering it.", + style: Theme.of(context).textTheme.subtitle2, + ), + const SizedBox(height: 8), + TextFormField( + decoration: InputDecoration( + filled: true, + hintText: "Enter your recovery key", + contentPadding: const EdgeInsets.all(20), + border: UnderlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(6), + ), + ), + style: const TextStyle( + fontSize: 14, + fontFeatures: [FontFeature.tabularFigures()], + ), + controller: _recoveryKey, + autofocus: false, + autocorrect: false, + keyboardType: TextInputType.multiline, + minLines: 4, + maxLines: null, + onChanged: (_) { + setState(() {}); + }, + ), + const SizedBox(height: 8), + Text( + "If you saved the recovery key from older app versions, you might have a 64 character recovery code instead of 24 words. You can enter that too.", + style: Theme.of(context).textTheme.caption, + ), + const SizedBox(height: 8), + Expanded( + child: Container( + alignment: Alignment.bottomCenter, + width: double.infinity, + padding: const EdgeInsets.fromLTRB(0, 12, 0, 40), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + GradientButton( + onTap: _verifyRecoveryKey, + text: "Verify", + paddingValue: 6, + iconData: Icons.shield_outlined, + ), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: Hero( + tag: "recovery_key", + child: ElevatedButton( + style: Theme.of(context) + .colorScheme + .optionalActionButtonStyle, + onPressed: _onViewRecoveryKeyClick, + child: const Text( + "View recovery key", + // same for both themes + style: TextStyle(color: Colors.black), + ), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 20) + ], + ), + ), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/ui/status_bar_widget.dart b/lib/ui/status_bar_widget.dart index 4b5806044..cbcee1fd0 100644 --- a/lib/ui/status_bar_widget.dart +++ b/lib/ui/status_bar_widget.dart @@ -8,8 +8,12 @@ import 'package:photos/ente_theme_data.dart'; import 'package:photos/events/sync_status_update_event.dart'; import 'package:photos/services/feature_flag_service.dart'; import 'package:photos/services/sync_service.dart'; +import 'package:photos/services/user_remote_flag_service.dart'; +import 'package:photos/ui/account/verify_recovery_page.dart'; +import 'package:photos/ui/components/notification_warning_widget.dart'; import 'package:photos/ui/header_error_widget.dart'; import 'package:photos/ui/viewer/search/search_widget.dart'; +import 'package:photos/utils/navigation_util.dart'; const double kContainerHeight = 36; @@ -100,6 +104,20 @@ class _StatusBarWidgetState extends State { duration: const Duration(milliseconds: 1000), child: const Divider(), ), + UserRemoteFlagService.instance.shouldShowRecoveryVerification() + ? NotificationWarningWidget( + warningIcon: Icons.gpp_maybe, + actionIcon: Icons.arrow_forward, + text: "Please ensure that you have your 24 word recovery key", + onTap: () async => { + await routeToPage( + context, + const VerifyRecoveryPage(), + forceCustomPageRoute: true, + ) + }, + ) + : const SizedBox.shrink() ], ); } From 6bab2189424850a06501b429fb9f091ee93bc058 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 20 Sep 2022 11:55:22 +0530 Subject: [PATCH 10/15] refactor --- lib/services/user_remote_flag_service.dart | 20 ++++++++------------ lib/ui/account/verify_recovery_page.dart | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/services/user_remote_flag_service.dart b/lib/services/user_remote_flag_service.dart index 1f1016efb..1325389a8 100644 --- a/lib/services/user_remote_flag_service.dart +++ b/lib/services/user_remote_flag_service.dart @@ -52,7 +52,7 @@ class UserRemoteFlagService { // check the session creationTime. If any active session is older than // 1 day, set the need to verification as true final activeSessions = await UserService.instance.getActiveSessions(); - final int microSecondsInADay = const Duration(days: 1).inMicroseconds; + final int microSecondsInADay = const Duration(minutes: 1).inMicroseconds; final bool anyActiveSessionOlderThanADay = activeSessions.sessions.firstWhere( (e) => @@ -65,6 +65,7 @@ class UserRemoteFlagService { await _prefs.setBool(needRecoveryKeyVerification, true); } else { // continue defaulting to no verification prompt + _logger.finest('No active session older than 1 day'); } } } @@ -83,18 +84,17 @@ class UserRemoteFlagService { throw Exception("Unexpected status code"); } return response.data["status"] ?? defaultVal; - } on DioError catch (e) { - _logger.info(e); - rethrow; } catch (e) { - _logger.warning("marking recovery key status failed", e); + _logger.info( + "Error while fetching bool status for $key", + ); rethrow; } } - // markRecoveryVerificationAsCompleted sets the corresponding flag on remote + // _setBooleanFlag sets the corresponding flag on remote // to mark recovery as completed - Future _setBooleanFlag(String key, bool value) async { + Future _setBooleanFlag(String key, bool value) async { try { final response = await _dio.post( _config.getHttpEndpoint() + "/users/remote-store/bool/$key/$value", @@ -107,12 +107,8 @@ class UserRemoteFlagService { if (response.statusCode != HttpStatus.ok) { throw Exception("Unexpected state"); } - return true; - } on DioError catch (e) { - _logger.info(e); - rethrow; } catch (e) { - _logger.warning("marking recovery key status failed", e); + _logger.warning("Failed to set flag for $key", e); rethrow; } } diff --git a/lib/ui/account/verify_recovery_page.dart b/lib/ui/account/verify_recovery_page.dart index 28cf4d540..7ab29b17a 100644 --- a/lib/ui/account/verify_recovery_page.dart +++ b/lib/ui/account/verify_recovery_page.dart @@ -47,7 +47,7 @@ class _VerifyRecoveryPageState extends State { "No internet connection", "Please check your internet connection and try again.", ); - } else { + // } else { await showGenericErrorDialog(context); } return; From aed002157c4650bffd2f444ddb8352df364a1941 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 20 Sep 2022 16:03:22 +0530 Subject: [PATCH 11/15] minor bug fixes --- lib/services/user_remote_flag_service.dart | 32 ++++++++++++++-------- lib/ui/account/verify_recovery_page.dart | 2 +- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/lib/services/user_remote_flag_service.dart b/lib/services/user_remote_flag_service.dart index 1325389a8..1a2bd503a 100644 --- a/lib/services/user_remote_flag_service.dart +++ b/lib/services/user_remote_flag_service.dart @@ -40,13 +40,14 @@ class UserRemoteFlagService { // recovery key in the past or not. This helps in avoid showing the same // prompt to the user on re-install or signing into a different device Future markRecoveryVerificationAsDone() async { - await _setBooleanFlag(recoveryVerificationFlag, true); + await _updateKeyValue(recoveryVerificationFlag, true.toString()); await _prefs.setBool(needRecoveryKeyVerification, false); } Future _refreshRecoveryVerificationFlag() async { - final remoteStatusValue = await _getBooleanFlag(recoveryVerificationFlag); - if (remoteStatusValue) { + final remoteStatusValue = + await _getValue(recoveryVerificationFlag, "false"); + if (remoteStatusValue.toLowerCase() == "true") { await _prefs.setBool(needRecoveryKeyVerification, false); } else { // check the session creationTime. If any active session is older than @@ -70,10 +71,15 @@ class UserRemoteFlagService { } } - Future _getBooleanFlag(String key, {bool defaultVal = false}) async { + Future _getValue(String key, String? defaultValue) async { try { + final Map queryParams = {"key": key}; + if (defaultValue != null) { + queryParams["defaultValue"] = defaultValue; + } final response = await _dio.get( - _config.getHttpEndpoint() + "/users/remote-store/bool/$key", + _config.getHttpEndpoint() + "/remote-store", + queryParameters: queryParams, options: Options( headers: { "X-Auth-Token": _config.getToken(), @@ -81,23 +87,25 @@ class UserRemoteFlagService { ), ); if (response.statusCode != HttpStatus.ok) { - throw Exception("Unexpected status code"); + throw Exception("Unexpected status code ${response.statusCode}"); } - return response.data["status"] ?? defaultVal; + return response.data["value"]; } catch (e) { - _logger.info( - "Error while fetching bool status for $key", - ); + _logger.info("Error while fetching bool status for $key", e); rethrow; } } // _setBooleanFlag sets the corresponding flag on remote // to mark recovery as completed - Future _setBooleanFlag(String key, bool value) async { + Future _updateKeyValue(String key, String value) async { try { final response = await _dio.post( - _config.getHttpEndpoint() + "/users/remote-store/bool/$key/$value", + _config.getHttpEndpoint() + "/remote-store/update", + data: { + "key": key, + "value": value, + }, options: Options( headers: { "X-Auth-Token": _config.getToken(), diff --git a/lib/ui/account/verify_recovery_page.dart b/lib/ui/account/verify_recovery_page.dart index 7ab29b17a..28cf4d540 100644 --- a/lib/ui/account/verify_recovery_page.dart +++ b/lib/ui/account/verify_recovery_page.dart @@ -47,7 +47,7 @@ class _VerifyRecoveryPageState extends State { "No internet connection", "Please check your internet connection and try again.", ); - // } else { + } else { await showGenericErrorDialog(context); } return; From b9f426ae4af6676f954c535d24367f5a4c0861fb Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 20 Sep 2022 16:30:51 +0530 Subject: [PATCH 12/15] Fix: Refresh status to dismiss notificiation --- lib/events/notification_event.dart | 5 +++++ lib/ui/account/verify_recovery_page.dart | 7 +++++-- lib/ui/status_bar_widget.dart | 9 +++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 lib/events/notification_event.dart diff --git a/lib/events/notification_event.dart b/lib/events/notification_event.dart new file mode 100644 index 000000000..92aca5960 --- /dev/null +++ b/lib/events/notification_event.dart @@ -0,0 +1,5 @@ +import 'package:photos/events/event.dart'; + +// NotificationEvent event is used to re-fresh the UI to show latest notification +// (if any) +class NotificationEvent extends Event {} diff --git a/lib/ui/account/verify_recovery_page.dart b/lib/ui/account/verify_recovery_page.dart index 28cf4d540..7b3f4eae1 100644 --- a/lib/ui/account/verify_recovery_page.dart +++ b/lib/ui/account/verify_recovery_page.dart @@ -5,7 +5,9 @@ import 'package:dio/dio.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/ente_theme_data.dart'; +import 'package:photos/events/notification_event.dart'; import 'package:photos/services/local_authentication_service.dart'; import 'package:photos/services/user_remote_flag_service.dart'; import 'package:photos/services/user_service.dart'; @@ -52,6 +54,7 @@ class _VerifyRecoveryPageState extends State { } return; } + Bus.instance.fire(NotificationEvent()); showToast(context, "Verification successful!"); await dialog.hide(); Navigator.of(context).pop(); @@ -150,7 +153,7 @@ class _VerifyRecoveryPageState extends State { "you have safely backed up your 24 word recovery key by re-entering it.", style: Theme.of(context).textTheme.subtitle2, ), - const SizedBox(height: 8), + const SizedBox(height: 12), TextFormField( decoration: InputDecoration( filled: true, @@ -175,7 +178,7 @@ class _VerifyRecoveryPageState extends State { setState(() {}); }, ), - const SizedBox(height: 8), + const SizedBox(height: 12), Text( "If you saved the recovery key from older app versions, you might have a 64 character recovery code instead of 24 words. You can enter that too.", style: Theme.of(context).textTheme.caption, diff --git a/lib/ui/status_bar_widget.dart b/lib/ui/status_bar_widget.dart index cbcee1fd0..db3bd0c43 100644 --- a/lib/ui/status_bar_widget.dart +++ b/lib/ui/status_bar_widget.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/ente_theme_data.dart'; +import 'package:photos/events/notification_event.dart'; import 'package:photos/events/sync_status_update_event.dart'; import 'package:photos/services/feature_flag_service.dart'; import 'package:photos/services/sync_service.dart'; @@ -26,6 +27,7 @@ class StatusBarWidget extends StatefulWidget { class _StatusBarWidgetState extends State { StreamSubscription _subscription; + StreamSubscription _notificationSubscription; bool _showStatus = false; bool _showErrorBanner = false; @@ -56,12 +58,19 @@ class _StatusBarWidgetState extends State { }); } }); + _notificationSubscription = + Bus.instance.on().listen((event) { + if (mounted) { + setState(() {}); + } + }); super.initState(); } @override void dispose() { _subscription.cancel(); + _notificationSubscription.cancel(); super.dispose(); } From 4c346452b931f8877e43d11ecee2f6908f212ea9 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 20 Sep 2022 16:37:02 +0530 Subject: [PATCH 13/15] show dialog instead of toast on verification --- lib/ui/account/verify_recovery_page.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/ui/account/verify_recovery_page.dart b/lib/ui/account/verify_recovery_page.dart index 7b3f4eae1..7228faf78 100644 --- a/lib/ui/account/verify_recovery_page.dart +++ b/lib/ui/account/verify_recovery_page.dart @@ -16,7 +16,6 @@ import 'package:photos/ui/common/dialogs.dart'; import 'package:photos/ui/common/gradient_button.dart'; import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/navigation_util.dart'; -import 'package:photos/utils/toast_util.dart'; class VerifyRecoveryPage extends StatefulWidget { const VerifyRecoveryPage({Key? key}) : super(key: key); @@ -55,8 +54,14 @@ class _VerifyRecoveryPageState extends State { return; } Bus.instance.fire(NotificationEvent()); - showToast(context, "Verification successful!"); await dialog.hide(); + // todo: change this as per figma once the component is ready + await showErrorDialog( + context, + "Recovery key verified", + "Great! Your recovery key is valid. Thank you for verifying.\n Please" + " remember to keep your recovery key safely backed up.", + ); Navigator.of(context).pop(); } else { throw Exception("recovery key didn't match"); From 55d39b1f050231c3a2fbf653d6d624f460969020 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 20 Sep 2022 16:43:53 +0530 Subject: [PATCH 14/15] update copy --- lib/ui/account/verify_recovery_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ui/account/verify_recovery_page.dart b/lib/ui/account/verify_recovery_page.dart index 7228faf78..65253badf 100644 --- a/lib/ui/account/verify_recovery_page.dart +++ b/lib/ui/account/verify_recovery_page.dart @@ -59,7 +59,8 @@ class _VerifyRecoveryPageState extends State { await showErrorDialog( context, "Recovery key verified", - "Great! Your recovery key is valid. Thank you for verifying.\n Please" + "Great! Your recovery key is valid. Thank you for verifying.\n" + "\nPlease" " remember to keep your recovery key safely backed up.", ); Navigator.of(context).pop(); From 113d77a456ccf71f0037ff4cc0fefd37ad48381d Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 20 Sep 2022 16:49:03 +0530 Subject: [PATCH 15/15] fix delay --- lib/services/user_remote_flag_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/user_remote_flag_service.dart b/lib/services/user_remote_flag_service.dart index 1a2bd503a..7ac0f1239 100644 --- a/lib/services/user_remote_flag_service.dart +++ b/lib/services/user_remote_flag_service.dart @@ -53,7 +53,7 @@ class UserRemoteFlagService { // check the session creationTime. If any active session is older than // 1 day, set the need to verification as true final activeSessions = await UserService.instance.getActiveSessions(); - final int microSecondsInADay = const Duration(minutes: 1).inMicroseconds; + final int microSecondsInADay = const Duration(days: 1).inMicroseconds; final bool anyActiveSessionOlderThanADay = activeSessions.sessions.firstWhere( (e) =>