Add credential screen & screen arguments; refactor config screen

This commit is contained in:
mitchell 2019-07-07 00:14:09 -04:00
parent af8834f7bd
commit 910bdeae12
9 changed files with 246 additions and 31 deletions

View File

@ -5,11 +5,13 @@ import 'repositories/credentials_client.dart';
import 'repositories/config.dart' as repo;
import 'screens/authentication.dart';
import 'screens/credential.dart';
import 'screens/credentials.dart';
import 'screens/config.dart';
import 'screens/home.dart';
import 'types/abstracts.dart';
import 'types/screen_arguments.dart';
void main() => runApp(Selfpass());
@ -31,23 +33,37 @@ class Selfpass extends StatelessWidget {
break;
case '/home':
title = 'Credential Hosts';
title = 'Hosts';
builder = (BuildContext context) => Provider<CredentialsRepo>(
builder: (BuildContext context) =>
CredentialsClient(settings.arguments),
CredentialsClient.cached(config: settings.arguments),
child: Home(),
);
break;
case '/credentials':
title = 'Credentials';
builder =
(BuildContext context) => Credentials(settings.arguments);
builder = (BuildContext context) => Provider<CredentialsRepo>(
builder: (BuildContext context) =>
CredentialsClient.cached(),
child: Credentials(settings.arguments),
);
break;
case '/credential':
title = 'Credential';
builder = (BuildContext context) => Provider<CredentialsRepo>(
builder: (BuildContext context) =>
CredentialsClient.cached(),
child: Credential(settings.arguments),
);
break;
case '/config':
final ConfigScreenArguments arguments = settings.arguments;
title = 'Configuration';
builder = (BuildContext context) => Config(settings.arguments);
builder = (BuildContext context) =>
Config(arguments.connectionConfig, arguments.privateKey);
break;
}

View File

@ -12,6 +12,7 @@ import '../types/connection_config.dart';
import '../types/credential.dart';
class CredentialsClient implements CredentialsRepo {
static CredentialsClient _cached;
grpc.CredentialServiceClient _client;
CredentialsClient(ConnectionConfig config) {
@ -32,6 +33,9 @@ class CredentialsClient implements CredentialsRepo {
));
}
factory CredentialsClient.cached({ConnectionConfig config}) =>
_cached == null ? _cached = CredentialsClient(config) : _cached;
Stream<Metadata> getAllMetadata(String sourceHost) {
final request = grpc.GetAllMetadataRequest();
request.sourceHost = sourceHost;

View File

@ -48,6 +48,7 @@ class _AuthenticationState extends State<Authentication> {
const Flexible(child: Text('Master password:')),
Flexible(
child: TextField(
maxLines: 1,
obscure: true,
autofocus: true,
controller: _passwordController,
@ -59,6 +60,7 @@ class _AuthenticationState extends State<Authentication> {
children.add(const Flexible(child: Text('Re-enter password:')));
children.add(Flexible(
child: TextField(
maxLines: 1,
obscure: true,
controller: _confirmController,
),
@ -85,7 +87,7 @@ class _AuthenticationState extends State<Authentication> {
children.add(CupertinoButton(
child: Text('Enter'),
onPressed: _buildSubmitPressedBuilder(context),
onPressed: _buildEnterPressedBuilder(context),
));
if (_passesDontMatch) {
@ -99,7 +101,7 @@ class _AuthenticationState extends State<Authentication> {
return children;
}
VoidCallback _buildSubmitPressedBuilder(BuildContext context) {
VoidCallback _buildEnterPressedBuilder(BuildContext context) {
return () async {
if (await _passwordIsSet) {
if (await _config.matchesPasswordHash(_passwordController.text)) {

View File

@ -8,11 +8,13 @@ import '../widgets/text_field.dart';
class Config extends StatefulWidget {
final ConnectionConfig connectionConfig;
final String privateKey;
const Config(this.connectionConfig, {Key key}) : super(key: key);
const Config(this.connectionConfig, this.privateKey, {Key key})
: super(key: key);
@override
State createState() => _ConfigState(this.connectionConfig);
State createState() => _ConfigState(this.connectionConfig, this.privateKey);
}
class _ConfigState extends State<Config> {
@ -20,10 +22,12 @@ class _ConfigState extends State<Config> {
TextEditingController _caCertController;
TextEditingController _certController;
TextEditingController _privateCertController;
TextEditingController _privateKeyController;
ConnectionConfig _connectionConfig;
String _privateKey;
ConfigRepo _config;
_ConfigState(this._connectionConfig) {
_ConfigState(this._connectionConfig, this._privateKey) {
if (_connectionConfig == null) {
_connectionConfig = ConnectionConfig();
}
@ -35,6 +39,8 @@ class _ConfigState extends State<Config> {
TextEditingController(text: _connectionConfig.caCertificate);
_privateCertController =
TextEditingController(text: _connectionConfig.privateCertificate);
_privateKeyController = TextEditingController(text: _privateKey);
}
@override
@ -57,22 +63,26 @@ class _ConfigState extends State<Config> {
),
),
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)),
margin: const EdgeInsets.symmetric(horizontal: 30),
child: ListView(children: [
Container(margin: EdgeInsets.only(top: 10), child: Text('Host:')),
TextField(maxLines: 1, controller: _hostController),
Container(
margin: EdgeInsets.only(top: 5), child: Text('Private key:')),
TextField(maxLines: 1, controller: _privateKeyController),
Container(
margin: EdgeInsets.only(top: 5), child: Text('CA certificate:')),
TextField(maxLines: 5, controller: _caCertController),
Container(
margin: EdgeInsets.only(top: 5),
child: Text('Client certificate:')),
TextField(maxLines: 5, controller: _certController),
Container(
margin: EdgeInsets.only(top: 5),
child: Text('Private certificate:')),
TextField(maxLines: 5, controller: _privateCertController),
CupertinoButton(
child: Text('Save'), onPressed: _makeSaveOnPressed(context))
child: Text('Save'), onPressed: _makeSaveOnPressed(context)),
]),
),
);
@ -116,6 +126,7 @@ class _ConfigState extends State<Config> {
);
await _config.setConnectionConfig(connConfig);
await _config.setPrivateKey(_privateKeyController.text);
Navigator.of(context)
.pushReplacementNamed('/home', arguments: connConfig);

155
lib/screens/credential.dart Normal file
View File

@ -0,0 +1,155 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import '../types/credential.dart' as types;
import '../widgets/text_field.dart';
class Credential extends StatefulWidget {
final types.Credential credential;
const Credential(this.credential, {Key key}) : super(key: key);
@override
State createState() => _CredentialState(credential);
}
class _CredentialState extends State<Credential> {
_CredentialControllers _controllers;
Map<String, _FieldBuildConfig> _fieldMap;
types.Credential _credential;
_CredentialState(this._credential) : super() {
_controllers = _CredentialControllers.fromCredential(_credential);
_fieldMap = _buildFieldMap(_controllers, _credential);
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Container(
margin: const EdgeInsets.only(top: 30, left: 30),
child: ListView(
children: _buildFieldRows(context),
),
),
);
}
Map<String, _FieldBuildConfig> _buildFieldMap(
_CredentialControllers controllers,
types.Credential credential,
) {
final fieldMap = {
'Id:': _FieldBuildConfig(mutable: false, text: credential.meta.id),
'Created: ': _FieldBuildConfig(
mutable: false,
copyable: false,
text: credential.meta.createdAt.toString(),
),
'Updated: ': _FieldBuildConfig(
mutable: false,
copyable: false,
text: credential.meta.updatedAt.toString(),
),
'Host:': _FieldBuildConfig(controller: controllers.sourceHost),
'Primary:': _FieldBuildConfig(controller: controllers.primary),
};
if (credential.meta.tag != null && credential.meta.tag != '') {
fieldMap['Tag'] = _FieldBuildConfig(controller: controllers.tag);
}
if (credential.username != null && credential.username != '') {
fieldMap['User:'] = _FieldBuildConfig(controller: controllers.username);
}
if (credential.email != null && credential.email != '') {
fieldMap['Email:'] = _FieldBuildConfig(controller: controllers.email);
}
return fieldMap;
}
List<Widget> _buildFieldRows(BuildContext context) {
List<Widget> rows = [];
_fieldMap.forEach((key, value) {
rows.add(Container(
margin: EdgeInsets.only(top: 10),
child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)),
));
final List<Widget> widgets = [
Expanded(
flex: 3,
child: value.mutable
? TextField(maxLines: 1, controller: value.controller)
: Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Text(value.text),
),
),
];
if (value.copyable) {
widgets.add(Flexible(
child: CupertinoButton(
child: Text('Copy'),
onPressed: () => Clipboard.setData(ClipboardData(
text: value.mutable ? value.controller.text : value.text,
)),
),
));
}
rows.add(Row(children: widgets));
});
return rows;
}
}
class _FieldBuildConfig {
final TextEditingController controller;
final String text;
final bool mutable;
final bool copyable;
const _FieldBuildConfig({
this.mutable = true,
this.copyable = true,
this.controller,
this.text,
});
}
class _CredentialControllers {
final TextEditingController sourceHost;
final TextEditingController primary;
final TextEditingController tag;
final TextEditingController username;
final TextEditingController email;
final TextEditingController password;
final TextEditingController otpSecret;
const _CredentialControllers({
this.sourceHost,
this.primary,
this.tag,
this.username,
this.email,
this.password,
this.otpSecret,
});
factory _CredentialControllers.fromCredential(types.Credential credential) =>
_CredentialControllers(
sourceHost: TextEditingController(text: credential.meta.sourceHost),
primary: TextEditingController(text: credential.meta.primary),
tag: TextEditingController(text: credential.meta.tag),
username: TextEditingController(text: credential.username),
email: TextEditingController(text: credential.email),
);
}

View File

@ -1,5 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import '../types/abstracts.dart';
import '../types/credential.dart';
import '../widgets/tappable_text_list.dart';
@ -18,12 +20,21 @@ class Credentials extends StatelessWidget {
}
Map<String, GestureTapCallback> _buildTappableText(BuildContext context) {
var handleOnTap = () {};
var makeOnTapHandler = (String id) => () async {
final credential =
await Provider.of<CredentialsRepo>(context).get(id);
Navigator.of(context).pushNamed('/credential', arguments: credential);
};
Map<String, GestureTapCallback> tappableText = {};
metadatas.forEach(
(Metadata metadata) => tappableText[metadata.id] = handleOnTap);
metadatas.forEach((Metadata metadata) {
var primary = metadata.primary;
if (metadata.tag != null && metadata.tag != '') {
primary += "-" + metadata.tag;
}
tappableText[primary] = makeOnTapHandler(metadata.id);
});
return tappableText;
}

View File

@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import '../types/abstracts.dart';
import '../types/credential.dart';
import '../types/screen_arguments.dart';
import '../widgets/tappable_text_list.dart';
@ -72,7 +73,7 @@ class _HomeState extends State<Home> {
}
}
final handleOnTap = (List<Metadata> metadatas) => () =>
final handleOnTap = (List<Metadata> metadatas) => () async =>
Navigator.of(context).pushNamed('/credentials', arguments: metadatas);
final Map<String, GestureTapCallback> tappableText = {};
@ -88,7 +89,8 @@ class _HomeState extends State<Home> {
}
GestureTapCallback _makeConfigOnTapHandler(BuildContext context) {
return () async => Navigator.of(context)
.pushNamed('/config', arguments: await _config.connectionConfig);
return () async => Navigator.of(context).pushNamed('/config',
arguments: ConfigScreenArguments(
await _config.connectionConfig, await _config.privateKey));
}
}

View File

@ -0,0 +1,8 @@
import 'connection_config.dart';
class ConfigScreenArguments {
final ConnectionConfig connectionConfig;
final String privateKey;
const ConfigScreenArguments(this.connectionConfig, this.privateKey);
}

View File

@ -7,6 +7,8 @@ typedef OnSubmittedBuilder = ValueChanged<String> Function(
class TextField extends StatelessWidget {
final OnSubmittedBuilder onSubmittedBuilder;
final TextEditingController controller;
final Widget prefix;
final Widget suffix;
final bool obscure;
final bool autofocus;
final bool autocorrect;
@ -21,6 +23,8 @@ class TextField extends StatelessWidget {
this.minLines,
this.maxLines,
this.autocorrect = false,
this.prefix,
this.suffix,
});
@override
@ -43,6 +47,8 @@ class TextField extends StatelessWidget {
autocorrect: autocorrect,
minLines: minLines,
maxLines: maxLines,
prefix: prefix,
suffix: suffix,
),
);
}