Update UI for the OTT sign in flow

This commit is contained in:
Vishnu Mohandas 2020-08-25 11:30:19 +05:30
parent 8a101af009
commit ddb2c7dc82
11 changed files with 217 additions and 144 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -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);
}

View file

@ -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<int>(),

View file

@ -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),
);
}

View file

@ -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<EmailEntryPage> {
}
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<void> _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;
},
),
);
}
}

View file

@ -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<GalleryAppBarWidget> {
bool _hasSyncErrors = false;
StreamSubscription<RemoteSyncEvent> _subscription;
@override
void initState() {
_subscription = Bus.instance.on<RemoteSyncEvent>().listen((event) {
setState(() {
_hasSyncErrors = !event.success;
});
});
widget.selectedFiles.addListener(() {
setState(() {});
});
@ -80,13 +71,13 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
List<Widget> _getDefaultActions(BuildContext context) {
List<Widget> actions = List<Widget>();
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<GalleryAppBarWidget> {
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();
}
}

View file

@ -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<OTTVerificationPage> {
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<OTTVerificationPage> {
Padding(padding: EdgeInsets.all(12)),
Text.rich(
TextSpan(
text: "We've sent a mail to ",
style: TextStyle(fontSize: 18),
children: <TextSpan>[
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<OTTVerificationPage> {
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,
),
))
],
),
),

View file

@ -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<void> 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<void> 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<bool> 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"];

View file

@ -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.");
}

View file

@ -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:

View file

@ -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