cake_wallet/lib/src/widgets/seed_widget.dart

437 lines
16 KiB
Dart
Raw Normal View History

2020-01-04 19:31:52 +00:00
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/palette.dart';
2020-06-20 07:10:00 +00:00
import 'package:cake_wallet/core/seed_validator.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
2020-09-21 11:50:26 +00:00
import 'package:cake_wallet/entities/mnemonic_item.dart';
2020-01-04 19:31:52 +00:00
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter/widgets.dart';
2020-01-04 19:31:52 +00:00
class SeedWidget extends StatefulWidget {
2020-06-20 07:10:00 +00:00
SeedWidget(
{Key key,
this.maxLength,
this.onMnemonicChange,
this.onFinish,
this.leading,
this.middle,
2020-06-20 07:10:00 +00:00
this.validator})
: super(key: key);
final int maxLength;
final Function(List<MnemonicItem>) onMnemonicChange;
final Function() onFinish;
2020-06-20 07:10:00 +00:00
final SeedValidator validator;
final Widget leading;
final Widget middle;
2020-01-08 12:26:34 +00:00
@override
2020-06-20 07:10:00 +00:00
SeedWidgetState createState() => SeedWidgetState(maxLength: maxLength);
2020-01-04 19:31:52 +00:00
}
class SeedWidgetState extends State<SeedWidget> {
2020-06-20 07:10:00 +00:00
SeedWidgetState({this.maxLength});
2020-06-20 07:10:00 +00:00
List<MnemonicItem> items = <MnemonicItem>[];
final int maxLength;
2020-01-04 19:31:52 +00:00
final _seedController = TextEditingController();
final _seedTextFieldKey = GlobalKey();
2020-06-20 07:10:00 +00:00
MnemonicItem selectedItem;
2020-01-04 19:31:52 +00:00
bool isValid;
String errorMessage;
2020-06-20 07:10:00 +00:00
List<MnemonicItem> currentMnemonics;
bool isCurrentMnemonicValid;
String _errorMessage;
2020-01-04 19:31:52 +00:00
@override
void initState() {
super.initState();
isValid = false;
2020-06-20 07:10:00 +00:00
isCurrentMnemonicValid = false;
_seedController
2020-06-20 07:10:00 +00:00
.addListener(() => changeCurrentMnemonic(_seedController.text));
2020-01-04 19:31:52 +00:00
}
2020-06-20 07:10:00 +00:00
void addMnemonic(String text) {
setState(() => items.add(MnemonicItem(text: text.trim().toLowerCase())));
2020-01-04 19:31:52 +00:00
_seedController.text = '';
2020-06-20 07:10:00 +00:00
if (widget.onMnemonicChange != null) {
widget.onMnemonicChange(items);
2020-01-04 19:31:52 +00:00
}
}
2020-06-20 07:10:00 +00:00
void mnemonicFromText(String text) {
2020-01-04 19:31:52 +00:00
final splitted = text.split(' ');
if (splitted.length >= 2) {
for (final text in splitted) {
if (text == ' ' || text.isEmpty) {
continue;
}
if (selectedItem != null) {
2020-06-20 07:10:00 +00:00
editTextOfSelectedMnemonic(text);
2020-01-04 19:31:52 +00:00
} else {
2020-06-20 07:10:00 +00:00
addMnemonic(text);
2020-01-04 19:31:52 +00:00
}
}
}
}
2020-06-20 07:10:00 +00:00
void selectMnemonic(MnemonicItem item) {
setState(() {
selectedItem = item;
2020-06-20 07:10:00 +00:00
currentMnemonics = [item];
_seedController
..text = item.text
..selection = TextSelection.collapsed(offset: item.text.length);
});
2020-01-04 19:31:52 +00:00
}
2020-06-20 07:10:00 +00:00
void onMnemonicTap(MnemonicItem item) {
2020-01-04 19:31:52 +00:00
if (selectedItem == item) {
setState(() => selectedItem = null);
_seedController.text = '';
return;
}
2020-06-20 07:10:00 +00:00
selectMnemonic(item);
2020-01-04 19:31:52 +00:00
}
2020-06-20 07:10:00 +00:00
void editTextOfSelectedMnemonic(String text) {
2020-01-04 19:31:52 +00:00
setState(() => selectedItem.changeText(text));
selectedItem = null;
_seedController.text = '';
2020-06-20 07:10:00 +00:00
if (widget.onMnemonicChange != null) {
widget.onMnemonicChange(items);
2020-01-04 19:31:52 +00:00
}
}
void clear() {
setState(() {
items = [];
selectedItem = null;
2020-01-04 19:31:52 +00:00
_seedController.text = '';
2020-06-20 07:10:00 +00:00
if (widget.onMnemonicChange != null) {
widget.onMnemonicChange(items);
2020-01-04 19:31:52 +00:00
}
});
}
2020-06-20 07:10:00 +00:00
void invalidate() => setState(() => isValid = false);
2020-01-04 19:31:52 +00:00
2020-06-20 07:10:00 +00:00
void validated() => setState(() => isValid = true);
2020-01-04 19:31:52 +00:00
2020-06-20 07:10:00 +00:00
void setErrorMessage(String errorMessage) =>
setState(() => this.errorMessage = errorMessage);
2020-01-04 19:31:52 +00:00
void replaceText(String text) {
setState(() => items = []);
2020-06-20 07:10:00 +00:00
mnemonicFromText(text);
2020-01-04 19:31:52 +00:00
}
2020-06-20 07:10:00 +00:00
void changeCurrentMnemonic(String text) {
setState(() {
final trimmedText = text.trim();
final splitted = trimmedText.split(' ');
_errorMessage = null;
if (text == null) {
2020-06-20 07:10:00 +00:00
currentMnemonics = [];
isCurrentMnemonicValid = false;
return;
}
2020-06-20 07:10:00 +00:00
currentMnemonics =
splitted.map((text) => MnemonicItem(text: text)).toList();
2020-06-20 07:10:00 +00:00
var isValid = true;
2020-06-20 07:10:00 +00:00
for (final word in currentMnemonics) {
isValid = widget.validator.isValid(word);
if (!isValid) {
break;
}
}
2020-06-20 07:10:00 +00:00
isCurrentMnemonicValid = isValid;
});
}
2020-06-20 07:10:00 +00:00
void saveCurrentMnemonicToItems() {
setState(() {
if (selectedItem != null) {
2020-06-20 07:10:00 +00:00
selectedItem.changeText(currentMnemonics.first.text.trim());
selectedItem = null;
} else {
2020-06-20 07:10:00 +00:00
items.addAll(currentMnemonics);
}
2020-06-20 07:10:00 +00:00
currentMnemonics = [];
_seedController.text = '';
});
}
void showErrorIfExist() {
setState(() => _errorMessage =
2020-06-20 07:10:00 +00:00
!isCurrentMnemonicValid ? S.current.incorrect_seed : null);
}
bool isSeedValid() {
bool isValid;
for (final item in items) {
2020-06-20 07:10:00 +00:00
isValid = widget.validator.isValid(item);
if (!isValid) {
break;
}
}
return isValid;
}
2020-01-04 19:31:52 +00:00
@override
Widget build(BuildContext context) {
return Container(
child: Column(children: [
Flexible(
2020-06-20 07:10:00 +00:00
fit: FlexFit.tight,
flex: 2,
2020-06-20 07:10:00 +00:00
child: Container(
2020-09-10 14:51:59 +00:00
width: double.infinity,
height: double.infinity,
padding: EdgeInsets.all(0),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)),
gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme.subhead.color,
Theme.of(context).primaryTextTheme.subhead.decorationColor,
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
child: Column(
children: <Widget>[
CupertinoNavigationBar(
leading: widget.leading,
middle: widget.middle,
backgroundColor: Colors.transparent,
border: null,
),
Expanded(
child: Container(
padding: EdgeInsets.all(24),
alignment: Alignment.topLeft,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).restore_active_seed,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.textTheme
.overline
.backgroundColor),
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Wrap(
children: items.map((item) {
final isValid =
widget.validator.isValid(item);
final isSelected = selectedItem == item;
return InkWell(
onTap: () => onMnemonicTap(item),
child: Container(
decoration: BoxDecoration(
color: isValid
? Colors.transparent
: Palette.red),
margin: EdgeInsets.only(
right: 7, bottom: 8),
child: Text(
item.toString(),
style: TextStyle(
color: isValid
2020-09-10 14:51:59 +00:00
? Colors.white
: Colors.grey,
fontSize: 16,
fontWeight: isSelected
? FontWeight.w900
: FontWeight.w600,
decoration: isSelected
? TextDecoration.underline
: TextDecoration.none),
)),
);
}).toList(),
))
],
),
2020-09-10 14:51:59 +00:00
),
))
],
)),
),
Flexible(
fit: FlexFit.tight,
flex: 3,
child: Padding(
2020-06-20 07:10:00 +00:00
padding:
EdgeInsets.only(left: 24, top: 48, right: 24, bottom: 24),
child: Column(
2020-06-20 07:10:00 +00:00
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
S.of(context).restore_new_seed,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
2020-06-20 07:10:00 +00:00
color:
Theme.of(context).primaryTextTheme.title.color),
),
2020-06-20 07:10:00 +00:00
Padding(
padding: EdgeInsets.only(top: 24),
child: TextFormField(
key: _seedTextFieldKey,
onFieldSubmitted: (text) => isCurrentMnemonicValid
? saveCurrentMnemonicToItems()
: null,
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.normal,
2020-06-20 07:10:00 +00:00
color:
Theme.of(context).primaryTextTheme.title.color),
controller: _seedController,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
suffixIcon: GestureDetector(
behavior: HitTestBehavior.opaque,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 145),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text('${items.length}/$maxLength',
style: TextStyle(
color: Theme.of(context)
.accentTextTheme
.display2
.decorationColor,
fontWeight: FontWeight.normal,
fontSize: 16)),
2020-06-20 07:10:00 +00:00
SizedBox(width: 10),
InkWell(
onTap: () async =>
Clipboard.getData('text/plain').then(
(clipboard) =>
2020-06-20 07:10:00 +00:00
replaceText(clipboard.text)),
child: Container(
height: 35,
padding: EdgeInsets.all(7),
decoration: BoxDecoration(
color: Theme.of(context)
.accentTextTheme
.caption
.color,
2020-06-20 07:10:00 +00:00
borderRadius:
BorderRadius.circular(10.0)),
child: Text(
S.of(context).paste,
style: TextStyle(
color: Palette.blueCraiola),
2020-06-20 07:10:00 +00:00
)),
)
],
),
),
),
2020-06-20 07:10:00 +00:00
hintStyle: TextStyle(
color: Theme.of(context)
.accentTextTheme
.display2
.decorationColor,
fontWeight: FontWeight.normal,
2020-06-20 07:10:00 +00:00
fontSize: 16),
hintText:
S.of(context).restore_from_seed_placeholder,
errorText: _errorMessage,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
2020-09-10 14:51:59 +00:00
color: Theme.of(context)
.accentTextTheme
.subtitle
.backgroundColor,
2020-06-20 07:10:00 +00:00
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
2020-09-10 14:51:59 +00:00
color: Theme.of(context)
.accentTextTheme
.subtitle
.backgroundColor,
2020-06-20 07:10:00 +00:00
width: 1.0))),
enableInteractiveSelection: false,
),
)
]),
)),
Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: Row(
children: <Widget>[
Flexible(
2020-06-20 07:10:00 +00:00
child: Padding(
padding: EdgeInsets.only(right: 8),
child: PrimaryButton(
onPressed: clear,
text: S.of(context).clear,
color: Colors.orange,
2020-06-20 07:10:00 +00:00
textColor: Colors.white,
isDisabled: items.isEmpty,
),
)),
Flexible(
child: Padding(
padding: EdgeInsets.only(left: 8),
child: (selectedItem == null && items.length == maxLength)
? PrimaryButton(
2020-06-20 07:10:00 +00:00
text: S.of(context).restore_next,
isDisabled: !isSeedValid(),
onPressed: () => widget.onFinish != null
? widget.onFinish()
: null,
color: Theme.of(context).accentTextTheme.body2.color,
2020-06-20 07:10:00 +00:00
textColor: Colors.white)
: PrimaryButton(
2020-06-20 07:10:00 +00:00
text: selectedItem != null
? S.of(context).save
: S.of(context).add_new_word,
onPressed: () => isCurrentMnemonicValid
? saveCurrentMnemonicToItems()
: null,
onDisabledPressed: () => showErrorIfExist(),
isDisabled: !isCurrentMnemonicValid,
color: Theme.of(context).accentTextTheme.body2.color,
2020-06-20 07:10:00 +00:00
textColor: Colors.white),
),
)
],
))
2020-01-04 19:31:52 +00:00
]),
);
}
}