瀏覽代碼

changes for recovery key page

Neeraj Gupta 3 年之前
父節點
當前提交
1679f8d8e7

+ 181 - 64
lib/ui/recovery_key_page.dart

@@ -1,79 +1,196 @@
-import 'package:flutter/foundation.dart';
+import 'dart:io' as io;
+import 'dart:ui';
+
+import 'package:bip39/bip39.dart' as bip39;
+import 'package:dotted_border/dotted_border.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
 import 'package:photos/core/configuration.dart';
-import 'package:photos/ui/settings/account_section_widget.dart';
-import 'package:photos/ui/settings/app_version_widget.dart';
-import 'package:photos/ui/settings/backup_section_widget.dart';
-import 'package:photos/ui/settings/danger_section_widget.dart';
-import 'package:photos/ui/settings/debug_section_widget.dart';
-import 'package:photos/ui/settings/details_section_widget.dart';
-import 'package:photos/ui/settings/info_section_widget.dart';
-import 'package:photos/ui/settings/security_section_widget.dart';
-import 'package:photos/ui/settings/social_section_widget.dart';
-import 'package:photos/ui/settings/support_section_widget.dart';
-import 'package:photos/ui/settings/theme_switch_widget.dart';
-import 'package:photos/utils/dialog_util.dart';
+import 'package:photos/core/constants.dart';
+import 'package:photos/ui/common/custom_color_scheme.dart';
+import 'package:share_plus/share_plus.dart';
+
+class RecoveryKeyPage extends StatefulWidget {
+  final bool showAppBar;
+  final String recoveryKey;
+  final String doneText;
+  final Function() onDone;
+  final bool isDismissible;
+  final String title;
+  final String text;
+  final String subText;
+
+  const RecoveryKeyPage(this.recoveryKey, this.doneText,
+      {Key key,
+      this.showAppBar,
+      this.onDone,
+      this.isDismissible,
+      this.title,
+      this.text,
+      this.subText})
+      : super(key: key);
+
+  @override
+  _RecoveryKeyPageState createState() => _RecoveryKeyPageState();
+}
 
-class RecoveryKeyPage extends StatelessWidget {
-  const RecoveryKeyPage({Key key}) : super(key: key);
+class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
+  bool _hasTriedToSave = false;
+  final _recoveryKeyFile = io.File(
+      Configuration.instance.getTempDirectory() + "ente-recovery-key.txt");
+  final _recoveryKey = TextEditingController();
 
   @override
   Widget build(BuildContext context) {
+    final String recoveryKey = bip39.entropyToMnemonic(widget.recoveryKey);
+    if (recoveryKey.split(' ').length != kMnemonicKeyWordCount) {
+      throw AssertionError(
+          'recovery code should have $kMnemonicKeyWordCount words');
+    }
+
     return Scaffold(
-      body: _getBody(context),
+      appBar: widget.showAppBar
+          ? AppBar(
+              title: Text(""),
+            )
+          : null,
+      body: Padding(
+        padding: const EdgeInsets.fromLTRB(20, 40, 20, 20),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          // mainAxisAlignment: MainAxisAlignment.center,
+
+          mainAxisSize: MainAxisSize.max,
+          children: [
+            Text(widget.title ?? "Recovery Key",
+                style: Theme.of(context).textTheme.headline4),
+            Padding(padding: EdgeInsets.all(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,
+            ),
+            Padding(padding: EdgeInsets.only(top: 24)),
+            DottedBorder(
+              color: Color.fromRGBO(17, 127, 56, 1),
+              //color of dotted/dash line
+              strokeWidth: 1,
+              //thickness of dash/dots
+              dashPattern: const [6, 6],
+              radius: Radius.circular(8),
+              //dash patterns, 10 is dash width, 6 is space width
+              child: SizedBox(
+                //inner container
+                height: 200, //height of inner container
+                width:
+                    double.infinity, //width to 100% match to parent container.
+                // ignore: prefer_const_literals_to_create_immutables
+                child: Column(
+                  children: [
+                    Container(
+                      decoration: BoxDecoration(
+                        border: Border.all(
+                          color: Color.fromRGBO(49, 155, 86, .2),
+                        ),
+                        borderRadius: BorderRadius.all(
+                          Radius.circular(12),
+                        ),
+                        color: Color.fromRGBO(49, 155, 86, .2),
+                      ),
+                      // color: Color.fromRGBO(49, 155, 86, .2),
+                      height: 120,
+                      padding: EdgeInsets.all(20),
+                      width: double.infinity,
+                      child: Text(
+                        recoveryKey,
+                        style: Theme.of(context).textTheme.bodyText1,
+                      ),
+                    ),
+                    SizedBox(
+                      height: 80,
+                      width: double.infinity,
+                      child: Padding(
+                          child: Text(
+                            widget.subText ??
+                                "we don’t store this key, please save this in a safe place",
+                            style: Theme.of(context).textTheme.bodyText1,
+                          ),
+                          padding: EdgeInsets.all(20)),
+                    ),
+                  ],
+                ),
+              ),
+            ),
+            Expanded(
+              child: Container(
+                alignment: Alignment.bottomCenter,
+                width: double.infinity,
+                padding: EdgeInsets.fromLTRB(10, 10, 10, 24),
+                child: Column(
+                    mainAxisAlignment: MainAxisAlignment.end,
+                    crossAxisAlignment: CrossAxisAlignment.stretch,
+                    children: _saveOptions(context, recoveryKey)),
+              ),
+            )
+          ],
+        ),
+      ),
     );
   }
 
-  Widget _getBody(BuildContext context) {
-    final hasLoggedIn = Configuration.instance.getToken() != null;
-    final List<Widget> contents = [];
-    contents.add(Row(
-        mainAxisAlignment: MainAxisAlignment.spaceAround,
-        crossAxisAlignment: CrossAxisAlignment.center,
-        children: const [ThemeSwitchWidget()]));
-    final sectionDivider = Divider(
-      height: 10,
-      color: Theme.of(context).colorScheme.onSurface.withOpacity(0.4),
-    );
-    if (hasLoggedIn) {
-      contents.addAll([
-        DetailsSectionWidget(),
-        sectionDivider,
-        BackupSectionWidget(),
-        sectionDivider,
-        AccountSectionWidget(),
-        sectionDivider,
-      ]);
+  List<Widget> _saveOptions(BuildContext context, String recoveryKey) {
+    List<Widget> childrens = [];
+    if (!_hasTriedToSave) {
+      childrens.add(ElevatedButton(
+        child: Text('Save Later'),
+        style: Theme.of(context).colorScheme.optionalActionButtonStyle,
+        onPressed: () async {
+          await _saveKeys();
+        },
+      ));
+      childrens.add(SizedBox(height: 10));
     }
-    contents.addAll([
-      SecuritySectionWidget(),
-      sectionDivider,
-      test(),
-      sectionDivider,
-      SupportSectionWidget(),
-      sectionDivider,
-      SocialSectionWidget(),
-      sectionDivider,
-      InfoSectionWidget(),
-    ]);
-    if (hasLoggedIn) {
-      contents.addAll([
-        sectionDivider,
-        DangerSectionWidget(),
-      ]);
+
+    childrens.add(ElevatedButton(
+      child: Text('Save'),
+      style: Theme.of(context).colorScheme.primaryActionButtonStyle,
+      onPressed: () async {
+        await _shareRecoveryKey(recoveryKey);
+      },
+    ));
+    if (_hasTriedToSave) {
+      childrens.add(SizedBox(height: 10));
+      childrens.add(ElevatedButton(
+        child: Text(widget.doneText),
+        // style: Theme.of(context).colorScheme.primaryActionButtonStyle,
+        onPressed: () async {
+          await _saveKeys();
+        },
+      ));
     }
-    contents.add(AppVersionWidget());
-    if (kDebugMode && hasLoggedIn) {
-      contents.add(DebugSectionWidget());
+    childrens.add(SizedBox(height: 12));
+    return childrens;
+  }
+
+  Future _shareRecoveryKey(String recoveryKey) async {
+    if (_recoveryKeyFile.existsSync()) {
+      await _recoveryKeyFile.delete();
     }
-    return SingleChildScrollView(
-      child: Padding(
-        padding: const EdgeInsets.all(12.0),
-        child: Column(
-          children: contents,
-        ),
-      ),
-    );
+    _recoveryKeyFile.writeAsStringSync(recoveryKey);
+    await Share.shareFiles([_recoveryKeyFile.path]);
+    Future.delayed(Duration(milliseconds: 500), () {
+      if (mounted) {
+        setState(() {
+          _hasTriedToSave = true;
+        });
+      }
+    });
+  }
+
+  Future<void> _saveKeys() async {
+    Navigator.of(context).pop();
+    if (_recoveryKeyFile.existsSync()) {
+      await _recoveryKeyFile.delete();
+    }
+    widget.onDone();
   }
 }

+ 99 - 64
lib/ui/recovery_page.dart

@@ -6,11 +6,9 @@ import 'package:photos/ui/common_elements.dart';
 import 'package:photos/ui/password_entry_page.dart';
 import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/toast_util.dart';
-import 'package:dotted_border/dotted_border.dart';
 
 class RecoveryPage extends StatefulWidget {
-  final bool showAppBar;
-  const RecoveryPage({Key key, @required this.showAppBar}) : super(key: key);
+  const RecoveryPage({Key key}) : super(key: key);
 
   @override
   _RecoveryPageState createState() => _RecoveryPageState();
@@ -22,71 +20,108 @@ class _RecoveryPageState extends State<RecoveryPage> {
   @override
   Widget build(BuildContext context) {
     return Scaffold(
-      appBar: widget.showAppBar
-          ? AppBar(
-              title: Text(""),
-            )
-          : null,
-      body: Padding(
-        padding: const EdgeInsets.fromLTRB(20, 40, 20, 20),
-        child: Column(
-          crossAxisAlignment: CrossAxisAlignment.stretch,
-          // mainAxisAlignment: MainAxisAlignment.center,
-
-          mainAxisSize: MainAxisSize.max,
-          children: [
-            Text("Recovery Key", style: Theme.of(context).textTheme.headline4),
-            Padding(padding: EdgeInsets.all(12)),
-            Text(
-              "If you forget your password, the only way you can recover your data is with this key.",
-              style: Theme.of(context).textTheme.subtitle1,
+      appBar: AppBar(
+        title: Text(
+          "recover account",
+          style: TextStyle(
+            fontSize: 18,
+          ),
+        ),
+      ),
+      body: Column(
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        mainAxisAlignment: MainAxisAlignment.center,
+        mainAxisSize: MainAxisSize.max,
+        children: [
+          Padding(
+            padding: const EdgeInsets.fromLTRB(60, 0, 60, 0),
+            child: TextFormField(
+              decoration: InputDecoration(
+                hintText: "enter your recovery key",
+                contentPadding: EdgeInsets.all(20),
+              ),
+              style: TextStyle(
+                fontSize: 14,
+                fontFeatures: [FontFeature.tabularFigures()],
+              ),
+              controller: _recoveryKey,
+              autofocus: false,
+              autocorrect: false,
+              keyboardType: TextInputType.multiline,
+              maxLines: null,
+              onChanged: (_) {
+                setState(() {});
+              },
             ),
-            Padding(padding: EdgeInsets.only(top: 24)),
-            DottedBorder(
-              color: Color.fromRGBO(17, 127, 56, 1), //color of dotted/dash line
-              strokeWidth: 1, //thickness of dash/dots
-              dashPattern: const [6, 6],
-              radius: Radius.circular(8),
-              //dash patterns, 10 is dash width, 6 is space width
-              child: SizedBox(
-                //inner container
-                height: 200, //height of inner container
-                width:
-                    double.infinity, //width to 100% match to parent container.
-                // ignore: prefer_const_literals_to_create_immutables
-                child: Column(
-                  children: [
-                    Container(
-                      decoration: BoxDecoration(
-                        border: Border.all(
-                          color: Color.fromRGBO(49, 155, 86, .2),
-                        ),
-                        borderRadius: BorderRadius.all(
-                          Radius.circular(12),
-                        ),
-                        color: Color.fromRGBO(49, 155, 86, .2),
-                      ),
-                      // color: Color.fromRGBO(49, 155, 86, .2),
-                      height: 120,
-                      width: double.infinity,
-                      child: const Text('1'),
-                    ),
-                    SizedBox(
-                      height: 80,
-                      width: double.infinity,
-                      child: Padding(
-                          child: Text(
-                            "we don’t store this key, please save this in a safe place",
-                            style: Theme.of(context).textTheme.bodyText1,
+          ),
+          Padding(padding: EdgeInsets.all(12)),
+          Container(
+            padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
+            width: double.infinity,
+            height: 64,
+            child: button(
+              "recover",
+              fontSize: 18,
+              onPressed: _recoveryKey.text.isNotEmpty
+                  ? () async {
+                      final dialog =
+                          createProgressDialog(context, "decrypting...");
+                      await dialog.show();
+                      try {
+                        await Configuration.instance
+                            .recover(_recoveryKey.text.trim());
+                        await dialog.hide();
+                        showToast("recovery successful!");
+                        Navigator.of(context).pushReplacement(
+                          MaterialPageRoute(
+                            builder: (BuildContext context) {
+                              return WillPopScope(
+                                onWillPop: () async => false,
+                                child: PasswordEntryPage(
+                                  mode: PasswordEntryMode.reset,
+                                ),
+                              );
+                            },
                           ),
-                          padding: EdgeInsets.all(20)),
-                    ),
-                  ],
+                        );
+                      } catch (e) {
+                        await dialog.hide();
+                        String errMessage =
+                            'the recovery key you entered is incorrect';
+                        if (e is AssertionError) {
+                          errMessage = '$errMessage : ${e.message}';
+                        }
+                        showErrorDialog(
+                            context, "incorrect recovery key", errMessage);
+                      }
+                    }
+                  : null,
+            ),
+          ),
+          GestureDetector(
+            behavior: HitTestBehavior.translucent,
+            onTap: () {
+              showErrorDialog(
+                context,
+                "sorry",
+                "due to the nature of our end-to-end encryption protocol, your data cannot be decrypted without your password or recovery key",
+              );
+            },
+            child: Container(
+              padding: EdgeInsets.all(40),
+              child: Center(
+                child: Text(
+                  "no recovery key?",
+                  style: TextStyle(
+                    decoration: TextDecoration.underline,
+                    fontSize: 12,
+                    color: Colors.white.withOpacity(0.9),
+                  ),
                 ),
               ),
-            )
-          ],
-        ),
+            ),
+          ),
+        ],
       ),
     );
   }

+ 7 - 10
lib/ui/settings/account_section_widget.dart

@@ -8,12 +8,13 @@ import 'package:photos/ui/app_lock.dart';
 import 'package:photos/ui/change_email_dialog.dart';
 import 'package:photos/ui/password_entry_page.dart';
 import 'package:photos/ui/payment/subscription.dart';
-import 'package:photos/ui/recovery_key_dialog.dart';
+import 'package:photos/ui/recovery_key_page.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';
 import 'package:photos/utils/auth_util.dart';
 import 'package:photos/utils/dialog_util.dart';
+import 'package:photos/utils/navigation_util.dart';
 import 'package:photos/utils/toast_util.dart';
 
 class AccountSectionWidget extends StatefulWidget {
@@ -72,17 +73,13 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
               showGenericErrorDialog(context);
               return;
             }
-
-            showDialog(
-              context: context,
-              builder: (BuildContext context) {
-                return RecoveryKeyDialog(recoveryKey, "ok", () {});
-              },
-              barrierColor: Colors.black.withOpacity(0.85),
-            );
+            routeToPage(
+                context,
+                RecoveryKeyPage(recoveryKey, "OK",
+                    showAppBar: true, onDone: () {}));
           },
           child:
-              SettingsTextItem(text: "Recovery key", icon: Icons.navigate_next),
+              SettingsTextItem(text: "Recovery New", icon: Icons.navigate_next),
         ),
         SectionOptionDivider,
         GestureDetector(

+ 5 - 9
lib/ui/settings/backup_section_widget.dart

@@ -10,7 +10,6 @@ import 'package:photos/services/sync_service.dart';
 import 'package:photos/ui/backup_folder_selection_page.dart';
 import 'package:photos/ui/deduplicate_page.dart';
 import 'package:photos/ui/free_space_page.dart';
-import 'package:photos/ui/recovery_page.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';
@@ -45,14 +44,11 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
           behavior: HitTestBehavior.translucent,
           onTap: () async {
             routeToPage(
-                context,
-                RecoveryPage(
-                  showAppBar: true,
-                )
-                // BackupFolderSelectionPage(
-                //   buttonText: "Backup",
-                // ),
-                );
+              context,
+              BackupFolderSelectionPage(
+                buttonText: "Backup",
+              ),
+            );
           },
           child: SettingsTextItem(
               text: "Backed up folders", icon: Icons.navigate_next),