Added new config screen and buttons for navigation

This commit is contained in:
mitchell 2019-07-05 05:15:57 -04:00
parent d0505a4a58
commit af8834f7bd
8 changed files with 267 additions and 84 deletions

View File

@ -2,10 +2,11 @@ import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'repositories/credentials_client.dart'; import 'repositories/credentials_client.dart';
import 'repositories/config.dart'; import 'repositories/config.dart' as repo;
import 'screens/authentication.dart'; import 'screens/authentication.dart';
import 'screens/credentials.dart'; import 'screens/credentials.dart';
import 'screens/config.dart';
import 'screens/home.dart'; import 'screens/home.dart';
import 'types/abstracts.dart'; import 'types/abstracts.dart';
@ -16,7 +17,7 @@ class Selfpass extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Provider<ConfigRepo>( return Provider<ConfigRepo>(
builder: (BuildContext context) => Config(), builder: (BuildContext context) => repo.Config(),
child: CupertinoApp( child: CupertinoApp(
title: 'Selfpass', title: 'Selfpass',
onGenerateRoute: (RouteSettings settings) { onGenerateRoute: (RouteSettings settings) {
@ -30,7 +31,7 @@ class Selfpass extends StatelessWidget {
break; break;
case '/home': case '/home':
title = "Credential Hosts"; title = 'Credential Hosts';
builder = (BuildContext context) => Provider<CredentialsRepo>( builder = (BuildContext context) => Provider<CredentialsRepo>(
builder: (BuildContext context) => builder: (BuildContext context) =>
CredentialsClient(settings.arguments), CredentialsClient(settings.arguments),
@ -39,10 +40,15 @@ class Selfpass extends StatelessWidget {
break; break;
case '/credentials': case '/credentials':
title = "Credentials"; title = 'Credentials';
builder = builder =
(BuildContext context) => Credentials(settings.arguments); (BuildContext context) => Credentials(settings.arguments);
break; break;
case '/config':
title = 'Configuration';
builder = (BuildContext context) => Config(settings.arguments);
break;
} }
return CupertinoPageRoute(builder: builder, title: title); return CupertinoPageRoute(builder: builder, title: title);

View File

@ -54,8 +54,18 @@ class Config implements ConfigRepo {
Future<ConnectionConfig> get connectionConfig async { Future<ConnectionConfig> get connectionConfig async {
_checkIfPasswordMatched(); _checkIfPasswordMatched();
return ConnectionConfig.fromJson( final connConfig = await _storage.read(key: _keyConnectionConfig);
json.decode(await _storage.read(key: _keyConnectionConfig)));
if (connConfig == null) {
return null;
}
return ConnectionConfig.fromJson(json.decode(connConfig));
}
Future<void> deleteAll() {
_checkIfPasswordMatched();
return _storage.deleteAll();
} }
void _checkIfPasswordMatched() { void _checkIfPasswordMatched() {

View File

@ -3,7 +3,7 @@ import 'package:provider/provider.dart';
import '../types/abstracts.dart'; import '../types/abstracts.dart';
import '../widgets/obfuscated_text_field.dart'; import '../widgets/text_field.dart';
class Authentication extends StatefulWidget { class Authentication extends StatefulWidget {
@override @override
@ -11,9 +11,10 @@ class Authentication extends StatefulWidget {
} }
class _AuthenticationState extends State<Authentication> { class _AuthenticationState extends State<Authentication> {
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _confirmController = TextEditingController();
bool _invalid = false; bool _invalid = false;
bool _passesDontMatch = false; bool _passesDontMatch = false;
String _masterpass;
ConfigRepo _config; ConfigRepo _config;
Future<bool> _passwordIsSet; Future<bool> _passwordIsSet;
@ -21,14 +22,11 @@ class _AuthenticationState extends State<Authentication> {
didChangeDependencies() { didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
_config = Provider.of<ConfigRepo>(context); _config = Provider.of<ConfigRepo>(context);
_passwordIsSet = _config.passwordSet;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_passwordIsSet == null) {
_passwordIsSet = _config.passwordSet;
}
return CupertinoPageScaffold( return CupertinoPageScaffold(
child: Container( child: Container(
margin: const EdgeInsets.symmetric(horizontal: 50.0), margin: const EdgeInsets.symmetric(horizontal: 50.0),
@ -36,30 +34,34 @@ class _AuthenticationState extends State<Authentication> {
future: _passwordIsSet, future: _passwordIsSet,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) => builder: (BuildContext context, AsyncSnapshot<bool> snapshot) =>
snapshot.connectionState == ConnectionState.done snapshot.connectionState == ConnectionState.done
? Column(children: _buildColumnChildren(snapshot.data)) ? Column(
: Container(), children: _buildColumnChildren(context, snapshot.data))
: Center(child: CupertinoActivityIndicator()),
), ),
), ),
); );
} }
List<Widget> _buildColumnChildren(bool passwordIsSet) { List<Widget> _buildColumnChildren(BuildContext context, bool passwordIsSet) {
List<Widget> children = [ List<Widget> children = [
const Spacer(flex: 4), const Spacer(flex: 4),
const Flexible(child: Text('Master password:')), const Flexible(child: Text('Master password:')),
Flexible( Flexible(
child: ObfuscatedTextField( child: TextField(
onSubmittedBuilder: obscure: true,
_buildMasterpassSubmittedBuilder(passwordIsSet)), autofocus: true,
controller: _passwordController,
),
), ),
]; ];
if (!passwordIsSet) { if (!passwordIsSet) {
children.add(const Flexible(child: Text('Re-enter password:'))); children.add(const Flexible(child: Text('Re-enter password:')));
children.add(Flexible( children.add(Flexible(
child: ObfuscatedTextField( child: TextField(
onSubmittedBuilder: obscure: true,
_buildConfirmPassSubmittedBuilder(passwordIsSet)), controller: _confirmController,
),
)); ));
} }
@ -81,6 +83,11 @@ class _AuthenticationState extends State<Authentication> {
)); ));
} }
children.add(CupertinoButton(
child: Text('Enter'),
onPressed: _buildSubmitPressedBuilder(context),
));
if (_passesDontMatch) { if (_passesDontMatch) {
children.add(const Spacer(flex: 1)); children.add(const Spacer(flex: 1));
} else if (_invalid || !passwordIsSet) { } else if (_invalid || !passwordIsSet) {
@ -92,40 +99,31 @@ class _AuthenticationState extends State<Authentication> {
return children; return children;
} }
OnSubmittedBuilder _buildMasterpassSubmittedBuilder(bool passwordIsSet) { VoidCallback _buildSubmitPressedBuilder(BuildContext context) {
return (BuildContext context) { return () async {
return (String pass) async { if (await _passwordIsSet) {
if (passwordIsSet) { if (await _config.matchesPasswordHash(_passwordController.text)) {
if (await _config.matchesPasswordHash(pass)) { Navigator.of(context).pushReplacementNamed(
Navigator.of(context).pushReplacementNamed('/home', '/home',
arguments: await _config.connectionConfig); arguments: await _config.connectionConfig,
return; );
}
this.setState(() => _invalid = true);
return; return;
} }
_masterpass = pass; this.setState(() => _invalid = true);
}; return;
}; }
}
OnSubmittedBuilder _buildConfirmPassSubmittedBuilder(bool passwordIsSet) { if (_passwordController.text != _confirmController.text) {
return (BuildContext context) { this.setState(() {
return (String pass) async { _passesDontMatch = true;
if (pass != _masterpass) { });
this.setState(() { return;
_passesDontMatch = true; }
});
return;
}
_config.setPassword(_masterpass); _config.setPassword(_passwordController.text);
_passwordIsSet = Future<bool>.value(true); _passwordIsSet = Future<bool>.value(true);
Navigator.of(context).pushReplacementNamed('/home', Navigator.of(context).pushReplacementNamed('/config');
arguments: await _config.connectionConfig);
};
}; };
} }
} }

124
lib/screens/config.dart Normal file
View File

@ -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<Config> {
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<ConfigRepo>(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);
};
}
}

View File

@ -13,20 +13,21 @@ class Home extends StatefulWidget {
class _HomeState extends State<Home> { class _HomeState extends State<Home> {
CredentialsRepo _client; CredentialsRepo _client;
ConfigRepo _config;
Future<List<Metadata>> _metadatas; Future<List<Metadata>> _metadatas;
@override @override
didChangeDependencies() { didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
_config = Provider.of<ConfigRepo>(context);
_client = Provider.of<CredentialsRepo>(context); _client = Provider.of<CredentialsRepo>(context);
_metadatas = _client.getAllMetadata('').toList();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_metadatas == null) {
_metadatas = _client.getAllMetadata('').toList();
}
return CupertinoPageScaffold( return CupertinoPageScaffold(
child: FutureBuilder<List<Metadata>>( child: FutureBuilder<List<Metadata>>(
future: _metadatas, future: _metadatas,
@ -35,9 +36,21 @@ class _HomeState extends State<Home> {
(snapshot.connectionState == ConnectionState.done) (snapshot.connectionState == ConnectionState.done)
? TappableTextList( ? TappableTextList(
tappableText: _buildTappableText(context, snapshot.data)) 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<Home> {
return tappableText; 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);
}
} }

View File

@ -21,4 +21,6 @@ abstract class ConfigRepo {
Future<void> setConnectionConfig(ConnectionConfig config); Future<void> setConnectionConfig(ConnectionConfig config);
Future<ConnectionConfig> get connectionConfig; Future<ConnectionConfig> get connectionConfig;
Future<void> deleteAll();
} }

View File

@ -1,28 +0,0 @@
import 'package:flutter/cupertino.dart';
typedef OnSubmittedBuilder = ValueChanged<String> 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,
),
);
}
}

View File

@ -0,0 +1,49 @@
import 'package:flutter/cupertino.dart';
typedef OnSubmittedBuilder = ValueChanged<String> 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,
),
);
}
}