2021-09-08 07:14:19 -07:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
|
|
|
import 'package:flutter_svg/svg.dart';
|
|
|
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
|
|
|
import 'package:warp_api/warp_api.dart';
|
|
|
|
|
|
|
|
import 'main.dart';
|
|
|
|
import 'generated/l10n.dart';
|
|
|
|
import 'store.dart';
|
|
|
|
|
|
|
|
class ContactsTab extends StatefulWidget {
|
2021-09-10 02:56:15 -07:00
|
|
|
ContactsTab({Key? key}) : super(key: key);
|
2021-09-08 07:14:19 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
State<StatefulWidget> createState() => ContactsState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class ContactsState extends State<ContactsTab> {
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Observer(builder: (context) {
|
|
|
|
return contacts.contacts.isEmpty
|
|
|
|
? NoContact()
|
|
|
|
: Column(children: [
|
|
|
|
if (contacts.dirty) OutlinedButton(onPressed: _onCommit, child: Text(S.of(context).saveToBlockchain), style: OutlinedButton.styleFrom(
|
|
|
|
side: BorderSide(
|
|
|
|
width: 1, color: Theme.of(context).primaryColor))),
|
|
|
|
Expanded(child: ListView.builder(
|
|
|
|
itemCount: contacts.contacts.length,
|
|
|
|
itemBuilder: (BuildContext context, int index) {
|
|
|
|
final c = contacts.contacts[index];
|
|
|
|
return Card(
|
|
|
|
child: Dismissible(
|
|
|
|
key: Key("${c.id}"),
|
|
|
|
child: ListTile(
|
|
|
|
title: Text(c.name,
|
|
|
|
style: Theme.of(context).textTheme.headline5),
|
|
|
|
subtitle: Text(c.address),
|
|
|
|
trailing: Icon(Icons.chevron_right),
|
|
|
|
onTap: () { _onContact(c); },
|
|
|
|
onLongPress: () { _editContact(c); },
|
|
|
|
),
|
|
|
|
confirmDismiss: (_) async {
|
|
|
|
return await _onConfirmDelContact(c);
|
|
|
|
},
|
|
|
|
onDismissed: (_) {
|
|
|
|
_delContact(c);
|
|
|
|
}));
|
|
|
|
})
|
|
|
|
)]);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_onContact(Contact c) {
|
|
|
|
Navigator.of(context).pushNamed('/send', arguments: c);
|
|
|
|
}
|
|
|
|
|
|
|
|
_editContact(Contact c) async {
|
|
|
|
final contact = await showContactForm(context, c);
|
|
|
|
if (contact != null) contacts.add(contact);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool> _onConfirmDelContact(Contact c) async {
|
|
|
|
final confirm = await showMessageBox(context, S.of(context).deleteContact,
|
|
|
|
S.of(context).areYouSureYouWantToDeleteThisContact,
|
|
|
|
S.of(context).delete);
|
|
|
|
return confirm;
|
|
|
|
}
|
|
|
|
|
|
|
|
_delContact(Contact c) async {
|
|
|
|
await contacts.remove(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<Contact> showContactForm(BuildContext context, Contact c) async {
|
|
|
|
final key = GlobalKey<ContactState>();
|
|
|
|
|
|
|
|
final contact = await showDialog<Contact>(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => AlertDialog(
|
|
|
|
contentPadding: EdgeInsets.all(16),
|
2021-09-15 02:28:07 -07:00
|
|
|
title: Text(S.of(context).addContact),
|
2021-09-08 07:14:19 -07:00
|
|
|
content: ContactForm(c, key: key),
|
|
|
|
actions: confirmButtons(context, () {
|
2021-09-10 02:56:15 -07:00
|
|
|
key.currentState!.onOK();
|
2021-09-08 07:14:19 -07:00
|
|
|
}),
|
|
|
|
));
|
2021-09-10 02:56:15 -07:00
|
|
|
return contact!;
|
2021-09-08 07:14:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
_onCommit() async {
|
|
|
|
final approve = await showMessageBox(context, S.of(context).saveToBlockchain,
|
|
|
|
S.of(context).areYouSureYouWantToSaveYourContactsIt(coin.ticker),
|
|
|
|
S.of(context).ok);
|
|
|
|
if (approve) {
|
|
|
|
contacts.markContactsDirty(false);
|
|
|
|
final tx = await WarpApi.commitUnsavedContacts(
|
|
|
|
accountManager.active.id, settings.anchorOffset);
|
|
|
|
final snackBar = SnackBar(content: Text("${S.of(context).txId}: $tx"));
|
2021-09-10 02:56:15 -07:00
|
|
|
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
|
2021-09-08 07:14:19 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class NoContact extends StatelessWidget {
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
final Widget contact = SvgPicture.asset('assets/contacts.svg',
|
|
|
|
color: Theme.of(context).primaryColor, semanticsLabel: 'Contacts');
|
|
|
|
|
|
|
|
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
|
|
|
SizedBox(child: contact, height: 150, width: 150),
|
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 16)),
|
2021-09-15 02:28:07 -07:00
|
|
|
Text(S.of(context).noContacts, style: Theme.of(context).textTheme.headline5),
|
2021-09-08 07:14:19 -07:00
|
|
|
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
2021-09-15 02:28:07 -07:00
|
|
|
Text(S.of(context).createANewContactAndItWillShowUpHere,
|
2021-09-08 07:14:19 -07:00
|
|
|
style: Theme.of(context).textTheme.bodyText1),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ContactForm extends StatefulWidget {
|
|
|
|
final Contact contact;
|
|
|
|
|
2021-09-10 02:56:15 -07:00
|
|
|
ContactForm(this.contact, {Key? key}) : super(key: key);
|
2021-09-08 07:14:19 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
State<StatefulWidget> createState() => ContactState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class ContactState extends State<ContactForm> {
|
|
|
|
final formKey = GlobalKey<FormState>();
|
|
|
|
final nameController = TextEditingController();
|
|
|
|
var address = "";
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
nameController.text = widget.contact.name;
|
|
|
|
address = widget.contact.address;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Form(
|
|
|
|
key: formKey,
|
|
|
|
child: SingleChildScrollView(
|
|
|
|
child: Column(children: [
|
|
|
|
TextFormField(
|
2021-09-15 02:28:07 -07:00
|
|
|
decoration: InputDecoration(labelText: S.of(context).contactName),
|
2021-09-08 07:14:19 -07:00
|
|
|
controller: nameController,
|
|
|
|
validator: _checkName,
|
|
|
|
),
|
2021-09-15 02:28:07 -07:00
|
|
|
AddressInput(S.of(context).address, address, (addr) {
|
2021-09-10 02:56:15 -07:00
|
|
|
address = addr ?? "";
|
2021-09-08 07:14:19 -07:00
|
|
|
})
|
|
|
|
])));
|
|
|
|
}
|
|
|
|
|
|
|
|
onOK() {
|
2021-09-10 02:56:15 -07:00
|
|
|
final state = formKey.currentState!;
|
2021-09-08 07:14:19 -07:00
|
|
|
if (state.validate()) {
|
|
|
|
state.save();
|
|
|
|
final contact = Contact(widget.contact.id, nameController.text, address);
|
|
|
|
Navigator.of(context).pop(contact);
|
2021-09-15 22:55:20 -07:00
|
|
|
accountManager.fetchAccountData(true);
|
2021-09-08 07:14:19 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-10 02:56:15 -07:00
|
|
|
String? _checkName(String? v) {
|
|
|
|
if (v == null || v.isEmpty) return S.of(context).nameIsEmpty;
|
2021-09-08 07:14:19 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class AddressInput extends StatefulWidget {
|
2021-09-10 02:56:15 -07:00
|
|
|
final void Function(String?) onSaved;
|
2021-09-08 07:14:19 -07:00
|
|
|
final String labelText;
|
|
|
|
final String initialValue;
|
|
|
|
|
2021-09-10 02:56:15 -07:00
|
|
|
AddressInput(this.labelText, this.initialValue, this.onSaved);
|
2021-09-08 07:14:19 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
State<StatefulWidget> createState() => AddressState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class AddressState extends State<AddressInput> {
|
|
|
|
final _addressController = TextEditingController();
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
_addressController.text = widget.initialValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Row(children: <Widget>[
|
|
|
|
Expanded(
|
|
|
|
child: TextFormField(
|
|
|
|
decoration: InputDecoration(labelText: widget.labelText),
|
|
|
|
minLines: 4,
|
|
|
|
maxLines: null,
|
|
|
|
keyboardType: TextInputType.multiline,
|
|
|
|
controller: _addressController,
|
|
|
|
validator: _checkAddress,
|
|
|
|
onSaved: widget.onSaved,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
IconButton(icon: new Icon(MdiIcons.qrcodeScan), onPressed: _onScan)
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2021-09-10 02:56:15 -07:00
|
|
|
String? _checkAddress(String? v) {
|
|
|
|
if (v == null || v.isEmpty) return S.of(context).addressIsEmpty;
|
2021-09-08 07:14:19 -07:00
|
|
|
final zaddr = WarpApi.getSaplingFromUA(v);
|
|
|
|
if (zaddr.isNotEmpty) return null;
|
|
|
|
if (!WarpApi.validAddress(v)) return S.of(context).invalidAddress;
|
2021-09-16 23:06:02 -07:00
|
|
|
if (contacts.contacts.where((c) => c.address == v).isNotEmpty) return S.of(context).duplicateContact;
|
2021-09-08 07:14:19 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onScan() async {
|
2021-09-10 02:56:15 -07:00
|
|
|
var address = await scanCode(context);
|
|
|
|
if (address != null)
|
|
|
|
setState(() {
|
|
|
|
_addressController.text = address;
|
|
|
|
});
|
2021-09-08 07:14:19 -07:00
|
|
|
}
|
|
|
|
}
|