diff --git a/assets/around_the_world.svg b/assets/around_the_world.svg new file mode 100644 index 000000000..a17b5950d --- /dev/null +++ b/assets/around_the_world.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/core/configuration.dart b/lib/core/configuration.dart index 8cd30c876..86ed4340d 100644 --- a/lib/core/configuration.dart +++ b/lib/core/configuration.dart @@ -12,10 +12,9 @@ class Configuration { static final Configuration instance = Configuration._privateConstructor(); static const endpointKey = "endpoint"; - static const tokenKey = "token"; - static const usernameKey = "username"; static const userIDKey = "user_id"; - static const passwordKey = "password"; + static const emailKey = "email"; + static const tokenKey = "token"; static const hasOptedForE2EKey = "has_opted_for_e2e_encryption"; static const keyKey = "key"; static const keyEncryptedKey = "encrypted_key"; @@ -50,7 +49,7 @@ class Configuration { } String getEndpoint() { - return _preferences.getString(endpointKey); + return "192.168.0.106"; } String getHttpEndpoint() { @@ -72,12 +71,12 @@ class Configuration { await _preferences.setString(tokenKey, token); } - String getUsername() { - return _preferences.getString(usernameKey); + String getEmail() { + return _preferences.getString(emailKey); } - void setUsername(String username) async { - await _preferences.setString(usernameKey, username); + void setEmail(String email) async { + await _preferences.setString(emailKey, email); } int getUserID() { @@ -88,14 +87,6 @@ class Configuration { await _preferences.setInt(userIDKey, userID); } - String getPassword() { - return _preferences.getString(passwordKey); - } - - void setPassword(String password) async { - await _preferences.setString(passwordKey, password); - } - void setOptInForE2E(bool hasOptedForE2E) async { await _preferences.setBool(hasOptedForE2EKey, hasOptedForE2E); } diff --git a/lib/folder_service.dart b/lib/folder_service.dart index 29775e92a..907afbf28 100644 --- a/lib/folder_service.dart +++ b/lib/folder_service.dart @@ -136,7 +136,7 @@ class FolderSharingService { try { return Folder( null, - Configuration.instance.getUsername() + "s " + deviceFolder, + Configuration.instance.getEmail() + "s " + deviceFolder, Configuration.instance.getUserID(), deviceFolder, Set(), diff --git a/lib/main.dart b/lib/main.dart index 250fcbf88..c392738d5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -109,8 +109,11 @@ class MyApp extends StatelessWidget with WidgetsBindingObserver { return MaterialApp( title: _title, - theme: ThemeData.dark() - .copyWith(hintColor: Colors.grey, accentColor: Colors.pink[400]), + theme: ThemeData.dark().copyWith( + hintColor: Colors.grey, + accentColor: Colors.pink[400], + buttonColor: Colors.pink, + ), home: HomeWidget(_title), ); } diff --git a/lib/ui/email_entry_page.dart b/lib/ui/email_entry_page.dart index 1b1dc9547..5b5f30c4f 100644 --- a/lib/ui/email_entry_page.dart +++ b/lib/ui/email_entry_page.dart @@ -1,11 +1,8 @@ -import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:logging/logging.dart'; -import 'package:photos/core/configuration.dart'; -import 'package:photos/ui/ott_verification_page.dart'; -import 'package:photos/utils/dialog_util.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:photos/user_authenticator.dart'; class EmailEntryPage extends StatefulWidget { EmailEntryPage({Key key}) : super(key: key); @@ -28,96 +25,44 @@ class _EmailEntryPageState extends State { } Widget _getBody() { - return Container( - padding: EdgeInsets.all(8), - child: Column( - children: [ - TextFormField( - decoration: InputDecoration( - hintText: 'email@domain.com', - contentPadding: EdgeInsets.all(20), + return SingleChildScrollView( + child: Container( + padding: EdgeInsets.all(8), + child: Column( + children: [ + SvgPicture.asset( + "assets/around_the_world.svg", + width: 256, + height: 256, ), - controller: _emailController, - autofocus: true, - autocorrect: false, - keyboardType: TextInputType.emailAddress, - ), - Padding(padding: EdgeInsets.all(8)), - SizedBox( - width: double.infinity, - child: RaisedButton( - onPressed: () { - _getOtt(_emailController.text); - }, - padding: const EdgeInsets.fromLTRB(8, 12, 8, 12), - child: Text("Sign In"), - color: Colors.pink[400], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18.0), - ), - )), - ], - ), - ); - } - - Future _getOtt(String email) async { - final dialog = createProgressDialog(context, "Please wait..."); - await dialog.show(); - await Dio() - .get(Configuration.instance.getHttpEndpoint() + "/users/ott", - queryParameters: { - "email": email, - }, - options: Options( - headers: { - "X-Auth-Token": Configuration.instance.getToken(), - }, - )) - .catchError((e) async { - Logger("EmailEntryPage").severe(e); - await dialog.hide(); - _showErrorDialog(); - }).then((response) async { - await dialog.hide(); - if (response.statusCode == 200) { - _navigateToVerificationInstructionPage(email); - } else { - _showErrorDialog(); - } - }); - } - - void _navigateToVerificationInstructionPage(String email) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return OTTVerificationPage(email); - }, - ), - ); - } - - _showErrorDialog() { - AlertDialog alert = AlertDialog( - title: Text("Oops."), - content: Text("Sorry, something went wrong. Please try again."), - actions: [ - FlatButton( - child: Text("OK"), - onPressed: () { - Navigator.of(context).pop(); - }, + TextFormField( + decoration: InputDecoration( + hintText: 'email@domain.com', + contentPadding: EdgeInsets.all(20), + ), + controller: _emailController, + autofocus: true, + autocorrect: false, + keyboardType: TextInputType.emailAddress, + ), + Padding(padding: EdgeInsets.all(8)), + SizedBox( + width: double.infinity, + child: RaisedButton( + onPressed: () { + UserAuthenticator.instance + .getOtt(context, _emailController.text); + }, + padding: const EdgeInsets.fromLTRB(8, 12, 8, 12), + child: Text("Sign In"), + color: Theme.of(context).buttonColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(18.0), + ), + )), + ], ), - ], - ); - - // show the dialog - showDialog( - context: context, - builder: (BuildContext context) { - return alert; - }, + ), ); } } diff --git a/lib/ui/gallery_app_bar_widget.dart b/lib/ui/gallery_app_bar_widget.dart index 846724f6e..14f96b159 100644 --- a/lib/ui/gallery_app_bar_widget.dart +++ b/lib/ui/gallery_app_bar_widget.dart @@ -2,11 +2,10 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:photos/core/configuration.dart'; -import 'package:photos/core/event_bus.dart'; -import 'package:photos/events/remote_sync_event.dart'; import 'package:photos/file_repository.dart'; import 'package:photos/models/selected_files.dart'; -import 'package:photos/ui/setup_page.dart'; +import 'package:photos/ui/email_entry_page.dart'; +import 'package:photos/ui/ott_verification_page.dart'; import 'package:photos/ui/share_folder_widget.dart'; import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/file_util.dart'; @@ -41,16 +40,8 @@ class GalleryAppBarWidget extends StatefulWidget } class _GalleryAppBarWidgetState extends State { - bool _hasSyncErrors = false; - StreamSubscription _subscription; - @override void initState() { - _subscription = Bus.instance.on().listen((event) { - setState(() { - _hasSyncErrors = !event.success; - }); - }); widget.selectedFiles.addListener(() { setState(() {}); }); @@ -80,13 +71,13 @@ class _GalleryAppBarWidgetState extends State { List _getDefaultActions(BuildContext context) { List actions = List(); - if (_hasSyncErrors || !Configuration.instance.hasConfiguredAccount()) { + if (!Configuration.instance.hasConfiguredAccount()) { actions.add(IconButton( icon: Icon(Configuration.instance.hasConfiguredAccount() ? Icons.sync_problem : Icons.sync_disabled), onPressed: () { - _openSyncConfiguration(context); + _navigateToSignInPage(context); }, )); } else if (widget.type == GalleryAppBarType.local_folder && @@ -179,20 +170,14 @@ class _GalleryAppBarWidgetState extends State { widget.selectedFiles.clearAll(); } - void _openSyncConfiguration(BuildContext context) { - final page = SetupPage(); + void _navigateToSignInPage(BuildContext context) { Navigator.of(context).push( MaterialPageRoute( - settings: RouteSettings(name: "/setup"), builder: (BuildContext context) { - return page; + // return OTTVerificationPage("hello@ente.io"); + return EmailEntryPage(); }, ), ); } - - void dispose() { - _subscription.cancel(); - super.dispose(); - } } diff --git a/lib/ui/ott_verification_page.dart b/lib/ui/ott_verification_page.dart index 3c4399d61..5f65babeb 100644 --- a/lib/ui/ott_verification_page.dart +++ b/lib/ui/ott_verification_page.dart @@ -1,6 +1,9 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:photos/user_authenticator.dart'; class OTTVerificationPage extends StatefulWidget { final String email; @@ -29,6 +32,8 @@ class _OTTVerificationPageState extends State { padding: EdgeInsets.fromLTRB(8, 64, 8, 8), child: Column( crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, children: [ SvgPicture.asset( "assets/email_sent.svg", @@ -38,22 +43,22 @@ class _OTTVerificationPageState extends State { Padding(padding: EdgeInsets.all(12)), Text.rich( TextSpan( - text: "We've sent a mail to ", style: TextStyle(fontSize: 18), children: [ + TextSpan(text: "We've sent a mail to "), TextSpan( text: widget.email, style: TextStyle( - decoration: TextDecoration.underline, + color: Theme.of(context).accentColor, )), - // can add more TextSpans here... + TextSpan(text: "."), ], ), textAlign: TextAlign.center, ), Padding(padding: EdgeInsets.all(12)), Text( - "Please check your inbox (and spam folders) to complete the verification.", + "Please check your inbox (and spam) to complete verification.", textAlign: TextAlign.center, ), Padding(padding: EdgeInsets.all(12)), @@ -63,11 +68,45 @@ class _OTTVerificationPageState extends State { contentPadding: EdgeInsets.all(20), ), controller: _verificationCodeController, - autofocus: true, + autofocus: false, autocorrect: false, keyboardType: TextInputType.visiblePassword, textAlign: TextAlign.center, + onChanged: (_) { + setState(() {}); + }, ), + Padding(padding: EdgeInsets.all(8)), + SizedBox( + width: double.infinity, + child: RaisedButton( + onPressed: _verificationCodeController.text == null || + _verificationCodeController.text.isEmpty + ? null + : () { + UserAuthenticator.instance.getCredentials(context, + widget.email, _verificationCodeController.text); + }, + padding: const EdgeInsets.fromLTRB(8, 12, 8, 12), + child: Text( + "Sign In", + ), + color: Colors.pink, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(18.0), + ), + )), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + "Did not get email?", + style: TextStyle( + decoration: TextDecoration.underline, + fontSize: 12, + ), + )) ], ), ), diff --git a/lib/user_authenticator.dart b/lib/user_authenticator.dart index a9795a6a7..138be3e4d 100644 --- a/lib/user_authenticator.dart +++ b/lib/user_authenticator.dart @@ -1,9 +1,13 @@ import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; import 'package:photos/core/configuration.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/events/user_authenticated_event.dart'; +import 'package:photos/ui/ott_verification_page.dart'; +import 'package:photos/utils/dialog_util.dart'; class UserAuthenticator { final _dio = Dio(); @@ -14,6 +18,60 @@ class UserAuthenticator { static final UserAuthenticator instance = UserAuthenticator._privateConstructor(); + Future getOtt(BuildContext context, String email) async { + final dialog = createProgressDialog(context, "Please wait..."); + await dialog.show(); + await Dio().get( + Configuration.instance.getHttpEndpoint() + "/users/ott", + queryParameters: { + "email": email, + }, + ).catchError((e) async { + _logger.severe(e); + await dialog.hide(); + showGenericErrorDialog(context); + }).then((response) async { + await dialog.hide(); + if (response.statusCode == 200) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return OTTVerificationPage(email); + }, + ), + ); + } else { + showGenericErrorDialog(context); + } + }); + } + + Future getCredentials( + BuildContext context, String email, String ott) async { + final dialog = createProgressDialog(context, "Please wait..."); + await dialog.show(); + await Dio().get( + Configuration.instance.getHttpEndpoint() + "/users/credentials", + queryParameters: { + "email": email, + "ott": ott, + }, + ).catchError((e) async { + _logger.severe(e); + await dialog.hide(); + showGenericErrorDialog(context); + }).then((response) async { + await dialog.hide(); + if (response.statusCode == 200) { + _saveConfiguration(email, response); + Navigator.of(context).pop(); + } else { + showErrorDialog( + context, "Oops.", "Verification failed, please try again."); + } + }); + } + Future login(String username, String password) { return _dio.post( Configuration.instance.getHttpEndpoint() + "/users/authenticate", @@ -22,7 +80,7 @@ class UserAuthenticator { "password": password, }).then((response) { if (response.statusCode == 200 && response.data != null) { - _saveConfiguration(username, password, response); + _saveConfiguration(username, response); Bus.instance.fire(UserAuthenticatedEvent()); return true; } else { @@ -41,7 +99,7 @@ class UserAuthenticator { "password": password, }).then((response) { if (response.statusCode == 200 && response.data != null) { - _saveConfiguration(username, password, response); + _saveConfiguration(username, response); return true; } else { if (response.data != null && response.data["message"] != null) { @@ -68,9 +126,9 @@ class UserAuthenticator { ); } - void _saveConfiguration(String username, String password, Response response) { - Configuration.instance.setUsername(username); - Configuration.instance.setPassword(password); + void _saveConfiguration(String email, Response response) { + _logger.info("Saving configuration " + response.data.toString()); + Configuration.instance.setEmail(email); Configuration.instance.setUserID(response.data["id"]); Configuration.instance.setToken(response.data["token"]); final String encryptedKey = response.data["encryptedKey"]; diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index 17285d9ae..2333018ee 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -20,3 +20,30 @@ ProgressDialog createProgressDialog(BuildContext context, String message) { ); return dialog; } + +void showErrorDialog(BuildContext context, String title, String content) { + AlertDialog alert = AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + FlatButton( + child: Text("OK"), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + + showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + ); +} + +void showGenericErrorDialog(BuildContext context) { + showErrorDialog( + context, "Oops.", "Sorry, something went wrong. Please try again."); +} diff --git a/pubspec.lock b/pubspec.lock index 8f76665da..087f18737 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -272,6 +272,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.3.3" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "0.18.1" flutter_test: dependency: "direct dev" description: flutter @@ -408,6 +415,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1+1" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" path_provider: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index d86e6ba75..fee38d86d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,6 +62,7 @@ dependencies: computer: ^1.0.2 flutter_secure_storage: ^3.3.3 uni_links: ^0.4.0 + flutter_svg: ^0.18.1 dev_dependencies: flutter_test: @@ -78,6 +79,8 @@ flutter_icons: # The following section is specific to Flutter. flutter: + assets: + - assets/ # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in