Browse Source

Accept password on the first screen

Vishnu Mohandas 4 năm trước cách đây
mục cha
commit
e7d13b5704

+ 14 - 4
lib/core/configuration.dart

@@ -55,6 +55,7 @@ class Configuration {
   FlutterSecureStorage _secureStorage;
   FlutterSecureStorage _secureStorage;
   String _tempDirectory;
   String _tempDirectory;
   String _thumbnailCacheDirectory;
   String _thumbnailCacheDirectory;
+  String _volatilePassword;
 
 
   Future<void> init() async {
   Future<void> init() async {
     _preferences = await SharedPreferences.getInstance();
     _preferences = await SharedPreferences.getInstance();
@@ -77,7 +78,8 @@ class Configuration {
       _logger.warning(e);
       _logger.warning(e);
     }
     }
     tempDirectory.createSync(recursive: true);
     tempDirectory.createSync(recursive: true);
-    _thumbnailCacheDirectory = (await getTemporaryDirectory()).path + "/thumbnail-cache";
+    _thumbnailCacheDirectory =
+        (await getTemporaryDirectory()).path + "/thumbnail-cache";
     io.Directory(_thumbnailCacheDirectory).createSync(recursive: true);
     io.Directory(_thumbnailCacheDirectory).createSync(recursive: true);
     if (!_preferences.containsKey(tokenKey)) {
     if (!_preferences.containsKey(tokenKey)) {
       await _secureStorage.deleteAll();
       await _secureStorage.deleteAll();
@@ -243,9 +245,9 @@ class Configuration {
   }
   }
 
 
   String getHttpEndpoint() {
   String getHttpEndpoint() {
-    // if (kDebugMode) {
-    //   return "http://192.168.1.111:8080";
-    // }
+    if (kDebugMode) {
+      return "http://192.168.1.111:8080";
+    }
     return "https://api.ente.io";
     return "https://api.ente.io";
   }
   }
 
 
@@ -416,4 +418,12 @@ class Configuration {
   Future<void> setShouldHideFromRecents(bool value) {
   Future<void> setShouldHideFromRecents(bool value) {
     return _preferences.setBool(keyShouldHideFromRecents, value);
     return _preferences.setBool(keyShouldHideFromRecents, value);
   }
   }
+
+  void setVolatilePassword(String volatilePassword) {
+    _volatilePassword = volatilePassword;
+  }
+
+  String getVolatilePassword() {
+    return _volatilePassword;
+  }
 }
 }

+ 289 - 162
lib/ui/email_entry_page.dart

@@ -1,7 +1,11 @@
+import 'dart:io';
+
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter/widgets.dart';
+import 'package:flutter_password_strength/flutter_password_strength.dart';
+import 'package:logging/logging.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/models/billing_plan.dart';
 import 'package:photos/models/billing_plan.dart';
 import 'package:photos/services/billing_service.dart';
 import 'package:photos/services/billing_service.dart';
@@ -21,14 +25,22 @@ class EmailEntryPage extends StatefulWidget {
 }
 }
 
 
 class _EmailEntryPageState extends State<EmailEntryPage> {
 class _EmailEntryPageState extends State<EmailEntryPage> {
+  static const kPasswordStrengthThreshold = 0.4;
+
+  static final _logger = Logger("EmailEntry");
+
   final _config = Configuration.instance;
   final _config = Configuration.instance;
+  final _passwordController1 = TextEditingController(),
+      _passwordController2 = TextEditingController();
+
   String _email;
   String _email;
-  String _name;
+  double _passwordStrength = 0;
+  bool _hasAgreedToTOS = true;
+  bool _hasAgreedToE2E = false;
 
 
   @override
   @override
   void initState() {
   void initState() {
     _email = _config.getEmail();
     _email = _config.getEmail();
-    _name = _config.getName();
     super.initState();
     super.initState();
   }
   }
 
 
@@ -44,182 +56,291 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
     );
     );
     return Scaffold(
     return Scaffold(
       appBar: appBar,
       appBar: appBar,
-      body: _getBody(appBar.preferredSize.height),
+      body: _getBody(),
+      resizeToAvoidBottomInset: false,
     );
     );
   }
   }
 
 
-  Widget _getBody(final appBarSize) {
-    final pageSize = MediaQuery.of(context).size.height;
-    final notifySize = MediaQuery.of(context).padding.top;
-    return SingleChildScrollView(
-      child: Container(
-        height: pageSize - (appBarSize + notifySize),
-        padding: EdgeInsets.all(8),
-        child: Column(
-          crossAxisAlignment: CrossAxisAlignment.stretch,
-          mainAxisAlignment: MainAxisAlignment.center,
-          mainAxisSize: MainAxisSize.max,
-          children: [
-            Padding(
-              padding: EdgeInsets.all(60),
-            ),
-            Padding(
-              padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
-              child: TextFormField(
-                decoration: InputDecoration(
-                  hintText: 'name',
-                  hintStyle: TextStyle(
-                    color: Colors.white30,
-                  ),
-                  contentPadding: EdgeInsets.all(12),
-                ),
-                onChanged: (value) {
-                  setState(() {
-                    _name = value;
-                  });
-                },
-                autocorrect: false,
-                keyboardType: TextInputType.text,
-                textCapitalization: TextCapitalization.words,
-                initialValue: _name,
-              ),
-            ),
-            Padding(padding: EdgeInsets.all(8)),
-            Padding(
-              padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
-              child: TextFormField(
-                decoration: InputDecoration(
-                  hintText: 'email',
-                  hintStyle: TextStyle(
-                    color: Colors.white30,
+  Widget _getBody() {
+    return Column(
+      children: [
+        FlutterPasswordStrength(
+          password: _passwordController1.text,
+          backgroundColor: Colors.white.withOpacity(0.1),
+          strengthCallback: (strength) {
+            _passwordStrength = strength;
+          },
+        ),
+        Expanded(child: Container()),
+        SingleChildScrollView(
+          child: Container(
+            padding: EdgeInsets.all(8),
+            child: Column(
+              crossAxisAlignment: CrossAxisAlignment.stretch,
+              mainAxisAlignment: MainAxisAlignment.center,
+              mainAxisSize: MainAxisSize.max,
+              children: [
+                Padding(
+                  padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
+                  child: TextFormField(
+                    decoration: InputDecoration(
+                      hintText: 'email',
+                      hintStyle: TextStyle(
+                        color: Colors.white30,
+                      ),
+                      contentPadding: EdgeInsets.all(12),
+                    ),
+                    onChanged: (value) {
+                      setState(() {
+                        _email = value;
+                      });
+                    },
+                    autocorrect: false,
+                    keyboardType: TextInputType.emailAddress,
+                    initialValue: _email,
                   ),
                   ),
-                  contentPadding: EdgeInsets.all(12),
                 ),
                 ),
-                onChanged: (value) {
-                  setState(() {
-                    _email = value;
-                  });
-                },
-                autocorrect: false,
-                keyboardType: TextInputType.emailAddress,
-                initialValue: _email,
-              ),
-            ),
-            Padding(padding: EdgeInsets.all(8)),
-            Padding(
-              padding: const EdgeInsets.all(12),
-              child: RichText(
-                text: TextSpan(
-                  children: [
-                    TextSpan(
-                      text: "by clicking sign up, I agree to the ",
-                    ),
-                    TextSpan(
-                      text: "terms of service",
-                      style: TextStyle(
-                        color: Colors.blue,
-                        fontFamily: 'Ubuntu',
+                Padding(padding: EdgeInsets.all(8)),
+                Padding(
+                  padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
+                  child: TextFormField(
+                    decoration: InputDecoration(
+                      hintText: "password",
+                      hintStyle: TextStyle(
+                        color: Colors.white30,
                       ),
                       ),
-                      recognizer: TapGestureRecognizer()
-                        ..onTap = () {
-                          Navigator.of(context).push(
-                            MaterialPageRoute(
-                              builder: (BuildContext context) {
-                                return WebPage(
-                                    "terms", "https://ente.io/terms");
-                              },
-                            ),
-                          );
-                        },
+                      contentPadding: EdgeInsets.all(12),
                     ),
                     ),
-                    TextSpan(text: " and "),
-                    TextSpan(
-                      text: "privacy policy",
-                      style: TextStyle(
-                        color: Colors.blue,
-                        fontFamily: 'Ubuntu',
+                    controller: _passwordController1,
+                    autofocus: false,
+                    autocorrect: false,
+                    keyboardType: TextInputType.visiblePassword,
+                    onChanged: (_) {
+                      setState(() {});
+                    },
+                  ),
+                ),
+                Padding(padding: EdgeInsets.all(8)),
+                Padding(
+                  padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
+                  child: TextFormField(
+                    decoration: InputDecoration(
+                      hintText: "confirm password",
+                      hintStyle: TextStyle(
+                        color: Colors.white30,
                       ),
                       ),
-                      recognizer: TapGestureRecognizer()
-                        ..onTap = () {
-                          Navigator.of(context).push(
-                            MaterialPageRoute(
-                              builder: (BuildContext context) {
-                                return WebPage(
-                                    "privacy", "https://ente.io/privacy");
-                              },
-                            ),
-                          );
-                        },
+                      contentPadding: EdgeInsets.all(12),
                     ),
                     ),
-                  ],
-                  style: TextStyle(
-                    height: 1.25,
-                    fontSize: 12,
-                    fontFamily: 'Ubuntu',
-                    color: Colors.white70,
+                    controller: _passwordController2,
+                    autofocus: false,
+                    autocorrect: false,
+                    obscureText: true,
+                    keyboardType: TextInputType.visiblePassword,
+                    onChanged: (_) {
+                      setState(() {});
+                    },
                   ),
                   ),
                 ),
                 ),
-                textAlign: TextAlign.center,
-              ),
+                Padding(
+                  padding: EdgeInsets.all(20),
+                ),
+                _getAgreement(),
+                Padding(padding: EdgeInsets.all(16)),
+                Container(
+                  width: double.infinity,
+                  height: 64,
+                  padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
+                  child: button(
+                    "sign up",
+                    onPressed: _isFormValid()
+                        ? () {
+                            if (!isValidEmail(_email)) {
+                              showErrorDialog(context, "invalid email address",
+                                  "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,
+                  ),
+                ),
+              ],
             ),
             ),
-            Padding(padding: EdgeInsets.all(4)),
-            Container(
-              width: double.infinity,
-              height: 64,
-              padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
-              child: button(
-                "sign up",
-                onPressed: _email != null &&
-                        _email.isNotEmpty &&
-                        _name != null &&
-                        _name.isNotEmpty
-                    ? () {
-                        if (!isValidEmail(_email)) {
-                          showErrorDialog(context, "invalid email address",
-                              "please enter a valid email address.");
-                          return;
-                        }
-                        _config.setEmail(_email);
-                        _config.setName(_name);
-                        UserService.instance.getOtt(context, _email);
-                      }
-                    : null,
-                fontSize: 18,
+          ),
+        ),
+        Expanded(child: Container()),
+      ],
+    );
+  }
+
+  Container _getAgreement() {
+    return Container(
+      padding: const EdgeInsets.only(left: 20, right: 20),
+      child: Column(
+        children: [
+          _getTOSAgreement(),
+          _getPasswordAgreement(),
+        ],
+      ),
+    );
+  }
+
+  Widget _getTOSAgreement() {
+    return GestureDetector(
+      onTap: () {
+        setState(() {
+          _hasAgreedToTOS = !_hasAgreedToTOS;
+        });
+      },
+      behavior: HitTestBehavior.translucent,
+      child: Row(
+        children: [
+          Checkbox(
+              value: _hasAgreedToTOS,
+              onChanged: (value) {
+                setState(() {
+                  _hasAgreedToTOS = value;
+                });
+              }),
+          Expanded(
+            child: RichText(
+              text: TextSpan(
+                children: [
+                  TextSpan(
+                    text: "I agree to the ",
+                  ),
+                  TextSpan(
+                    text: "terms of service",
+                    style: TextStyle(
+                      color: Colors.blue,
+                      fontFamily: 'Ubuntu',
+                    ),
+                    recognizer: TapGestureRecognizer()
+                      ..onTap = () {
+                        Navigator.of(context).push(
+                          MaterialPageRoute(
+                            builder: (BuildContext context) {
+                              return WebPage("terms", "https://ente.io/terms");
+                            },
+                          ),
+                        );
+                      },
+                  ),
+                  TextSpan(text: " and "),
+                  TextSpan(
+                    text: "privacy policy",
+                    style: TextStyle(
+                      color: Colors.blue,
+                      fontFamily: 'Ubuntu',
+                    ),
+                    recognizer: TapGestureRecognizer()
+                      ..onTap = () {
+                        Navigator.of(context).push(
+                          MaterialPageRoute(
+                            builder: (BuildContext context) {
+                              return WebPage(
+                                  "privacy", "https://ente.io/privacy");
+                            },
+                          ),
+                        );
+                      },
+                  ),
+                ],
+                style: TextStyle(
+                  height: 1.25,
+                  fontSize: 12,
+                  fontFamily: 'Ubuntu',
+                  color: Colors.white70,
+                ),
               ),
               ),
+              textAlign: TextAlign.left,
             ),
             ),
-            Expanded(child: Container()),
-            Align(
-              alignment: Alignment.center,
-              child: GestureDetector(
-                behavior: HitTestBehavior.opaque,
-                onTap: () {
-                  showModalBottomSheet<void>(
-                      context: context,
-                      backgroundColor: Theme.of(context).cardColor,
-                      builder: (BuildContext context) {
-                        return PricingWidget();
-                      });
-                },
-                child: Container(
-                  padding: EdgeInsets.all(32),
-                  child: Row(
-                    mainAxisAlignment: MainAxisAlignment.center,
-                    crossAxisAlignment: CrossAxisAlignment.center,
-                    children: [
-                      Text(
-                        "pricing",
-                      ),
-                      Icon(Icons.arrow_drop_up),
-                    ],
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _getPasswordAgreement() {
+    return GestureDetector(
+      onTap: () {
+        setState(() {
+          _hasAgreedToE2E = !_hasAgreedToE2E;
+        });
+      },
+      behavior: HitTestBehavior.translucent,
+      child: Row(
+        children: [
+          Checkbox(
+              value: _hasAgreedToE2E,
+              onChanged: (value) {
+                setState(() {
+                  _hasAgreedToE2E = value;
+                });
+              }),
+          Expanded(
+            child: RichText(
+              text: TextSpan(
+                children: [
+                  TextSpan(
+                    text:
+                        "I understand that if I lose my password, I may lose my data since my data is ",
                   ),
                   ),
+                  TextSpan(
+                    text: "end-to-end encrypted",
+                    style: TextStyle(
+                      color: Colors.blue,
+                      fontFamily: 'Ubuntu',
+                    ),
+                    recognizer: TapGestureRecognizer()
+                      ..onTap = () {
+                        Navigator.of(context).push(
+                          MaterialPageRoute(
+                            builder: (BuildContext context) {
+                              return WebPage(
+                                  "encryption", "https://ente.io/encryption");
+                            },
+                          ),
+                        );
+                      },
+                  ),
+                  TextSpan(text: " with ente"),
+                ],
+                style: TextStyle(
+                  height: 1.5,
+                  fontSize: 12,
+                  fontFamily: 'Ubuntu',
+                  color: Colors.white70,
                 ),
                 ),
               ),
               ),
+              textAlign: TextAlign.left,
             ),
             ),
-          ],
-        ),
+          ),
+        ],
       ),
       ),
     );
     );
   }
   }
+
+  bool _isFormValid() {
+    return _email != null &&
+        _email.isNotEmpty &&
+        _passwordController1.text.isNotEmpty &&
+        _passwordController2.text.isNotEmpty &&
+        _hasAgreedToTOS &&
+        _hasAgreedToE2E;
+  }
 }
 }
 
 
 class PricingWidget extends StatelessWidget {
 class PricingWidget extends StatelessWidget {
@@ -245,7 +366,10 @@ class PricingWidget extends StatelessWidget {
   Container _buildPlans(BuildContext context, BillingPlans plans) {
   Container _buildPlans(BuildContext context, BillingPlans plans) {
     final planWidgets = List<BillingPlanWidget>();
     final planWidgets = List<BillingPlanWidget>();
     for (final plan in plans.plans) {
     for (final plan in plans.plans) {
-      planWidgets.add(BillingPlanWidget(plan));
+      final productID = Platform.isAndroid ? plan.androidID : plan.iosID;
+      if (productID != null && productID.isNotEmpty) {
+        planWidgets.add(BillingPlanWidget(plan));
+      }
     }
     }
     final freePlan = plans.freePlan;
     final freePlan = plans.freePlan;
     return Container(
     return Container(
@@ -261,11 +385,14 @@ class PricingWidget extends StatelessWidget {
               fontSize: 18,
               fontSize: 18,
             ),
             ),
           ),
           ),
-          Row(
-            mainAxisAlignment: MainAxisAlignment.center,
-            children: planWidgets,
+          SingleChildScrollView(
+            scrollDirection: Axis.horizontal,
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.center,
+              children: planWidgets,
+            ),
           ),
           ),
-          Text("there is also a free trial of " +
+          Text("we offer a free trial of " +
               convertBytesToReadableFormat(freePlan.storage) +
               convertBytesToReadableFormat(freePlan.storage) +
               " for " +
               " for " +
               freePlan.duration.toString() +
               freePlan.duration.toString() +

+ 24 - 6
lib/ui/password_entry_page.dart

@@ -40,6 +40,16 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
   final _passwordController1 = TextEditingController(),
   final _passwordController1 = TextEditingController(),
       _passwordController2 = TextEditingController();
       _passwordController2 = TextEditingController();
   double _passwordStrength = 0;
   double _passwordStrength = 0;
+  String _password;
+
+  @override
+  void initState() {
+    super.initState();
+    _password = Configuration.instance.getVolatilePassword();
+    if (_password != null) {
+      Future.delayed(Duration.zero, () => _showRecoveryCodeDialog(_password));
+    }
+  }
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -48,6 +58,8 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
       title = "change password";
       title = "change password";
     } else if (widget.mode == PasswordEntryMode.reset) {
     } else if (widget.mode == PasswordEntryMode.reset) {
       title = "reset password";
       title = "reset password";
+    } else if (_password != null) {
+      title = "encryption keys";
     }
     }
     return Scaffold(
     return Scaffold(
       appBar: AppBar(
       appBar: AppBar(
@@ -61,6 +73,9 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
   }
   }
 
 
   Widget _getBody(String buttonText) {
   Widget _getBody(String buttonText) {
+    if (_password != null) {
+      return Container();
+    }
     return Column(
     return Column(
       children: [
       children: [
         FlutterPasswordStrength(
         FlutterPasswordStrength(
@@ -190,7 +205,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
           "the password you have chosen is too simple, please choose another one");
           "the password you have chosen is too simple, please choose another one");
     } else {
     } else {
       if (widget.mode == PasswordEntryMode.set) {
       if (widget.mode == PasswordEntryMode.set) {
-        _showRecoveryCodeDialog();
+        _showRecoveryCodeDialog(_passwordController1.text);
       } else {
       } else {
         _updatePassword();
         _updatePassword();
       }
       }
@@ -229,13 +244,12 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
     }
     }
   }
   }
 
 
-  Future<void> _showRecoveryCodeDialog() async {
+  Future<void> _showRecoveryCodeDialog(String password) async {
     final dialog =
     final dialog =
         createProgressDialog(context, "generating encryption keys...");
         createProgressDialog(context, "generating encryption keys...");
     await dialog.show();
     await dialog.show();
     try {
     try {
-      final result =
-          await Configuration.instance.generateKey(_passwordController1.text);
+      final result = await Configuration.instance.generateKey(password);
       await dialog.hide();
       await dialog.hide();
       final onDone = () async {
       final onDone = () async {
         final dialog = createProgressDialog(context, "please wait...");
         final dialog = createProgressDialog(context, "please wait...");
@@ -252,7 +266,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
             (route) => route.isFirst,
             (route) => route.isFirst,
           );
           );
         } catch (e, s) {
         } catch (e, s) {
-          Logger("PEP").severe(e, s);
+          _logger.severe(e, s);
           await dialog.hide();
           await dialog.hide();
           showGenericErrorDialog(context);
           showGenericErrorDialog(context);
         }
         }
@@ -261,7 +275,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
         context: context,
         context: context,
         builder: (BuildContext context) {
         builder: (BuildContext context) {
           return RecoveryKeyDialog(
           return RecoveryKeyDialog(
-              result.privateKeyAttributes.recoveryKey, "continue", onDone);
+            result.privateKeyAttributes.recoveryKey,
+            "continue",
+            onDone,
+            isDismissible: false,
+          );
         },
         },
         barrierColor: Colors.black.withOpacity(0.85),
         barrierColor: Colors.black.withOpacity(0.85),
         barrierDismissible: false,
         barrierDismissible: false,