mirror of
https://github.com/mitchell/selfpass.git
synced 2025-12-14 21:27:22 +00:00
Add 'client/' from commit '92f38b5810'
git-subtree-dir: client git-subtree-mainline:024017338egit-subtree-split:92f38b5810
This commit is contained in:
commit
66ec035ee0
90 changed files with 3817 additions and 0 deletions
129
client/lib/screens/authentication.dart
Normal file
129
client/lib/screens/authentication.dart
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../types/abstracts.dart';
|
||||
|
||||
import '../widgets/text_field.dart';
|
||||
|
||||
class Authentication extends StatefulWidget {
|
||||
@override
|
||||
_AuthenticationState createState() => _AuthenticationState();
|
||||
}
|
||||
|
||||
class _AuthenticationState extends State<Authentication> {
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
final TextEditingController _confirmController = TextEditingController();
|
||||
bool _invalid = false;
|
||||
bool _passesDontMatch = false;
|
||||
ConfigRepo _config;
|
||||
Future<bool> _passwordIsSet;
|
||||
|
||||
@override
|
||||
didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_config = Provider.of<ConfigRepo>(context);
|
||||
_passwordIsSet = _config.passwordIsSet;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CupertinoPageScaffold(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 50.0),
|
||||
child: FutureBuilder<bool>(
|
||||
future: _passwordIsSet,
|
||||
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) =>
|
||||
snapshot.connectionState == ConnectionState.done
|
||||
? Column(
|
||||
children: _buildColumnChildren(context, snapshot.data))
|
||||
: Center(child: CupertinoActivityIndicator()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildColumnChildren(BuildContext context, bool passwordIsSet) {
|
||||
List<Widget> children = [
|
||||
const Spacer(flex: 4),
|
||||
const Flexible(child: Text('Master password:')),
|
||||
Flexible(
|
||||
child: TextField(
|
||||
maxLines: 1,
|
||||
obscure: true,
|
||||
autofocus: true,
|
||||
controller: _passwordController,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
if (!passwordIsSet) {
|
||||
children.add(const Flexible(child: Text('Re-enter password:')));
|
||||
children.add(Flexible(
|
||||
child: TextField(
|
||||
maxLines: 1,
|
||||
obscure: true,
|
||||
controller: _confirmController,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if (_invalid) {
|
||||
children.add(const Flexible(
|
||||
child: Text(
|
||||
'invalid masterpass',
|
||||
style: TextStyle(color: CupertinoColors.destructiveRed),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if (_passesDontMatch) {
|
||||
children.add(const Flexible(
|
||||
child: Text(
|
||||
'passwords don\'t match',
|
||||
style: TextStyle(color: CupertinoColors.destructiveRed),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
children.add(CupertinoButton(
|
||||
child: Text('Enter'),
|
||||
onPressed: _buildEnterPressedBuilder(context),
|
||||
));
|
||||
|
||||
if (_passesDontMatch) {
|
||||
children.add(const Spacer(flex: 1));
|
||||
} else if (_invalid || !passwordIsSet) {
|
||||
children.add(const Spacer(flex: 2));
|
||||
} else {
|
||||
children.add(const Spacer(flex: 3));
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
VoidCallback _buildEnterPressedBuilder(BuildContext context) {
|
||||
return () async {
|
||||
if (await _passwordIsSet) {
|
||||
if (await _config.matchesPasswordHash(_passwordController.text)) {
|
||||
Navigator.of(context).pushReplacementNamed('/home',
|
||||
arguments: await _config.connectionConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(() => _invalid = true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_passwordController.text != _confirmController.text) {
|
||||
this.setState(() {
|
||||
_passesDontMatch = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_config.setPassword(_passwordController.text);
|
||||
_passwordIsSet = Future<bool>.value(true);
|
||||
Navigator.of(context).pushReplacementNamed('/config');
|
||||
};
|
||||
}
|
||||
}
|
||||
135
client/lib/screens/config.dart
Normal file
135
client/lib/screens/config.dart
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
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;
|
||||
final String privateKey;
|
||||
|
||||
const Config(this.connectionConfig, this.privateKey, {Key key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State createState() => _ConfigState(this.connectionConfig, this.privateKey);
|
||||
}
|
||||
|
||||
class _ConfigState extends State<Config> {
|
||||
TextEditingController _hostController;
|
||||
TextEditingController _caCertController;
|
||||
TextEditingController _certController;
|
||||
TextEditingController _privateCertController;
|
||||
TextEditingController _privateKeyController;
|
||||
ConnectionConfig _connectionConfig;
|
||||
String _privateKey;
|
||||
ConfigRepo _config;
|
||||
|
||||
_ConfigState(this._connectionConfig, this._privateKey) {
|
||||
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);
|
||||
|
||||
_privateKeyController = TextEditingController(text: _privateKey);
|
||||
}
|
||||
|
||||
@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: 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)),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
await _config.setPrivateKey(_privateKeyController.text);
|
||||
|
||||
Navigator.of(context)
|
||||
.pushReplacementNamed('/home', arguments: connConfig);
|
||||
};
|
||||
}
|
||||
}
|
||||
177
client/lib/screens/credential.dart
Normal file
177
client/lib/screens/credential.dart
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:otp/otp.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();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 15, bottom: 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);
|
||||
}
|
||||
|
||||
fieldMap['Password:'] =
|
||||
_FieldBuildConfig(controller: controllers.password, obscured: true);
|
||||
|
||||
if (credential.otpSecret != null && credential.otpSecret != '') {
|
||||
fieldMap['OTP Secret:'] = _FieldBuildConfig(
|
||||
controller: controllers.otpSecret, obscured: true, otp: true);
|
||||
}
|
||||
|
||||
return fieldMap;
|
||||
}
|
||||
|
||||
List<Widget> _buildFieldRows(BuildContext context) {
|
||||
List<Widget> rows = [];
|
||||
|
||||
_controllers = _CredentialControllers.fromCredential(_credential);
|
||||
_fieldMap = _buildFieldMap(_controllers, _credential);
|
||||
|
||||
_fieldMap.forEach((key, value) {
|
||||
rows.add(Container(
|
||||
margin: EdgeInsets.only(top: 2.5),
|
||||
child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)),
|
||||
));
|
||||
|
||||
final List<Widget> widgets = [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: value.mutable
|
||||
? TextField(
|
||||
maxLines: 1,
|
||||
controller: value.controller,
|
||||
obscure: value.obscured)
|
||||
: Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(value.text)),
|
||||
),
|
||||
];
|
||||
|
||||
if (value.copyable) {
|
||||
widgets.add(Expanded(
|
||||
child: CupertinoButton(
|
||||
child: Text(value.otp ? 'OTP' : 'Copy'),
|
||||
onPressed: () => Clipboard.setData(ClipboardData(
|
||||
text: value.otp
|
||||
? OTP
|
||||
.generateTOTPCode(value.controller.text,
|
||||
DateTime.now().millisecondsSinceEpoch)
|
||||
.toString()
|
||||
: 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;
|
||||
final bool obscured;
|
||||
final bool otp;
|
||||
|
||||
const _FieldBuildConfig({
|
||||
this.mutable = true,
|
||||
this.copyable = true,
|
||||
this.obscured = false,
|
||||
this.otp = false,
|
||||
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),
|
||||
password: TextEditingController(text: credential.password),
|
||||
otpSecret: TextEditingController(text: credential.otpSecret),
|
||||
);
|
||||
}
|
||||
72
client/lib/screens/credentials.dart
Normal file
72
client/lib/screens/credentials.dart
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../types/abstracts.dart';
|
||||
import '../types/credential.dart';
|
||||
|
||||
import '../utils/crypto.dart' as crypto;
|
||||
|
||||
import '../widgets/tappable_text_list.dart';
|
||||
|
||||
class Credentials extends StatelessWidget {
|
||||
final List<Metadata> metadatas;
|
||||
|
||||
const Credentials(this.metadatas);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CupertinoPageScaffold(
|
||||
child: TappableTextList(tappableText: _buildTappableText(context)),
|
||||
navigationBar: CupertinoNavigationBar(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, GestureTapCallback> _buildTappableText(BuildContext context) {
|
||||
final makeOnTapHandler = (String id) => () async {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => CupertinoAlertDialog(
|
||||
content: Column(
|
||||
children: [
|
||||
Text('Decrypting credential...'),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 10),
|
||||
child: CupertinoActivityIndicator()),
|
||||
],
|
||||
)),
|
||||
);
|
||||
|
||||
final config = Provider.of<ConfigRepo>(context);
|
||||
final client = Provider.of<CredentialsRepo>(context);
|
||||
|
||||
final Future<String> privateKey = config.privateKey;
|
||||
final String password = config.password;
|
||||
|
||||
final credential = await client.get(id);
|
||||
|
||||
credential.password = crypto.decryptPassword(
|
||||
password, await privateKey, credential.password);
|
||||
|
||||
if (credential.otpSecret != null && credential.otpSecret != '') {
|
||||
credential.otpSecret = crypto.decryptPassword(
|
||||
password, await privateKey, credential.otpSecret);
|
||||
}
|
||||
|
||||
Navigator.of(context)
|
||||
..pop()
|
||||
..pushNamed('/credential', arguments: credential);
|
||||
};
|
||||
|
||||
Map<String, GestureTapCallback> tappableText = {};
|
||||
|
||||
metadatas.forEach((Metadata metadata) {
|
||||
var primary = metadata.primary;
|
||||
if (metadata.tag != null && metadata.tag != '') {
|
||||
primary += "-" + metadata.tag;
|
||||
}
|
||||
tappableText[primary] = makeOnTapHandler(metadata.id);
|
||||
});
|
||||
|
||||
return tappableText;
|
||||
}
|
||||
}
|
||||
139
client/lib/screens/home.dart
Normal file
139
client/lib/screens/home.dart
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../types/abstracts.dart';
|
||||
import '../types/credential.dart';
|
||||
import '../types/screen_arguments.dart';
|
||||
|
||||
import '../widgets/tappable_text_list.dart';
|
||||
|
||||
class Home extends StatefulWidget {
|
||||
const Home({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State createState() => _HomeState();
|
||||
}
|
||||
|
||||
class _HomeState extends State<Home> with WidgetsBindingObserver {
|
||||
CredentialsRepo _client;
|
||||
ConfigRepo _config;
|
||||
Future<List<Metadata>> _metadatas;
|
||||
bool _stateIsPaused = false;
|
||||
Timer _pausedStateTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
_config = Provider.of<ConfigRepo>(context);
|
||||
_client = Provider.of<CredentialsRepo>(context);
|
||||
|
||||
_metadatas = _client.getAllMetadata('').toList();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
_stateIsPaused = state == AppLifecycleState.paused;
|
||||
|
||||
if (_stateIsPaused) {
|
||||
_pausedStateTimer = _newPausedStateTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pausedStateTimer != null) _pausedStateTimer.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CupertinoPageScaffold(
|
||||
child: FutureBuilder<List<Metadata>>(
|
||||
future: _metadatas,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
AsyncSnapshot<List<Metadata>> snapshot,
|
||||
) =>
|
||||
(snapshot.connectionState == ConnectionState.done)
|
||||
? TappableTextList(
|
||||
tappableText: _buildTappableText(context, snapshot.data))
|
||||
: 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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
if (_pausedStateTimer != null) _pausedStateTimer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Timer _newPausedStateTimer() {
|
||||
const checkPeriod = 30;
|
||||
|
||||
return Timer(Duration(seconds: checkPeriod), () {
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil('/', ModalRoute.withName('/home'));
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, GestureTapCallback> _buildTappableText(
|
||||
BuildContext context,
|
||||
List<Metadata> metadatas,
|
||||
) {
|
||||
final Map<String, List<Metadata>> metaMap = {};
|
||||
|
||||
metadatas.sort((a, b) => a.id.compareTo(b.id));
|
||||
|
||||
for (var metadata in metadatas) {
|
||||
final source = metadata.sourceHost;
|
||||
|
||||
if (metaMap[source] == null) {
|
||||
metaMap[source] = [metadata];
|
||||
} else {
|
||||
metaMap[source].add(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
final handleOnTap = (List<Metadata> metadatas) => () async =>
|
||||
Navigator.of(context).pushNamed('/credentials', arguments: metadatas);
|
||||
|
||||
final Map<String, GestureTapCallback> tappableText = {};
|
||||
|
||||
metaMap.forEach((String key, List<Metadata> value) =>
|
||||
tappableText[key] = handleOnTap(value));
|
||||
|
||||
return tappableText;
|
||||
}
|
||||
|
||||
GestureTapCallback _makeLockOnTapHandler(BuildContext context) {
|
||||
return () => Navigator.of(context).pushReplacementNamed('/');
|
||||
}
|
||||
|
||||
GestureTapCallback _makeConfigOnTapHandler(BuildContext context) {
|
||||
return () async => Navigator.of(context).pushNamed('/config',
|
||||
arguments: ConfigScreenArguments(
|
||||
connectionConfig: await _config.connectionConfig,
|
||||
privateKey: await _config.privateKey));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue