From af8834f7bdc777574bcfba44cea9715bc72d7ee2 Mon Sep 17 00:00:00 2001 From: mitchell Date: Fri, 5 Jul 2019 05:15:57 -0400 Subject: [PATCH] Added new config screen and buttons for navigation --- lib/main.dart | 14 ++- lib/repositories/config.dart | 14 ++- lib/screens/authentication.dart | 86 +++++++++-------- lib/screens/config.dart | 124 +++++++++++++++++++++++++ lib/screens/home.dart | 34 +++++-- lib/types/abstracts.dart | 2 + lib/widgets/obfuscated_text_field.dart | 28 ------ lib/widgets/text_field.dart | 49 ++++++++++ 8 files changed, 267 insertions(+), 84 deletions(-) create mode 100644 lib/screens/config.dart delete mode 100644 lib/widgets/obfuscated_text_field.dart create mode 100644 lib/widgets/text_field.dart diff --git a/lib/main.dart b/lib/main.dart index 4e26e49..b16f5a8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,10 +2,11 @@ import 'package:flutter/cupertino.dart'; import 'package:provider/provider.dart'; import 'repositories/credentials_client.dart'; -import 'repositories/config.dart'; +import 'repositories/config.dart' as repo; import 'screens/authentication.dart'; import 'screens/credentials.dart'; +import 'screens/config.dart'; import 'screens/home.dart'; import 'types/abstracts.dart'; @@ -16,7 +17,7 @@ class Selfpass extends StatelessWidget { @override Widget build(BuildContext context) { return Provider( - builder: (BuildContext context) => Config(), + builder: (BuildContext context) => repo.Config(), child: CupertinoApp( title: 'Selfpass', onGenerateRoute: (RouteSettings settings) { @@ -30,7 +31,7 @@ class Selfpass extends StatelessWidget { break; case '/home': - title = "Credential Hosts"; + title = 'Credential Hosts'; builder = (BuildContext context) => Provider( builder: (BuildContext context) => CredentialsClient(settings.arguments), @@ -39,10 +40,15 @@ class Selfpass extends StatelessWidget { break; case '/credentials': - title = "Credentials"; + title = 'Credentials'; builder = (BuildContext context) => Credentials(settings.arguments); break; + + case '/config': + title = 'Configuration'; + builder = (BuildContext context) => Config(settings.arguments); + break; } return CupertinoPageRoute(builder: builder, title: title); diff --git a/lib/repositories/config.dart b/lib/repositories/config.dart index aab0b72..01e04ec 100644 --- a/lib/repositories/config.dart +++ b/lib/repositories/config.dart @@ -54,8 +54,18 @@ class Config implements ConfigRepo { Future get connectionConfig async { _checkIfPasswordMatched(); - return ConnectionConfig.fromJson( - json.decode(await _storage.read(key: _keyConnectionConfig))); + final connConfig = await _storage.read(key: _keyConnectionConfig); + + if (connConfig == null) { + return null; + } + + return ConnectionConfig.fromJson(json.decode(connConfig)); + } + + Future deleteAll() { + _checkIfPasswordMatched(); + return _storage.deleteAll(); } void _checkIfPasswordMatched() { diff --git a/lib/screens/authentication.dart b/lib/screens/authentication.dart index eb85109..a65fecf 100644 --- a/lib/screens/authentication.dart +++ b/lib/screens/authentication.dart @@ -3,7 +3,7 @@ import 'package:provider/provider.dart'; import '../types/abstracts.dart'; -import '../widgets/obfuscated_text_field.dart'; +import '../widgets/text_field.dart'; class Authentication extends StatefulWidget { @override @@ -11,9 +11,10 @@ class Authentication extends StatefulWidget { } class _AuthenticationState extends State { + final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _confirmController = TextEditingController(); bool _invalid = false; bool _passesDontMatch = false; - String _masterpass; ConfigRepo _config; Future _passwordIsSet; @@ -21,14 +22,11 @@ class _AuthenticationState extends State { didChangeDependencies() { super.didChangeDependencies(); _config = Provider.of(context); + _passwordIsSet = _config.passwordSet; } @override Widget build(BuildContext context) { - if (_passwordIsSet == null) { - _passwordIsSet = _config.passwordSet; - } - return CupertinoPageScaffold( child: Container( margin: const EdgeInsets.symmetric(horizontal: 50.0), @@ -36,30 +34,34 @@ class _AuthenticationState extends State { future: _passwordIsSet, builder: (BuildContext context, AsyncSnapshot snapshot) => snapshot.connectionState == ConnectionState.done - ? Column(children: _buildColumnChildren(snapshot.data)) - : Container(), + ? Column( + children: _buildColumnChildren(context, snapshot.data)) + : Center(child: CupertinoActivityIndicator()), ), ), ); } - List _buildColumnChildren(bool passwordIsSet) { + List _buildColumnChildren(BuildContext context, bool passwordIsSet) { List children = [ const Spacer(flex: 4), const Flexible(child: Text('Master password:')), Flexible( - child: ObfuscatedTextField( - onSubmittedBuilder: - _buildMasterpassSubmittedBuilder(passwordIsSet)), + child: TextField( + obscure: true, + autofocus: true, + controller: _passwordController, + ), ), ]; if (!passwordIsSet) { children.add(const Flexible(child: Text('Re-enter password:'))); children.add(Flexible( - child: ObfuscatedTextField( - onSubmittedBuilder: - _buildConfirmPassSubmittedBuilder(passwordIsSet)), + child: TextField( + obscure: true, + controller: _confirmController, + ), )); } @@ -81,6 +83,11 @@ class _AuthenticationState extends State { )); } + children.add(CupertinoButton( + child: Text('Enter'), + onPressed: _buildSubmitPressedBuilder(context), + )); + if (_passesDontMatch) { children.add(const Spacer(flex: 1)); } else if (_invalid || !passwordIsSet) { @@ -92,40 +99,31 @@ class _AuthenticationState extends State { return children; } - OnSubmittedBuilder _buildMasterpassSubmittedBuilder(bool passwordIsSet) { - return (BuildContext context) { - return (String pass) async { - if (passwordIsSet) { - if (await _config.matchesPasswordHash(pass)) { - Navigator.of(context).pushReplacementNamed('/home', - arguments: await _config.connectionConfig); - return; - } - - this.setState(() => _invalid = true); + VoidCallback _buildSubmitPressedBuilder(BuildContext context) { + return () async { + if (await _passwordIsSet) { + if (await _config.matchesPasswordHash(_passwordController.text)) { + Navigator.of(context).pushReplacementNamed( + '/home', + arguments: await _config.connectionConfig, + ); return; } - _masterpass = pass; - }; - }; - } + this.setState(() => _invalid = true); + return; + } - OnSubmittedBuilder _buildConfirmPassSubmittedBuilder(bool passwordIsSet) { - return (BuildContext context) { - return (String pass) async { - if (pass != _masterpass) { - this.setState(() { - _passesDontMatch = true; - }); - return; - } + if (_passwordController.text != _confirmController.text) { + this.setState(() { + _passesDontMatch = true; + }); + return; + } - _config.setPassword(_masterpass); - _passwordIsSet = Future.value(true); - Navigator.of(context).pushReplacementNamed('/home', - arguments: await _config.connectionConfig); - }; + _config.setPassword(_passwordController.text); + _passwordIsSet = Future.value(true); + Navigator.of(context).pushReplacementNamed('/config'); }; } } diff --git a/lib/screens/config.dart b/lib/screens/config.dart new file mode 100644 index 0000000..c69fce5 --- /dev/null +++ b/lib/screens/config.dart @@ -0,0 +1,124 @@ +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + +import '../types/abstracts.dart'; +import '../types/connection_config.dart'; + +import '../widgets/text_field.dart'; + +class Config extends StatefulWidget { + final ConnectionConfig connectionConfig; + + const Config(this.connectionConfig, {Key key}) : super(key: key); + + @override + State createState() => _ConfigState(this.connectionConfig); +} + +class _ConfigState extends State { + TextEditingController _hostController; + TextEditingController _caCertController; + TextEditingController _certController; + TextEditingController _privateCertController; + ConnectionConfig _connectionConfig; + ConfigRepo _config; + + _ConfigState(this._connectionConfig) { + if (_connectionConfig == null) { + _connectionConfig = ConnectionConfig(); + } + + _hostController = TextEditingController(text: _connectionConfig.host); + _certController = + TextEditingController(text: _connectionConfig.certificate); + _caCertController = + TextEditingController(text: _connectionConfig.caCertificate); + _privateCertController = + TextEditingController(text: _connectionConfig.privateCertificate); + } + + @override + didChangeDependencies() async { + super.didChangeDependencies(); + + _config = Provider.of(context); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: _connectionConfig.host == null + ? null + : CupertinoNavigationBar( + trailing: GestureDetector( + onTap: _buildResetAllHandler(context), + child: Text('Reset App', + style: TextStyle(color: CupertinoColors.destructiveRed)), + ), + ), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 50.0), + child: Column(children: [ + Spacer(flex: 3), + Flexible(child: Text('Host:')), + Flexible(child: TextField(maxLines: 1, controller: _hostController)), + Flexible(child: Text('CA certificate:')), + Flexible( + child: TextField(maxLines: 3, controller: _caCertController)), + Flexible(child: Text('Client certificate:')), + Flexible(child: TextField(maxLines: 3, controller: _certController)), + Flexible(child: Text('Private certificate:')), + Flexible( + child: + TextField(maxLines: 3, controller: _privateCertController)), + CupertinoButton( + child: Text('Save'), onPressed: _makeSaveOnPressed(context)) + ]), + ), + ); + } + + GestureTapCallback _buildResetAllHandler(BuildContext context) { + return () { + showCupertinoDialog( + context: context, + builder: (BuildContext context) => CupertinoAlertDialog( + content: Text('Are you sure?'), + actions: [ + CupertinoDialogAction( + isDefaultAction: true, + child: Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), + ), + CupertinoDialogAction( + isDestructiveAction: true, + child: Text('Confirm'), + onPressed: () async { + _connectionConfig = null; + await _config.deleteAll(); + Navigator.of(context) + .pushNamedAndRemoveUntil('/', ModalRoute.withName('/')); + }, + ), + ], + ), + ); + }; + } + + VoidCallback _makeSaveOnPressed(BuildContext context) { + return () async { + final connConfig = ConnectionConfig( + host: _hostController.text, + certificate: _certController.text, + caCertificate: _caCertController.text, + privateCertificate: _privateCertController.text, + ); + + await _config.setConnectionConfig(connConfig); + + Navigator.of(context) + .pushReplacementNamed('/home', arguments: connConfig); + }; + } +} diff --git a/lib/screens/home.dart b/lib/screens/home.dart index f7e6643..619692f 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -13,20 +13,21 @@ class Home extends StatefulWidget { class _HomeState extends State { CredentialsRepo _client; + ConfigRepo _config; Future> _metadatas; @override didChangeDependencies() { super.didChangeDependencies(); + + _config = Provider.of(context); + _client = Provider.of(context); + _metadatas = _client.getAllMetadata('').toList(); } @override Widget build(BuildContext context) { - if (_metadatas == null) { - _metadatas = _client.getAllMetadata('').toList(); - } - return CupertinoPageScaffold( child: FutureBuilder>( future: _metadatas, @@ -35,9 +36,21 @@ class _HomeState extends State { (snapshot.connectionState == ConnectionState.done) ? TappableTextList( tappableText: _buildTappableText(context, snapshot.data)) - : Container(), + : Center(child: CupertinoActivityIndicator()), + ), + navigationBar: CupertinoNavigationBar( + leading: GestureDetector( + child: Align( + child: Text('Lock', + style: TextStyle(color: CupertinoColors.destructiveRed)), + alignment: Alignment(-0.9, 0)), + onTap: _makeLockOnTapHandler(context), + ), + trailing: GestureDetector( + child: Icon(CupertinoIcons.gear), + onTap: _makeConfigOnTapHandler(context), + ), ), - navigationBar: CupertinoNavigationBar(), ); } @@ -69,4 +82,13 @@ class _HomeState extends State { return tappableText; } + + GestureTapCallback _makeLockOnTapHandler(BuildContext context) { + return () => Navigator.of(context).pushReplacementNamed('/'); + } + + GestureTapCallback _makeConfigOnTapHandler(BuildContext context) { + return () async => Navigator.of(context) + .pushNamed('/config', arguments: await _config.connectionConfig); + } } diff --git a/lib/types/abstracts.dart b/lib/types/abstracts.dart index 5ac1041..358322d 100644 --- a/lib/types/abstracts.dart +++ b/lib/types/abstracts.dart @@ -21,4 +21,6 @@ abstract class ConfigRepo { Future setConnectionConfig(ConnectionConfig config); Future get connectionConfig; + + Future deleteAll(); } diff --git a/lib/widgets/obfuscated_text_field.dart b/lib/widgets/obfuscated_text_field.dart deleted file mode 100644 index d72a047..0000000 --- a/lib/widgets/obfuscated_text_field.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/cupertino.dart'; - -typedef OnSubmittedBuilder = ValueChanged Function( - BuildContext context, -); - -class ObfuscatedTextField extends StatelessWidget { - final OnSubmittedBuilder onSubmittedBuilder; - - const ObfuscatedTextField({this.onSubmittedBuilder}); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 5.0), - child: CupertinoTextField( - decoration: BoxDecoration( - border: Border.all(color: CupertinoColors.black), - borderRadius: const BorderRadius.all(Radius.circular(5.0)), - ), - clearButtonMode: OverlayVisibilityMode.editing, - textAlign: TextAlign.center, - onSubmitted: onSubmittedBuilder(context), - obscureText: true, - ), - ); - } -} diff --git a/lib/widgets/text_field.dart b/lib/widgets/text_field.dart new file mode 100644 index 0000000..ed52fa1 --- /dev/null +++ b/lib/widgets/text_field.dart @@ -0,0 +1,49 @@ +import 'package:flutter/cupertino.dart'; + +typedef OnSubmittedBuilder = ValueChanged Function( + BuildContext context, +); + +class TextField extends StatelessWidget { + final OnSubmittedBuilder onSubmittedBuilder; + final TextEditingController controller; + final bool obscure; + final bool autofocus; + final bool autocorrect; + final int minLines; + final int maxLines; + + const TextField({ + this.onSubmittedBuilder, + this.controller, + this.obscure = false, + this.autofocus = false, + this.minLines, + this.maxLines, + this.autocorrect = false, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: CupertinoTextField( + decoration: BoxDecoration( + border: Border.all(color: CupertinoColors.black), + borderRadius: const BorderRadius.all(Radius.circular(5.0)), + ), + clearButtonMode: OverlayVisibilityMode.editing, + textAlign: TextAlign.start, + onSubmitted: this.onSubmittedBuilder != null + ? onSubmittedBuilder(context) + : null, + controller: controller, + obscureText: obscure, + autofocus: autofocus, + autocorrect: autocorrect, + minLines: minLines, + maxLines: maxLines, + ), + ); + } +}