Merge pull request #1 from ente-io/login_with_email

Login with email
This commit is contained in:
Vishnu Mohandas 2020-08-26 08:08:28 +05:30 committed by GitHub
commit 93b1fb446d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 706 additions and 85 deletions

1
assets/email_sent.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

1
assets/vault.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.7 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);
}
@ -143,6 +134,6 @@ class Configuration {
}
bool hasConfiguredAccount() {
return getEndpoint() != null && getToken() != null;
return getToken() != null && getKey() != null;
}
}

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

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:computer/computer.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/core/configuration.dart';
@ -14,7 +13,6 @@ import 'package:photos/ui/home_widget.dart';
import 'package:sentry/sentry.dart';
import 'package:super_logging/super_logging.dart';
import 'package:logging/logging.dart';
import 'package:uni_links/uni_links.dart';
final _logger = Logger("main");
@ -36,7 +34,6 @@ void _main() async {
await PhotoSyncManager.instance.init();
await MemoriesService.instance.init();
await FavoriteFilesRepository.instance.init();
await initDeepLinks();
_sync();
final SentryClient sentry = new SentryClient(dsn: SENTRY_DSN);
@ -76,31 +73,6 @@ void _sendErrorToSentry(SentryClient sentry, Object error, StackTrace stack) {
}
}
Future<void> initDeepLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
String initialLink = await getInitialLink();
// Parse the link and warn the user, if it is not correct,
// but keep in mind it could be `null`.
if (initialLink != null) {
_logger.info("Initial link received: " + initialLink);
} else {
_logger.info("No initial link received.");
}
} on PlatformException {
// Handle exception by warning the user their action did not succeed
// return?
_logger.severe("PlatformException thrown while getting initial link");
}
// Attach a listener to the stream
getLinksStream().listen((String link) {
_logger.info("Link received: " + link);
}, onError: (err) {
_logger.severe(err);
});
}
class MyApp extends StatelessWidget with WidgetsBindingObserver {
final _title = 'ente';
@override
@ -109,8 +81,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

@ -0,0 +1,71 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/user_authenticator.dart';
class EmailEntryPage extends StatefulWidget {
EmailEntryPage({Key key}) : super(key: key);
@override
_EmailEntryPageState createState() => _EmailEntryPageState();
}
class _EmailEntryPageState extends State<EmailEntryPage> {
TextEditingController _emailController;
@override
void initState() {
_emailController =
TextEditingController(text: Configuration.instance.getEmail());
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Preserve Memories"),
),
body: _getBody(),
);
}
Widget _getBody() {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(8),
child: Column(
children: [
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: () {
final email = _emailController.text;
Configuration.instance.setEmail(email);
UserAuthenticator.instance.getOtt(context, email);
},
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
child: Text("Sign In"),
color: Theme.of(context).buttonColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
)),
],
),
),
);
}
}

View file

@ -3,10 +3,13 @@ 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/events/user_authenticated_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/passphrase_entry_page.dart';
import 'package:photos/ui/passphrase_reentry_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,22 +44,25 @@ class GalleryAppBarWidget extends StatefulWidget
}
class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
bool _hasSyncErrors = false;
StreamSubscription<RemoteSyncEvent> _subscription;
StreamSubscription _userAuthEventSubscription;
@override
void initState() {
_subscription = Bus.instance.on<RemoteSyncEvent>().listen((event) {
setState(() {
_hasSyncErrors = !event.success;
});
});
widget.selectedFiles.addListener(() {
setState(() {});
});
_userAuthEventSubscription =
Bus.instance.on<UserAuthenticatedEvent>().listen((event) {
setState(() {});
});
super.initState();
}
@override
void dispose() {
_userAuthEventSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (widget.selectedFiles.files.isEmpty) {
@ -80,13 +86,30 @@ 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),
icon: Icon(Icons.sync_disabled),
onPressed: () {
_openSyncConfiguration(context);
var page;
if (Configuration.instance.getToken() == null) {
page = EmailEntryPage();
} else {
// No key
if (Configuration.instance.getEncryptedKey() != null) {
// Yet to decrypt the key
page = PassphraseReentryPage();
} else {
// Never had a key
page = PassphraseEntryPage();
}
}
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
);
},
));
} else if (widget.type == GalleryAppBarType.local_folder &&
@ -178,21 +201,4 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
void _clearSelectedFiles() {
widget.selectedFiles.clearAll();
}
void _openSyncConfiguration(BuildContext context) {
final page = SetupPage();
Navigator.of(context).push(
MaterialPageRoute(
settings: RouteSettings(name: "/setup"),
builder: (BuildContext context) {
return page;
},
),
);
}
void dispose() {
_subscription.cancel();
super.dispose();
}
}

View file

@ -2,7 +2,9 @@ import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/models/filters/important_items_filter.dart';
@ -18,9 +20,11 @@ import 'package:photos/ui/loading_widget.dart';
import 'package:photos/ui/memories_widget.dart';
import 'package:photos/ui/remote_folder_gallery_widget.dart';
import 'package:photos/ui/search_page.dart';
import 'package:photos/user_authenticator.dart';
import 'package:photos/utils/logging_util.dart';
import 'package:shake/shake.dart';
import 'package:logging/logging.dart';
import 'package:uni_links/uni_links.dart';
class HomeWidget extends StatefulWidget {
final String title;
@ -56,6 +60,7 @@ class _HomeWidgetState extends State<HomeWidget> {
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
setState(() {});
});
_initDeepLinks();
super.initState();
}
@ -100,6 +105,44 @@ class _HomeWidgetState extends State<HomeWidget> {
);
}
Future<bool> _initDeepLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
String initialLink = await getInitialLink();
// Parse the link and warn the user, if it is not correct,
// but keep in mind it could be `null`.
if (initialLink != null) {
_logger.info("Initial link received: " + initialLink);
_getCredentials(context, initialLink);
return true;
} else {
_logger.info("No initial link received.");
}
} on PlatformException {
// Handle exception by warning the user their action did not succeed
// return?
_logger.severe("PlatformException thrown while getting initial link");
}
// Attach a listener to the stream
getLinksStream().listen((String link) {
_logger.info("Link received: " + link);
_getCredentials(context, link);
}, onError: (err) {
_logger.severe(err);
});
return false;
}
void _getCredentials(BuildContext context, String link) {
if (Configuration.instance.hasConfiguredAccount()) {
return;
}
final ott = Uri.parse(link).queryParameters["ott"];
_logger.info("Ott: " + ott);
UserAuthenticator.instance.getCredentials(context, ott);
}
Widget _getMainGalleryWidget() {
return FutureBuilder(
future: FileRepository.instance.loadFiles().then((files) {

View file

@ -0,0 +1,123 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/ui/email_entry_page.dart';
import 'package:photos/user_authenticator.dart';
class OTTVerificationPage extends StatefulWidget {
OTTVerificationPage({Key key}) : super(key: key);
@override
_OTTVerificationPageState createState() => _OTTVerificationPageState();
}
class _OTTVerificationPageState extends State<OTTVerificationPage> {
final _verificationCodeController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Verify Email"),
),
body: _getBody(),
);
}
Widget _getBody() {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.fromLTRB(8, 40, 8, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [
SvgPicture.asset(
"assets/email_sent.svg",
width: 256,
height: 256,
),
Padding(padding: EdgeInsets.all(12)),
Text.rich(
TextSpan(
style: TextStyle(fontSize: 18),
children: <TextSpan>[
TextSpan(text: "We've sent a mail to "),
TextSpan(
text: Configuration.instance.getEmail(),
style: TextStyle(
color: Theme.of(context).accentColor,
)),
TextSpan(text: "."),
],
),
textAlign: TextAlign.center,
),
Padding(padding: EdgeInsets.all(12)),
Text(
"Please check your inbox (and spam) to complete verification.",
textAlign: TextAlign.center,
),
Padding(padding: EdgeInsets.all(12)),
TextFormField(
decoration: InputDecoration(
hintText: 'Tap to enter verification code',
contentPadding: EdgeInsets.all(20),
),
controller: _verificationCodeController,
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, _verificationCodeController.text);
},
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
child: Text(
"Verify",
),
color: Colors.pink,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
)),
Padding(padding: EdgeInsets.all(8)),
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return EmailEntryPage();
},
),
);
},
child: Text(
"Did not get email?",
style: TextStyle(
decoration: TextDecoration.underline,
fontSize: 12,
),
)),
],
),
),
);
}
}

View file

@ -0,0 +1,160 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/svg.dart';
import 'package:photos/user_authenticator.dart';
import 'package:photos/utils/dialog_util.dart';
class PassphraseEntryPage extends StatefulWidget {
PassphraseEntryPage({Key key}) : super(key: key);
@override
_PassphraseEntryPageState createState() => _PassphraseEntryPageState();
}
class _PassphraseEntryPageState extends State<PassphraseEntryPage> {
final _passphraseController1 = TextEditingController(),
_passphraseController2 = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Icon(Icons.lock),
title: Text("Encryption Passphrase"),
),
body: _getBody(),
);
}
Widget _getBody() {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.fromLTRB(16, 40, 16, 16),
child: Column(
children: [
SvgPicture.asset(
"assets/vault.svg",
width: 196,
height: 196,
),
Padding(padding: EdgeInsets.all(12)),
Text(
"Please enter a passphrase that we can use to encrypt your data.",
textAlign: TextAlign.center,
),
Padding(padding: EdgeInsets.all(4)),
Text.rich(
TextSpan(
children: <TextSpan>[
TextSpan(
text:
"We don't store your passphrase, so if you forget, "),
TextSpan(
text: "we will not be able to help you",
style: TextStyle(
decoration: TextDecoration.underline,
fontWeight: FontWeight.bold,
)),
TextSpan(text: " recover your data."),
],
),
textAlign: TextAlign.center,
),
Padding(padding: EdgeInsets.all(12)),
TextFormField(
decoration: InputDecoration(
hintText: "something you'll never forget",
contentPadding: EdgeInsets.all(20),
),
controller: _passphraseController1,
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
onChanged: (_) {
setState(() {});
},
),
Padding(padding: EdgeInsets.all(8)),
TextFormField(
decoration: InputDecoration(
hintText: "something you'll never ever forget",
contentPadding: EdgeInsets.all(20),
),
controller: _passphraseController2,
autofocus: false,
autocorrect: false,
obscureText: true,
keyboardType: TextInputType.visiblePassword,
onChanged: (_) {
setState(() {});
},
),
Padding(padding: EdgeInsets.all(8)),
SizedBox(
width: double.infinity,
child: RaisedButton(
onPressed: _passphraseController1.text.isNotEmpty &&
_passphraseController2.text.isNotEmpty
? () {
if (_passphraseController1.text !=
_passphraseController2.text) {
showErrorDialog(context, "Uhm...",
"The passphrases you entered don't match.");
} else {
_showPassphraseConfirmationDialog();
}
}
: null,
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
child: Text("Set Passphrase"),
color: Theme.of(context).buttonColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
)),
],
),
),
);
}
void _showPassphraseConfirmationDialog() {
AlertDialog alert = AlertDialog(
title: Text("Confirmation"),
content: SingleChildScrollView(
child: Column(children: [
Text("The passphrase you are promising to never forget is"),
Padding(padding: EdgeInsets.all(8)),
Text(_passphraseController1.text,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 28,
)),
]),
),
actions: [
FlatButton(
child: Text("Change"),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: Text("Confirm"),
onPressed: () {
Navigator.of(context).pop();
UserAuthenticator.instance
.setPassphrase(context, _passphraseController1.text);
},
),
],
);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
}

View file

@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/svg.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/utils/dialog_util.dart';
class PassphraseReentryPage extends StatefulWidget {
PassphraseReentryPage({Key key}) : super(key: key);
@override
_PassphraseReentryPageState createState() => _PassphraseReentryPageState();
}
class _PassphraseReentryPageState extends State<PassphraseReentryPage> {
final _passphraseController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Icon(Icons.lock),
title: Text(
"Encryption Passphrase",
),
),
body: _getBody(),
);
}
Widget _getBody() {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.fromLTRB(16, 40, 16, 16),
child: Column(
children: [
SvgPicture.asset(
"assets/vault.svg",
width: 196,
height: 196,
),
Padding(padding: EdgeInsets.all(20)),
Text(
"Please enter your passphrase.",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
),
),
Padding(padding: EdgeInsets.all(12)),
TextFormField(
decoration: InputDecoration(
hintText: "that thing you promised to never forget",
contentPadding: EdgeInsets.all(20),
),
controller: _passphraseController,
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
onChanged: (_) {
setState(() {});
},
),
Padding(padding: EdgeInsets.all(12)),
SizedBox(
width: double.infinity,
child: RaisedButton(
onPressed: _passphraseController.text.isNotEmpty
? () async {
final dialog =
createProgressDialog(context, "Please wait...");
await dialog.show();
await Configuration.instance
.decryptEncryptedKey(_passphraseController.text);
await dialog.hide();
Bus.instance.fire(UserAuthenticatedEvent());
Navigator.of(context)
.popUntil((route) => route.isFirst);
}
: null,
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
child: Text("Set Passphrase"),
color: Theme.of(context).buttonColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
)),
],
),
),
);
}
}

View file

@ -1,9 +1,16 @@
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/ui/passphrase_entry_page.dart';
import 'package:photos/ui/passphrase_reentry_page.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/toast_util.dart';
class UserAuthenticator {
final _dio = Dio();
@ -14,6 +21,105 @@ 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);
}).then((response) async {
await dialog.hide();
if (response != null && response.statusCode == 200) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return OTTVerificationPage();
},
),
);
} else {
showGenericErrorDialog(context);
}
});
}
Future<void> getCredentials(BuildContext context, String ott) async {
final dialog = createProgressDialog(context, "Please wait...");
await dialog.show();
await Dio().get(
Configuration.instance.getHttpEndpoint() + "/users/credentials",
queryParameters: {
"email": Configuration.instance.getEmail(),
"ott": ott,
},
).catchError((e) async {
_logger.severe(e);
}).then((response) async {
await dialog.hide();
if (response != null && response.statusCode == 200) {
_saveConfiguration(response);
showToast("Email verification successful!");
var page;
if (Configuration.instance.getEncryptedKey() != null) {
page = PassphraseReentryPage();
} else {
page = PassphraseEntryPage();
}
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
(route) => route.isFirst,
);
} else {
showErrorDialog(
context, "Oops.", "Verification failed, please try again.");
}
});
}
Future<void> setPassphrase(BuildContext context, String passphrase) async {
final dialog = createProgressDialog(context, "Please wait...");
await dialog.show();
await Configuration.instance.generateAndSaveKey(passphrase);
await _dio
.put(
Configuration.instance.getHttpEndpoint() + "/users/encrypted-key",
data: {
"encryptedKey": Configuration.instance.getEncryptedKey(),
},
options: Options(
headers: {
"X-Auth-Token": Configuration.instance.getToken(),
},
),
)
.catchError((e) async {
await dialog.hide();
Configuration.instance.setKey(null);
Configuration.instance.setEncryptedKey(null);
_logger.severe(e);
showGenericErrorDialog(context);
}).then((response) async {
await dialog.hide();
if (response != null && response.statusCode == 200) {
Bus.instance.fire(UserAuthenticatedEvent());
Navigator.of(context).popUntil((route) => route.isFirst);
} else {
Configuration.instance.setKey(null);
Configuration.instance.setEncryptedKey(null);
showGenericErrorDialog(context);
}
});
}
@deprecated
Future<bool> login(String username, String password) {
return _dio.post(
Configuration.instance.getHttpEndpoint() + "/users/authenticate",
@ -22,7 +128,7 @@ class UserAuthenticator {
"password": password,
}).then((response) {
if (response.statusCode == 200 && response.data != null) {
_saveConfiguration(username, password, response);
_saveConfiguration(response);
Bus.instance.fire(UserAuthenticatedEvent());
return true;
} else {
@ -34,6 +140,7 @@ class UserAuthenticator {
});
}
@deprecated
Future<bool> create(String username, String password) {
return _dio
.post(Configuration.instance.getHttpEndpoint() + "/users", data: {
@ -41,7 +148,7 @@ class UserAuthenticator {
"password": password,
}).then((response) {
if (response.statusCode == 200 && response.data != null) {
_saveConfiguration(username, password, response);
_saveConfiguration(response);
return true;
} else {
if (response.data != null && response.data["message"] != null) {
@ -68,9 +175,7 @@ class UserAuthenticator {
);
}
void _saveConfiguration(String username, String password, Response response) {
Configuration.instance.setUsername(username);
Configuration.instance.setPassword(password);
void _saveConfiguration(Response response) {
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