Update UI for the OTT sign in flow
This commit is contained in:
parent
8a101af009
commit
ddb2c7dc82
11 changed files with 217 additions and 144 deletions
1
assets/around_the_world.svg
Normal file
1
assets/around_the_world.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 9.5 KiB |
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>(),
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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"];
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
21
pubspec.lock
21
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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue