diff --git a/lib/core/configuration.dart b/lib/core/configuration.dart index 86ed4340d..d43c1297c 100644 --- a/lib/core/configuration.dart +++ b/lib/core/configuration.dart @@ -134,6 +134,6 @@ class Configuration { } bool hasConfiguredAccount() { - return getEndpoint() != null && getToken() != null; + return getToken() != null && getKey() != null; } } diff --git a/lib/ui/gallery_app_bar_widget.dart b/lib/ui/gallery_app_bar_widget.dart index bc8ea40cf..54a1a10a7 100644 --- a/lib/ui/gallery_app_bar_widget.dart +++ b/lib/ui/gallery_app_bar_widget.dart @@ -2,10 +2,14 @@ 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/user_authenticated_event.dart'; import 'package:photos/file_repository.dart'; import 'package:photos/models/selected_files.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'; @@ -40,14 +44,25 @@ class GalleryAppBarWidget extends StatefulWidget } class _GalleryAppBarWidgetState extends State { + StreamSubscription _userAuthEventSubscription; @override void initState() { widget.selectedFiles.addListener(() { setState(() {}); }); + _userAuthEventSubscription = + Bus.instance.on().listen((event) { + setState(() {}); + }); super.initState(); } + @override + void dispose() { + _userAuthEventSubscription.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { if (widget.selectedFiles.files.isEmpty) { @@ -73,11 +88,28 @@ class _GalleryAppBarWidgetState extends State { List actions = List(); if (!Configuration.instance.hasConfiguredAccount()) { actions.add(IconButton( - icon: Icon(Configuration.instance.hasConfiguredAccount() - ? Icons.sync_problem - : Icons.sync_disabled), + icon: Icon(Icons.sync_disabled), onPressed: () { - _navigateToSignInPage(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 && @@ -169,14 +201,4 @@ class _GalleryAppBarWidgetState extends State { void _clearSelectedFiles() { widget.selectedFiles.clearAll(); } - - void _navigateToSignInPage(BuildContext context) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return EmailEntryPage(); - }, - ), - ); - } } diff --git a/lib/ui/passphrase_entry_page.dart b/lib/ui/passphrase_entry_page.dart index f9f994db1..3318a10b1 100644 --- a/lib/ui/passphrase_entry_page.dart +++ b/lib/ui/passphrase_entry_page.dart @@ -19,6 +19,7 @@ class _PassphraseEntryPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + leading: Icon(Icons.lock), title: Text("Encryption Passphrase"), ), body: _getBody(), diff --git a/lib/ui/passphrase_reentry_page.dart b/lib/ui/passphrase_reentry_page.dart new file mode 100644 index 000000000..f42098df8 --- /dev/null +++ b/lib/ui/passphrase_reentry_page.dart @@ -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 { + 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), + ), + )), + ], + ), + ), + ); + } +} diff --git a/lib/user_authenticator.dart b/lib/user_authenticator.dart index 5869a6fe3..9d75e0084 100644 --- a/lib/user_authenticator.dart +++ b/lib/user_authenticator.dart @@ -8,7 +8,9 @@ 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(); @@ -29,11 +31,9 @@ class UserAuthenticator { }, ).catchError((e) async { _logger.severe(e); - await dialog.hide(); - showGenericErrorDialog(context); }).then((response) async { await dialog.hide(); - if (response.statusCode == 200) { + if (response != null && response.statusCode == 200) { Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) { @@ -58,25 +58,25 @@ class UserAuthenticator { }, ).catchError((e) async { _logger.severe(e); - await dialog.hide(); - showGenericErrorDialog(context); }).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) { - // TODO: Passphrase re-enter to decrypt - Bus.instance.fire(UserAuthenticatedEvent()); - Navigator.of(context).popUntil((route) => route.isFirst); + page = PassphraseReentryPage(); } else { - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return PassphraseEntryPage(); - }, - ), - ); + page = PassphraseEntryPage(); } + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (BuildContext context) { + return page; + }, + ), + (route) => route.isFirst, + ); } else { showErrorDialog( context, "Oops.", "Verification failed, please try again.");