From ee33c5d5291b0c4f8d648de7d7401b976dd60bfa Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 9 Nov 2021 17:26:54 +0530 Subject: [PATCH 01/16] Android: Enable auto-fill for password --- lib/ui/email_entry_page.dart | 12 ++++++++++-- lib/ui/login_page.dart | 1 + lib/ui/password_entry_page.dart | 1 + lib/ui/password_reentry_page.dart | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/ui/email_entry_page.dart b/lib/ui/email_entry_page.dart index 7be31cf73..d68b6d720 100644 --- a/lib/ui/email_entry_page.dart +++ b/lib/ui/email_entry_page.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_password_strength/flutter_password_strength.dart'; @@ -94,12 +95,13 @@ class _EmailEntryPageState extends State { strengthColors: passwordStrengthColors, ), Expanded( - child: ListView( + child: AutofillGroup (child: ListView( children: [ Padding(padding: EdgeInsets.all(40)), Padding( padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), child: TextFormField( + autofillHints: [AutofillHints.email], decoration: InputDecoration( hintText: 'email', hintStyle: TextStyle( @@ -125,6 +127,8 @@ class _EmailEntryPageState extends State { keyboardType: TextInputType.text, controller: _passwordController1, obscureText: !_password1Visible, + enableSuggestions: true, + autofillHints: [AutofillHints.newPassword], decoration: InputDecoration( hintText: "password", hintStyle: TextStyle( @@ -155,6 +159,7 @@ class _EmailEntryPageState extends State { onEditingComplete: () { _password1FocusNode.unfocus(); _password2FocusNode.requestFocus(); + TextInput.finishAutofillContext(); }, ), ), @@ -162,9 +167,11 @@ class _EmailEntryPageState extends State { Padding( padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), child: TextFormField( - keyboardType: TextInputType.text, + keyboardType: TextInputType.visiblePassword, controller: _passwordController2, obscureText: !_password2Visible, + autofillHints: [AutofillHints.newPassword], + onEditingComplete: () => TextInput.finishAutofillContext(), decoration: InputDecoration( hintText: "confirm password", hintStyle: TextStyle( @@ -228,6 +235,7 @@ class _EmailEntryPageState extends State { ), ], ), + ), ), ], ); diff --git a/lib/ui/login_page.dart b/lib/ui/login_page.dart index fb36611c7..ba8bc27e9 100644 --- a/lib/ui/login_page.dart +++ b/lib/ui/login_page.dart @@ -58,6 +58,7 @@ class _LoginPageState extends State { Padding( padding: const EdgeInsets.fromLTRB(40, 40, 40, 0), child: TextFormField( + autofillHints: [AutofillHints.email], decoration: InputDecoration( hintText: 'email', hintStyle: TextStyle( diff --git a/lib/ui/password_entry_page.dart b/lib/ui/password_entry_page.dart index c96ab0478..da4c90c84 100644 --- a/lib/ui/password_entry_page.dart +++ b/lib/ui/password_entry_page.dart @@ -135,6 +135,7 @@ class _PasswordEntryPageState extends State { Padding( padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), child: TextFormField( + autofillHints: [AutofillHints.password], decoration: InputDecoration( hintText: "password", contentPadding: EdgeInsets.all(20), diff --git a/lib/ui/password_reentry_page.dart b/lib/ui/password_reentry_page.dart index c61bb71a1..4105aa2f3 100644 --- a/lib/ui/password_reentry_page.dart +++ b/lib/ui/password_reentry_page.dart @@ -53,6 +53,7 @@ class _PasswordReentryPageState extends State { Padding( padding: const EdgeInsets.fromLTRB(60, 0, 60, 0), child: TextFormField( + autofillHints: [AutofillHints.password], decoration: InputDecoration( hintText: "enter your password", contentPadding: EdgeInsets.all(20), From 67a9dbd5b6a6cadb3a27ad4be48772149fb1d398 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 9 Nov 2021 17:30:19 +0530 Subject: [PATCH 02/16] formatting --- lib/ui/email_entry_page.dart | 259 ++++++++++++++++++----------------- 1 file changed, 130 insertions(+), 129 deletions(-) diff --git a/lib/ui/email_entry_page.dart b/lib/ui/email_entry_page.dart index d68b6d720..cae2f4f0e 100644 --- a/lib/ui/email_entry_page.dart +++ b/lib/ui/email_entry_page.dart @@ -95,146 +95,147 @@ class _EmailEntryPageState extends State { strengthColors: passwordStrengthColors, ), Expanded( - child: AutofillGroup (child: ListView( - children: [ - Padding(padding: EdgeInsets.all(40)), - Padding( - padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), - child: TextFormField( - autofillHints: [AutofillHints.email], - decoration: InputDecoration( - hintText: 'email', - hintStyle: TextStyle( - color: Colors.white30, + child: AutofillGroup( + child: ListView( + children: [ + Padding(padding: EdgeInsets.all(40)), + Padding( + padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), + child: TextFormField( + autofillHints: [AutofillHints.email], + decoration: InputDecoration( + hintText: 'email', + hintStyle: TextStyle( + color: Colors.white30, + ), + contentPadding: EdgeInsets.all(12), ), - contentPadding: EdgeInsets.all(12), + onChanged: (value) { + setState(() { + _email = value.trim(); + }); + }, + autocorrect: false, + keyboardType: TextInputType.emailAddress, + initialValue: _email, + textInputAction: TextInputAction.next, ), - onChanged: (value) { - setState(() { - _email = value.trim(); - }); - }, - autocorrect: false, - keyboardType: TextInputType.emailAddress, - initialValue: _email, - textInputAction: TextInputAction.next, ), - ), - Padding(padding: EdgeInsets.all(8)), - Padding( - padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), - child: TextFormField( - keyboardType: TextInputType.text, - controller: _passwordController1, - obscureText: !_password1Visible, - enableSuggestions: true, - autofillHints: [AutofillHints.newPassword], - decoration: InputDecoration( - hintText: "password", - hintStyle: TextStyle( - color: Colors.white30, + Padding(padding: EdgeInsets.all(8)), + Padding( + padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), + child: TextFormField( + keyboardType: TextInputType.text, + controller: _passwordController1, + obscureText: !_password1Visible, + enableSuggestions: true, + autofillHints: [AutofillHints.newPassword], + decoration: InputDecoration( + hintText: "password", + hintStyle: TextStyle( + color: Colors.white30, + ), + contentPadding: EdgeInsets.all(12), + suffixIcon: _password1InFocus + ? IconButton( + icon: Icon( + _password1Visible + ? Icons.visibility + : Icons.visibility_off, + color: Colors.white.withOpacity(0.5), + size: 20, + ), + onPressed: () { + setState(() { + _password1Visible = !_password1Visible; + }); + }, + ) + : null, ), - contentPadding: EdgeInsets.all(12), - suffixIcon: _password1InFocus - ? IconButton( - icon: Icon( - _password1Visible - ? Icons.visibility - : Icons.visibility_off, - color: Colors.white.withOpacity(0.5), - size: 20, - ), - onPressed: () { - setState(() { - _password1Visible = !_password1Visible; - }); - }, - ) - : null, + focusNode: _password1FocusNode, + onChanged: (_) { + setState(() {}); + }, + onEditingComplete: () { + _password1FocusNode.unfocus(); + _password2FocusNode.requestFocus(); + TextInput.finishAutofillContext(); + }, ), - focusNode: _password1FocusNode, - onChanged: (_) { - setState(() {}); - }, - onEditingComplete: () { - _password1FocusNode.unfocus(); - _password2FocusNode.requestFocus(); - TextInput.finishAutofillContext(); - }, ), - ), - Padding(padding: EdgeInsets.all(8)), - Padding( - padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), - child: TextFormField( - keyboardType: TextInputType.visiblePassword, - controller: _passwordController2, - obscureText: !_password2Visible, - autofillHints: [AutofillHints.newPassword], - onEditingComplete: () => TextInput.finishAutofillContext(), - decoration: InputDecoration( - hintText: "confirm password", - hintStyle: TextStyle( - color: Colors.white30, + Padding(padding: EdgeInsets.all(8)), + Padding( + padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), + child: TextFormField( + keyboardType: TextInputType.visiblePassword, + controller: _passwordController2, + obscureText: !_password2Visible, + autofillHints: [AutofillHints.newPassword], + onEditingComplete: () => TextInput.finishAutofillContext(), + decoration: InputDecoration( + hintText: "confirm password", + hintStyle: TextStyle( + color: Colors.white30, + ), + contentPadding: EdgeInsets.all(12), + suffixIcon: _password2InFocus + ? IconButton( + icon: Icon( + _password2Visible + ? Icons.visibility + : Icons.visibility_off, + color: Colors.white.withOpacity(0.5), + size: 20, + ), + onPressed: () { + setState(() { + _password2Visible = !_password2Visible; + }); + }, + ) + : null, ), - contentPadding: EdgeInsets.all(12), - suffixIcon: _password2InFocus - ? IconButton( - icon: Icon( - _password2Visible - ? Icons.visibility - : Icons.visibility_off, - color: Colors.white.withOpacity(0.5), - size: 20, - ), - onPressed: () { - setState(() { - _password2Visible = !_password2Visible; - }); - }, - ) - : null, + focusNode: _password2FocusNode, ), - focusNode: _password2FocusNode, ), - ), - Padding( - padding: EdgeInsets.all(20), - ), - _getAgreement(), - Padding(padding: EdgeInsets.all(20)), - Container( - width: double.infinity, - height: 64, - padding: const EdgeInsets.fromLTRB(80, 0, 80, 0), - child: button( - AppLocalizations.of(context).sign_up, - onPressed: _isFormValid() - ? () { - if (!isValidEmail(_email)) { - showErrorDialog(context, "invalid email", - "please enter a valid email address."); - } else if (_passwordController1.text != - _passwordController2.text) { - showErrorDialog(context, "uhm...", - "the passwords you entered don't match"); - } else if (_passwordStrength < - kPasswordStrengthThreshold) { - showErrorDialog(context, "weak password", - "the password you have chosen is too simple, please choose another one"); - } else { - _config - .setVolatilePassword(_passwordController1.text); - _config.setEmail(_email); - UserService.instance.getOtt(context, _email); + Padding( + padding: EdgeInsets.all(20), + ), + _getAgreement(), + Padding(padding: EdgeInsets.all(20)), + Container( + width: double.infinity, + height: 64, + padding: const EdgeInsets.fromLTRB(80, 0, 80, 0), + child: button( + AppLocalizations.of(context).sign_up, + onPressed: _isFormValid() + ? () { + if (!isValidEmail(_email)) { + showErrorDialog(context, "invalid email", + "please enter a valid email address."); + } else if (_passwordController1.text != + _passwordController2.text) { + showErrorDialog(context, "uhm...", + "the passwords you entered don't match"); + } else if (_passwordStrength < + kPasswordStrengthThreshold) { + showErrorDialog(context, "weak password", + "the password you have chosen is too simple, please choose another one"); + } else { + _config.setVolatilePassword( + _passwordController1.text); + _config.setEmail(_email); + UserService.instance.getOtt(context, _email); + } } - } - : null, - fontSize: 18, + : null, + fontSize: 18, + ), ), - ), - ], - ), + ], + ), ), ), ], From 5a4e2bc92713e7cfb2c8948d27b671eb74b592e0 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 10 Nov 2021 18:29:37 +0530 Subject: [PATCH 03/16] register web.ente.io as .associated-domains for webcredentials --- ios/Runner.xcodeproj/project.pbxproj | 9 +++++++-- ios/Runner/Info.plist | 19 +++++++++---------- ios/Runner/Runner.entitlements | 10 ++++++++++ 3 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 ios/Runner/Runner.entitlements diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 454a581d6..cc6d313ed 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -15,7 +15,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - DA6BE5E826B3BC8600656280 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; + DA6BE5E826B3BC8600656280 /* (null) in Resources */ = {isa = PBXBuildFile; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -49,6 +49,7 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A78E51A260432466D4C456A9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; BB097BB5EB0EEB41344338D2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + DA8D6672273BBB59007651D4 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; F82DAEEB9A7D9FD00E0FFA1E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -98,6 +99,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + DA8D6672273BBB59007651D4 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -206,7 +208,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - DA6BE5E826B3BC8600656280 /* BuildFile in Resources */, + DA6BE5E826B3BC8600656280 /* (null) in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -472,6 +474,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 11; @@ -627,6 +630,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 11; @@ -660,6 +664,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 11; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 7cda2605c..7b01822a7 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,14 +2,14 @@ - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) BGTaskSchedulerPermittedIdentifiers com.transistorsoft.fetch + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -46,10 +46,10 @@ NSAllowsArbitraryLoadsInWebContent - NSPhotoLibraryUsageDescription - Please allow access to your photos so that ente can encrypt and backup them up. NSFaceIDUsageDescription Please allow ente to lock itself with FaceID or TouchID + NSPhotoLibraryUsageDescription + Please allow access to your photos so that ente can encrypt and backup them up. UIBackgroundModes fetch @@ -58,6 +58,8 @@ LaunchScreen UIMainStoryboardFile Main + UIStatusBarHidden + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -73,8 +75,5 @@ UIViewControllerBasedStatusBarAppearance - UIStatusBarHidden - - - \ No newline at end of file + diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 000000000..b237bb6a9 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.associated-domains + + webcredentials:web.ente.io + + + From 667e0de9dd528625f72fb7b4cca3c81600c307fe Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 11 Nov 2021 14:36:14 +0530 Subject: [PATCH 04/16] Android: link digital assetLinks for auto-fill --- android/app/src/main/AndroidManifest.xml | 2 +- android/app/src/main/res/values/strings.xml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bedbc9e74..fadfce5ed 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -48,7 +48,7 @@ - + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 46022fdfb..31960f932 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,4 +1,10 @@ ente backup + + [{ + \"include\": \"https://web.ente.io/.well-known/assetlinks.json\" + }] + + From 3478ddb3ad46cab6d40acf519ed0c33d3b2b76aa Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 11 Nov 2021 16:24:52 +0530 Subject: [PATCH 05/16] attempt to save password during change --- lib/ui/password_entry_page.dart | 238 +++++++++++++++++--------------- 1 file changed, 129 insertions(+), 109 deletions(-) diff --git a/lib/ui/password_entry_page.dart b/lib/ui/password_entry_page.dart index da4c90c84..50e0dfd89 100644 --- a/lib/ui/password_entry_page.dart +++ b/lib/ui/password_entry_page.dart @@ -92,6 +92,7 @@ class _PasswordEntryPageState extends State { if (_password != null) { return Container(); } + final String email = Configuration.instance.getEmail() ?? ''; return Column( children: [ FlutterPasswordStrength( @@ -105,119 +106,137 @@ class _PasswordEntryPageState extends State { SingleChildScrollView( child: Container( padding: EdgeInsets.fromLTRB(16, 36, 16, 16), - child: Column( - children: [ - Padding(padding: EdgeInsets.all(12)), - Text( - "enter a" + - (widget.mode != PasswordEntryMode.set ? " new " : " ") + - "password we can use to encrypt your data", - textAlign: TextAlign.center, - style: TextStyle( - height: 1.3, + child: AutofillGroup( + child: Column( + children: [ + Padding(padding: EdgeInsets.all(12)), + Text( + "enter a" + + (widget.mode != PasswordEntryMode.set ? " new " : " ") + + "password we can use to encrypt your data", + textAlign: TextAlign.center, + style: TextStyle( + height: 1.3, + ), ), - ), - Padding(padding: EdgeInsets.all(8)), - Text("we don't store this password, so if you forget, "), - Text.rich( - TextSpan( - text: "we cannot decrypt your data", - style: TextStyle( - decoration: TextDecoration.underline, - fontWeight: FontWeight.bold, - )), - style: TextStyle( - height: 1.3, + Padding(padding: EdgeInsets.all(8)), + Text("we don't store this password, so if you forget, "), + Text.rich( + TextSpan( + text: "we cannot decrypt your data", + style: TextStyle( + decoration: TextDecoration.underline, + fontWeight: FontWeight.bold, + )), + style: TextStyle( + height: 1.3, + ), + textAlign: TextAlign.center, ), - textAlign: TextAlign.center, - ), - Padding(padding: EdgeInsets.all(12)), - Padding( - padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), - child: TextFormField( - autofillHints: [AutofillHints.password], - decoration: InputDecoration( - hintText: "password", - contentPadding: EdgeInsets.all(20), - suffixIcon: _password1InFocus - ? IconButton( - icon: Icon( - _password1Visible - ? Icons.visibility - : Icons.visibility_off, - color: Colors.white.withOpacity(0.5), - size: 20, - ), - onPressed: () { - setState(() { - _password1Visible = !_password1Visible; - }); - }, - ) + Padding(padding: EdgeInsets.all(12)), + // hidden textForm for suggesting auto-fill service for saving + // password + SizedBox( + width: 0, + height: 0, + child: TextFormField( + autofillHints: [ + AutofillHints.email, + ], + autocorrect: false, + keyboardType: TextInputType.emailAddress, + initialValue: email, + textInputAction: TextInputAction.next, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), + child: TextFormField( + autofillHints: [AutofillHints.newPassword], + decoration: InputDecoration( + hintText: "password", + contentPadding: EdgeInsets.all(20), + suffixIcon: _password1InFocus + ? IconButton( + icon: Icon( + _password1Visible + ? Icons.visibility + : Icons.visibility_off, + color: Colors.white.withOpacity(0.5), + size: 20, + ), + onPressed: () { + setState(() { + _password1Visible = !_password1Visible; + }); + }, + ) + : null, + ), + obscureText: !_password1Visible, + controller: _passwordController1, + autofocus: false, + autocorrect: false, + keyboardType: TextInputType.visiblePassword, + onChanged: (_) { + setState(() {}); + }, + textInputAction: TextInputAction.next, + focusNode: _password1FocusNode, + ), + ), + Padding(padding: EdgeInsets.all(8)), + Padding( + padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), + child: TextFormField( + autofillHints: [AutofillHints.newPassword], + decoration: InputDecoration( + hintText: "password again", + contentPadding: EdgeInsets.all(20), + suffixIcon: _password2InFocus + ? IconButton( + icon: Icon( + _password2Visible + ? Icons.visibility + : Icons.visibility_off, + color: Colors.white.withOpacity(0.5), + size: 20, + ), + onPressed: () { + setState(() { + _password2Visible = !_password2Visible; + }); + }, + ) + : null, + ), + obscureText: !_password2Visible, + controller: _passwordController2, + autofocus: false, + autocorrect: false, + keyboardType: TextInputType.visiblePassword, + onChanged: (_) { + setState(() {}); + }, + focusNode: _password2FocusNode, + ), + ), + Padding(padding: EdgeInsets.all(20)), + Container( + width: double.infinity, + height: 64, + padding: EdgeInsets.fromLTRB(40, 0, 40, 0), + child: button( + buttonText, + fontSize: 18, + onPressed: _passwordController1.text.isNotEmpty && + _passwordController2.text.isNotEmpty + ? _onButtonPress : null, ), - obscureText: !_password1Visible, - controller: _passwordController1, - autofocus: false, - autocorrect: false, - keyboardType: TextInputType.visiblePassword, - onChanged: (_) { - setState(() {}); - }, - textInputAction: TextInputAction.next, - focusNode: _password1FocusNode, ), - ), - Padding(padding: EdgeInsets.all(8)), - Padding( - padding: const EdgeInsets.fromLTRB(32, 0, 32, 0), - child: TextFormField( - decoration: InputDecoration( - hintText: "password again", - contentPadding: EdgeInsets.all(20), - suffixIcon: _password2InFocus - ? IconButton( - icon: Icon( - _password2Visible - ? Icons.visibility - : Icons.visibility_off, - color: Colors.white.withOpacity(0.5), - size: 20, - ), - onPressed: () { - setState(() { - _password2Visible = !_password2Visible; - }); - }, - ) - : null, - ), - obscureText: !_password2Visible, - controller: _passwordController2, - autofocus: false, - autocorrect: false, - keyboardType: TextInputType.visiblePassword, - onChanged: (_) { - setState(() {}); - }, - focusNode: _password2FocusNode, - ), - ), - Padding(padding: EdgeInsets.all(20)), - Container( - width: double.infinity, - height: 64, - padding: EdgeInsets.fromLTRB(40, 0, 40, 0), - child: button( - buttonText, - fontSize: 18, - onPressed: _passwordController1.text.isNotEmpty && - _passwordController2.text.isNotEmpty - ? _onButtonPress - : null, - ), - ), - ], + ], + ), ), ), ), @@ -228,7 +247,8 @@ class _PasswordEntryPageState extends State { Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) { - return WebPage("how it works", "https://ente.io/architecture"); + return WebPage( + "how it works", "https://ente.io/architecture"); }, ), ); From 750645baa8f8397fcc88a8c2abe3c882fe7d2c3e Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 11 Nov 2021 16:33:32 +0530 Subject: [PATCH 06/16] Fix typo --- ios/Runner/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 7b01822a7..04b099338 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -49,7 +49,7 @@ NSFaceIDUsageDescription Please allow ente to lock itself with FaceID or TouchID NSPhotoLibraryUsageDescription - Please allow access to your photos so that ente can encrypt and backup them up. + Please allow access to your photos so that ente can encrypt and back them up. UIBackgroundModes fetch From 52a6373cc335b7e5521a0135b14d6fbaede739a1 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 11 Nov 2021 16:34:14 +0530 Subject: [PATCH 07/16] bump version to v0.3.45 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 81672ff26..117041243 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.3.44+254 +version: 0.3.45+255 environment: sdk: ">=2.10.0 <3.0.0" From b17735376863ab61320c18caeaafac2618212f52 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 12 Nov 2021 15:28:47 +0530 Subject: [PATCH 08/16] bump version to v0.3.48 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 79a5880f2..4f383ca63 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.3.47+257 +version: 0.3.48+258 environment: sdk: ">=2.10.0 <3.0.0" From 7196ab1e6f65c13cd2fad9b3a8c93f6018a36e0f Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 9 Nov 2021 12:28:37 +0530 Subject: [PATCH 09/16] refactor: ChangeCollectionNameDialog to generic RenameDialog --- lib/ui/gallery_app_bar_widget.dart | 8 ++-- ...on_name_dialog.dart => rename_dialog.dart} | 37 +++++++++---------- 2 files changed, 21 insertions(+), 24 deletions(-) rename lib/ui/{change_collection_name_dialog.dart => rename_dialog.dart} (63%) diff --git a/lib/ui/gallery_app_bar_widget.dart b/lib/ui/gallery_app_bar_widget.dart index 22999d421..37b66c929 100644 --- a/lib/ui/gallery_app_bar_widget.dart +++ b/lib/ui/gallery_app_bar_widget.dart @@ -12,8 +12,8 @@ import 'package:photos/models/collection.dart'; import 'package:photos/models/magic_metadata.dart'; import 'package:photos/models/selected_files.dart'; import 'package:photos/services/collections_service.dart'; -import 'package:photos/ui/change_collection_name_dialog.dart'; import 'package:photos/ui/create_collection_page.dart'; +import 'package:photos/ui/rename_dialog.dart'; import 'package:photos/ui/share_collection_widget.dart'; import 'package:photos/utils/delete_file_util.dart'; import 'package:photos/utils/dialog_util.dart'; @@ -122,15 +122,15 @@ class _GalleryAppBarWidgetState extends State { if (widget.type != GalleryAppBarType.owned_collection) { return; } - final result = await showDialog( + final result = await showDialog( context: context, builder: (BuildContext context) { - return ChangeCollectionNameDialog(name: _appBarTitle); + return RenameDialog(_appBarTitle, 'album'); }, barrierColor: Colors.black.withOpacity(0.85), ); // indicates user cancelled the rename request - if (result == null) { + if (result == null || result.trim() == _appBarTitle.trim()) { return; } diff --git a/lib/ui/change_collection_name_dialog.dart b/lib/ui/rename_dialog.dart similarity index 63% rename from lib/ui/change_collection_name_dialog.dart rename to lib/ui/rename_dialog.dart index bf6f6e597..ca2095b2b 100644 --- a/lib/ui/change_collection_name_dialog.dart +++ b/lib/ui/rename_dialog.dart @@ -2,24 +2,25 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:photos/utils/dialog_util.dart'; -class ChangeCollectionNameDialog extends StatefulWidget { +class RenameDialog extends StatefulWidget { final String name; + final String type; + final int maxLength; - const ChangeCollectionNameDialog({Key key, this.name}) : super(key: key); + const RenameDialog(this.name, this.type, {Key key, this.maxLength = 100}) + : super(key: key); @override - _ChangeCollectionNameDialogState createState() => - _ChangeCollectionNameDialogState(); + _RenameDialogState createState() => _RenameDialogState(); } -class _ChangeCollectionNameDialogState - extends State { - String _newCollectionName; +class _RenameDialogState extends State { + String _newName; @override void initState() { super.initState(); - _newCollectionName = widget.name; + _newName = widget.name; } @override @@ -33,7 +34,7 @@ class _ChangeCollectionNameDialogState children: [ TextFormField( decoration: InputDecoration( - hintText: 'album name', + hintText: '${widget.type} name', hintStyle: TextStyle( color: Colors.white30, ), @@ -41,12 +42,12 @@ class _ChangeCollectionNameDialogState ), onChanged: (value) { setState(() { - _newCollectionName = value; + _newName = value; }); }, autocorrect: false, keyboardType: TextInputType.text, - initialValue: _newCollectionName, + initialValue: _newName, autofocus: true, ), ], @@ -72,21 +73,17 @@ class _ChangeCollectionNameDialogState ), ), onPressed: () { - if (_newCollectionName.trim().isEmpty) { + if (_newName.trim().isEmpty) { showErrorDialog( - context, "empty name", "album name cannot be empty"); + context, "empty name", "${widget.type} name cannot be empty"); return; } - if (_newCollectionName.trim().length > 100) { + if (_newName.trim().length > widget.maxLength) { showErrorDialog(context, "name too large", - "album name should be less than 100 characters"); + "${widget.type} name should be less than ${widget.maxLength} characters"); return; } - if (_newCollectionName.trim() == widget.name.trim()) { - Navigator.of(context).pop(null); - return; - } - Navigator.of(context).pop(_newCollectionName.trim()); + Navigator.of(context).pop(_newName.trim()); }, ), ], From f5d1a24ee7c390bc74975d28128cdfd22ce8ba49 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 9 Nov 2021 17:10:40 +0530 Subject: [PATCH 10/16] Allow changing file name via info dialog --- lib/models/file.dart | 7 +++++++ lib/models/magic_metadata.dart | 6 +++++- lib/ui/file_info_dialog.dart | 12 ++++++++++-- lib/utils/magic_util.dart | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/lib/models/file.dart b/lib/models/file.dart index b3775e91f..56d4d5a6f 100644 --- a/lib/models/file.dart +++ b/lib/models/file.dart @@ -208,6 +208,13 @@ class File { } } + String getDisplayName() { + if (pubMagicMetadata != null && pubMagicMetadata.editedName != null) { + return pubMagicMetadata.editedName; + } + return title; + } + // returns true if the file isn't available in the user's gallery bool isRemoteFile() { return localID == null && uploadedFileID != null; diff --git a/lib/models/magic_metadata.dart b/lib/models/magic_metadata.dart index 8eeb3c3f6..ecfe5befa 100644 --- a/lib/models/magic_metadata.dart +++ b/lib/models/magic_metadata.dart @@ -6,6 +6,7 @@ const kVisibilityArchive = 1; const kMagicKeyVisibility = 'visibility'; const kPubMagicKeyEditedTime = 'editedTime'; +const kPubMagicKeyEditedName = 'editedName'; class MagicMetadata { // 0 -> visible @@ -36,8 +37,9 @@ class MagicMetadata { class PubMagicMetadata { int editedTime; + String editedName; - PubMagicMetadata({this.editedTime}); + PubMagicMetadata({this.editedTime, this.editedName}); factory PubMagicMetadata.fromEncodedJson(String encodedJson) => PubMagicMetadata.fromJson(jsonDecode(encodedJson)); @@ -48,6 +50,7 @@ class PubMagicMetadata { Map toJson() { final map = {}; map[kPubMagicKeyEditedTime] = editedTime; + map[kPubMagicKeyEditedName] = editedName; return map; } @@ -55,6 +58,7 @@ class PubMagicMetadata { if (map == null) return null; return PubMagicMetadata( editedTime: map[kPubMagicKeyEditedTime], + editedName: map[kPubMagicKeyEditedName], ); } } diff --git a/lib/ui/file_info_dialog.dart b/lib/ui/file_info_dialog.dart index a6a8e48bb..751aa66da 100644 --- a/lib/ui/file_info_dialog.dart +++ b/lib/ui/file_info_dialog.dart @@ -8,6 +8,7 @@ import 'package:photos/ui/exif_info_dialog.dart'; import 'package:photos/utils/date_time_util.dart'; import 'package:photos/utils/exif_util.dart'; import 'package:photos/utils/file_util.dart'; +import 'package:photos/utils/magic_util.dart'; import 'package:photos/utils/toast_util.dart'; class FileInfoWidget extends StatefulWidget { @@ -137,7 +138,8 @@ class _FileInfoWidgetState extends State { if (_isImage && _exif != null) { items.add(_getExifWidgets(_exif)); } - if (widget.file.uploadedFileID != null && widget.file.updationTime != null) { + if (widget.file.uploadedFileID != null && + widget.file.updationTime != null) { items.addAll( [ Row( @@ -170,7 +172,13 @@ class _FileInfoWidgetState extends State { ), ); return AlertDialog( - title: Text(widget.file.title), + title: InkWell( + child: Text(widget.file.getDisplayName()), + onTap: () async { + await editFilename(context, widget.file); + setState(() {}); + }, + ), content: SingleChildScrollView( child: ListBody( children: items, diff --git a/lib/utils/magic_util.dart b/lib/utils/magic_util.dart index 7a46f5bab..b56bd3218 100644 --- a/lib/utils/magic_util.dart +++ b/lib/utils/magic_util.dart @@ -1,10 +1,13 @@ +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; +import 'package:path/path.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/events/force_reload_home_gallery_event.dart'; import 'package:photos/models/file.dart'; import 'package:photos/models/magic_metadata.dart'; import 'package:photos/services/file_magic_service.dart'; +import 'package:photos/ui/rename_dialog.dart'; import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/toast_util.dart'; @@ -41,6 +44,35 @@ Future editTime( } } +Future editFilename( + BuildContext context, + File file, +) async { + try { + final fileName = file.getDisplayName(); + final nameWithoutExt = basenameWithoutExtension(fileName); + final extName = extension(fileName); + var result = await showDialog( + context: context, + builder: (BuildContext context) { + return RenameDialog(nameWithoutExt, 'file', maxLength: 50); + }, + barrierColor: Colors.black.withOpacity(0.85), + ); + + if (result == null || result.trim() == nameWithoutExt.trim()) { + return true; + } + result = result + extName; + await _updatePublicMetadata( + context, List.of([file]), kPubMagicKeyEditedName, result); + return true; + } catch (e, s) { + showToast('something went wrong'); + return false; + } +} + Future _updatePublicMetadata( BuildContext context, List files, String key, dynamic value) async { if (files.isEmpty) { From 74b3607ba20c9668f593163df85c04075a3617d6 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 12 Nov 2021 17:02:43 +0530 Subject: [PATCH 11/16] show edit icon against the file name --- lib/ui/file_info_dialog.dart | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/ui/file_info_dialog.dart b/lib/ui/file_info_dialog.dart index 751aa66da..db18ece9d 100644 --- a/lib/ui/file_info_dialog.dart +++ b/lib/ui/file_info_dialog.dart @@ -171,14 +171,33 @@ class _FileInfoWidgetState extends State { children: _getActions(), ), ); - return AlertDialog( - title: InkWell( - child: Text(widget.file.getDisplayName()), + + Widget titleContent; + if (widget.file.uploadedFileID == null) { + titleContent = Text(widget.file.getDisplayName()); + } else { + titleContent = InkWell( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.file.getDisplayName(), + ), + Icon( + Icons.edit, + color: Colors.white.withOpacity(0.85), + ), + ], + ), onTap: () async { await editFilename(context, widget.file); setState(() {}); }, - ), + ); + } + + return AlertDialog( + title: titleContent, content: SingleChildScrollView( child: ListBody( children: items, From 25654709ce1594a7c1173a22834a3583b25a0e71 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 12 Nov 2021 17:05:30 +0530 Subject: [PATCH 12/16] hide edit name icons for files owned by others --- lib/ui/file_info_dialog.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ui/file_info_dialog.dart b/lib/ui/file_info_dialog.dart index db18ece9d..ff664c7ff 100644 --- a/lib/ui/file_info_dialog.dart +++ b/lib/ui/file_info_dialog.dart @@ -1,6 +1,7 @@ import 'package:exif/exif.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:photos/core/configuration.dart'; import 'package:photos/models/file.dart'; import 'package:photos/models/file_type.dart'; import 'package:photos/services/collections_service.dart'; @@ -173,7 +174,8 @@ class _FileInfoWidgetState extends State { ); Widget titleContent; - if (widget.file.uploadedFileID == null) { + if (widget.file.uploadedFileID == null || + widget.file.ownerID != Configuration.instance.getUserID()) { titleContent = Text(widget.file.getDisplayName()); } else { titleContent = InkWell( From 112f10cc724d9d2f31671e379ab720a63d0959e3 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 12 Nov 2021 17:06:18 +0530 Subject: [PATCH 13/16] minor refactoring --- lib/ui/file_info_dialog.dart | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/ui/file_info_dialog.dart b/lib/ui/file_info_dialog.dart index ff664c7ff..759f0749b 100644 --- a/lib/ui/file_info_dialog.dart +++ b/lib/ui/file_info_dialog.dart @@ -44,6 +44,7 @@ class _FileInfoWidgetState extends State { @override Widget build(BuildContext context) { + final file = widget.file; var items = [ Row( children: [ @@ -54,7 +55,7 @@ class _FileInfoWidgetState extends State { Padding(padding: EdgeInsets.all(4)), Text( getFormattedTime( - DateTime.fromMicrosecondsSinceEpoch(widget.file.creationTime), + DateTime.fromMicrosecondsSinceEpoch(file.creationTime), ), style: TextStyle( color: Colors.white.withOpacity(0.85), @@ -71,9 +72,9 @@ class _FileInfoWidgetState extends State { ), Padding(padding: EdgeInsets.all(4)), Text( - widget.file.deviceFolder ?? + file.deviceFolder ?? CollectionsService.instance - .getCollectionByID(widget.file.collectionID) + .getCollectionByID(file.collectionID) .name, style: TextStyle( color: Colors.white.withOpacity(0.85), @@ -98,7 +99,7 @@ class _FileInfoWidgetState extends State { Padding(padding: EdgeInsets.all(6)), ], ); - if (widget.file.localID != null && !_isImage) { + if (file.localID != null && !_isImage) { items.addAll( [ Row( @@ -109,7 +110,7 @@ class _FileInfoWidgetState extends State { ), Padding(padding: EdgeInsets.all(4)), FutureBuilder( - future: widget.file.getAsset(), + future: file.getAsset(), builder: (context, snapshot) { if (snapshot.hasData) { return Text( @@ -139,8 +140,7 @@ class _FileInfoWidgetState extends State { if (_isImage && _exif != null) { items.add(_getExifWidgets(_exif)); } - if (widget.file.uploadedFileID != null && - widget.file.updationTime != null) { + if (file.uploadedFileID != null && file.updationTime != null) { items.addAll( [ Row( @@ -151,8 +151,8 @@ class _FileInfoWidgetState extends State { ), Padding(padding: EdgeInsets.all(4)), Text( - getFormattedTime(DateTime.fromMicrosecondsSinceEpoch( - widget.file.updationTime)), + getFormattedTime( + DateTime.fromMicrosecondsSinceEpoch(file.updationTime)), style: TextStyle( color: Colors.white.withOpacity(0.85), ), @@ -174,16 +174,16 @@ class _FileInfoWidgetState extends State { ); Widget titleContent; - if (widget.file.uploadedFileID == null || - widget.file.ownerID != Configuration.instance.getUserID()) { - titleContent = Text(widget.file.getDisplayName()); + if (file.uploadedFileID == null || + file.ownerID != Configuration.instance.getUserID()) { + titleContent = Text(file.getDisplayName()); } else { titleContent = InkWell( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - widget.file.getDisplayName(), + file.getDisplayName(), ), Icon( Icons.edit, @@ -192,7 +192,7 @@ class _FileInfoWidgetState extends State { ], ), onTap: () async { - await editFilename(context, widget.file); + await editFilename(context, file); setState(() {}); }, ); From fbc5e7329ec848a9d03b04f64ed3b6b79de3a6ed Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 12 Nov 2021 23:10:56 +0530 Subject: [PATCH 14/16] Add support to show mnemonic recovery key --- lib/core/configuration.dart | 11 +++++++++++ lib/core/constants.dart | 4 ++++ lib/ui/recovery_key_dialog.dart | 25 ++++++++++++++++++++++++- lib/ui/recovery_page.dart | 9 +++++++-- pubspec.lock | 21 +++++++++++++++++++++ pubspec.yaml | 1 + 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/lib/core/configuration.dart b/lib/core/configuration.dart index e6b828222..f9cc97dfb 100644 --- a/lib/core/configuration.dart +++ b/lib/core/configuration.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io' as io; import 'dart:typed_data'; +import 'package:bip39/bip39.dart' as bip39; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_sodium/flutter_sodium.dart'; import 'package:logging/logging.dart'; @@ -28,6 +29,8 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:super_logging/super_logging.dart'; import 'package:uuid/uuid.dart'; +import 'constants.dart'; + class Configuration { Configuration._privateConstructor(); @@ -265,6 +268,14 @@ class Configuration { } Future recover(String recoveryKey) async { + // check if user has entered mnemonic code + if (recoveryKey.contains(' ')) { + if (recoveryKey.split(' ').length != kMnemonicKeyWordCount) { + throw AssertionError( + 'recovery code should have $kMnemonicKeyWordCount words'); + } + recoveryKey = bip39.mnemonicToEntropy(recoveryKey); + } final attributes = getKeyAttributes(); Uint8List masterKey; try { diff --git a/lib/core/constants.dart b/lib/core/constants.dart index ff0c6f307..5c3272036 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -20,3 +20,7 @@ const String kLivePhotoToastCounterKey = "show_live_photo_toast"; const kThumbnailDiskLoadDeferDuration = Duration(milliseconds: 40); const kThumbnailServerLoadDeferDuration = Duration(milliseconds: 80); + +// 256 bit key maps to 24 words +// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#Generating_the_mnemonic +const kMnemonicKeyWordCount = 24; diff --git a/lib/ui/recovery_key_dialog.dart b/lib/ui/recovery_key_dialog.dart index 4d460f5ea..1f9811917 100644 --- a/lib/ui/recovery_key_dialog.dart +++ b/lib/ui/recovery_key_dialog.dart @@ -1,10 +1,12 @@ import 'dart:io' as io; import 'dart:ui'; +import 'package:bip39/bip39.dart' as bip39; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:photos/core/configuration.dart'; +import 'package:photos/core/constants.dart'; import 'package:photos/utils/toast_util.dart'; import 'package:share_plus/share_plus.dart'; @@ -34,12 +36,21 @@ class RecoveryKeyDialog extends StatefulWidget { class _RecoveryKeyDialogState extends State { bool _hasTriedToSave = false; + bool _showMnenomicCode = true; final _recoveryKeyFile = io.File( Configuration.instance.getTempDirectory() + "ente-recovery-key.txt"); @override Widget build(BuildContext context) { - final recoveryKey = widget.recoveryKey; + String recoveryKey = widget.recoveryKey; + if (_showMnenomicCode) { + recoveryKey = bip39.entropyToMnemonic(recoveryKey); + if (recoveryKey.split(' ').length != kMnemonicKeyWordCount) { + throw AssertionError( + 'recovery code should have $kMnemonicKeyWordCount words'); + } + } + List actions = []; if (!_hasTriedToSave) { actions.add(TextButton( @@ -128,6 +139,17 @@ class _RecoveryKeyDialogState extends State { Text( "please save this in a safe place", ), + Padding(padding: EdgeInsets.all(8)), + CheckboxListTile( + contentPadding: EdgeInsets.zero, + controlAffinity: ListTileControlAffinity.trailing, + title: Text('show human readable key'), + value: _showMnenomicCode, + onChanged: (value) { + setState(() { + _showMnenomicCode = value; + }); + }), ], ), ), @@ -136,6 +158,7 @@ class _RecoveryKeyDialogState extends State { ); } + // author civil bicycle drop gown ship file shallow chicken hub isolate among report piece cram crumble leopard guard spike segment update fall defy achieve Future _shareRecoveryKey(String recoveryKey) async { if (_recoveryKeyFile.existsSync()) { await _recoveryKeyFile.delete(); diff --git a/lib/ui/recovery_page.dart b/lib/ui/recovery_page.dart index 19332fe7c..91c27932f 100644 --- a/lib/ui/recovery_page.dart +++ b/lib/ui/recovery_page.dart @@ -86,8 +86,13 @@ class _RecoveryPageState extends State { ); } catch (e) { await dialog.hide(); - showErrorDialog(context, "incorrect recovery key", - "the recovery key you entered is incorrect"); + String errMessage = + 'the recovery key you entered is incorrect'; + if (e is AssertionError) { + errMessage = '$errMessage : ${e.message}'; + } + showErrorDialog( + context, "incorrect recovery key", errMessage); } } : null, diff --git a/pubspec.lock b/pubspec.lock index 08b459398..a2b31d93c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -50,6 +50,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.73" + bip39: + dependency: "direct main" + description: + name: bip39 + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.6" boolean_selector: dependency: transitive description: @@ -492,6 +499,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.0.5" + hex: + dependency: transitive + description: + name: hex + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" html: dependency: transitive description: @@ -827,6 +841,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "3.4.0" process: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 79a5880f2..18243f630 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: animate_do: ^2.0.0 archive: ^3.1.2 background_fetch: ^1.0.1 + bip39: ^1.0.6 cached_network_image: ^3.0.0 chewie: path: thirdparty/chewie From 43caf154a45d031c0dc57d2ff98688c8815056e9 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 13 Nov 2021 00:27:14 +0530 Subject: [PATCH 15/16] Remove redundant comment --- lib/ui/recovery_key_dialog.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ui/recovery_key_dialog.dart b/lib/ui/recovery_key_dialog.dart index 1f9811917..c52e41c16 100644 --- a/lib/ui/recovery_key_dialog.dart +++ b/lib/ui/recovery_key_dialog.dart @@ -158,7 +158,6 @@ class _RecoveryKeyDialogState extends State { ); } - // author civil bicycle drop gown ship file shallow chicken hub isolate among report piece cram crumble leopard guard spike segment update fall defy achieve Future _shareRecoveryKey(String recoveryKey) async { if (_recoveryKeyFile.existsSync()) { await _recoveryKeyFile.delete(); From 6b8c591420231ce841c34dd5da1be55e354d376f Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 14 Nov 2021 13:04:15 +0530 Subject: [PATCH 16/16] remove option to toggle b/w mnemonic and non-mnemonic code --- lib/ui/recovery_key_dialog.dart | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/lib/ui/recovery_key_dialog.dart b/lib/ui/recovery_key_dialog.dart index 1f9811917..4e1f82974 100644 --- a/lib/ui/recovery_key_dialog.dart +++ b/lib/ui/recovery_key_dialog.dart @@ -36,21 +36,16 @@ class RecoveryKeyDialog extends StatefulWidget { class _RecoveryKeyDialogState extends State { bool _hasTriedToSave = false; - bool _showMnenomicCode = true; final _recoveryKeyFile = io.File( Configuration.instance.getTempDirectory() + "ente-recovery-key.txt"); @override Widget build(BuildContext context) { - String recoveryKey = widget.recoveryKey; - if (_showMnenomicCode) { - recoveryKey = bip39.entropyToMnemonic(recoveryKey); - if (recoveryKey.split(' ').length != kMnemonicKeyWordCount) { - throw AssertionError( - 'recovery code should have $kMnemonicKeyWordCount words'); - } + final String recoveryKey = bip39.entropyToMnemonic(widget.recoveryKey); + if (recoveryKey.split(' ').length != kMnemonicKeyWordCount) { + throw AssertionError( + 'recovery code should have $kMnemonicKeyWordCount words'); } - List actions = []; if (!_hasTriedToSave) { actions.add(TextButton( @@ -139,17 +134,6 @@ class _RecoveryKeyDialogState extends State { Text( "please save this in a safe place", ), - Padding(padding: EdgeInsets.all(8)), - CheckboxListTile( - contentPadding: EdgeInsets.zero, - controlAffinity: ListTileControlAffinity.trailing, - title: Text('show human readable key'), - value: _showMnenomicCode, - onChanged: (value) { - setState(() { - _showMnenomicCode = value; - }); - }), ], ), ),