feat: Added receive and send pages

This commit is contained in:
Nathan Woodburn 2024-06-04 15:20:39 +10:00
parent eaff0ee299
commit 3bd61ae1ee
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
7 changed files with 348 additions and 6 deletions

View File

@ -72,7 +72,14 @@ class _DomainsPageState extends State<DomainsPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('${widget.wallet} Domains'), title: Text('${widget.wallet} - Domains'),
// Add refresh button to the app bar
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: fetchDomains,
),
],
), ),
// Get wallet list from api and display here // Get wallet list from api and display here
body: Center( body: Center(
@ -89,7 +96,6 @@ class _DomainsPageState extends State<DomainsPage> {
), ),
); );
}), }),
TextButton(onPressed: fetchDomains, child: Text('Refresh')),
], ],
); );
}, },

View File

@ -5,6 +5,8 @@ import 'dart:convert';
import 'package:firewallet/home.dart'; import 'package:firewallet/home.dart';
import 'package:firewallet/transactions.dart'; import 'package:firewallet/transactions.dart';
import 'package:firewallet/domains.dart'; import 'package:firewallet/domains.dart';
import 'package:firewallet/receive.dart';
import 'package:firewallet/send.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -96,6 +98,12 @@ class _MyHomePageState extends State<MyHomePage> {
icon: Icon(Icons.home_outlined), icon: Icon(Icons.home_outlined),
label: 'Home', label: 'Home',
), ),
NavigationDestination(
icon: Icon(Icons.call_received), label: 'Receive'),
NavigationDestination(
icon: Icon(Icons.send),
label: 'Send',
),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.text_format_rounded), icon: Icon(Icons.text_format_rounded),
label: 'Domains', label: 'Domains',
@ -111,6 +119,8 @@ class _MyHomePageState extends State<MyHomePage> {
index: currentPageIndex, index: currentPageIndex,
children: <Widget>[ children: <Widget>[
IndexPage(uuid: widget.uuid, wallet: wallet, setWallet: setWallet), IndexPage(uuid: widget.uuid, wallet: wallet, setWallet: setWallet),
ReceivePage(uuid: widget.uuid, wallet: wallet),
SendPage(uuid: widget.uuid, wallet: wallet),
DomainsPage(uuid: widget.uuid, wallet: wallet), DomainsPage(uuid: widget.uuid, wallet: wallet),
TransactionsPage(uuid: widget.uuid, wallet: wallet), TransactionsPage(uuid: widget.uuid, wallet: wallet),
], ],

104
lib/receive.dart Normal file
View File

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:qr_flutter/qr_flutter.dart';
class ReceivePage extends StatefulWidget {
ReceivePage({Key? key, required this.uuid, required this.wallet})
: super(key: key);
final String uuid;
String wallet;
@override
_ReceivePageState createState() => _ReceivePageState();
}
class _ReceivePageState extends State<ReceivePage> {
@override
void initState() {
super.initState();
fetchReceive();
}
String address = '';
Future<void> fetchReceive() async {
// Fetch domains from api
final response = await http.get(Uri.parse(
'https://api.firewallet.au/wallet/address?uuid=${widget.uuid}&name=${widget.wallet}'));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is Map && data.containsKey('error')) {
print('Error: ${data['error']}');
return;
}
// Return if empty
if (data.isEmpty) {
setState(() {
address = 'No address found';
});
return;
}
final addressData = data as Map;
setState(() {
address = addressData['address'];
});
} else {
// Handle error
print('Failed to load wallet names');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('${widget.wallet} - Receive HNS or Domains'),
),
// Get wallet list from api and display here
body: Center(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return ListView(
children: <Widget>[
Center(child: Text(address, style: TextStyle(fontSize: 18.0))),
// QR Code
Center(
child: QrImageView(
data: address,
version: QrVersions.auto,
// Width = 90% of the screen width
size: constraints.maxWidth * 0.9,
eyeStyle: QrEyeStyle(
eyeShape: QrEyeShape.square,
color: Theme.of(context).brightness == Brightness.light
? Colors.black
: Colors.white,
),
dataModuleStyle: QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: Theme.of(context).brightness == Brightness.light
? Colors.black
: Colors.white,
),
),
),
TextButton(onPressed: copyAddress, child: Text('Copy Address')),
TextButton(onPressed: fetchReceive, child: Text('Refresh')),
],
);
},
),
),
);
}
Future<void> copyAddress() async {
// Copy the address to the clipboard
await Clipboard.setData(ClipboardData(text: address));
}
}

189
lib/send.dart Normal file
View File

@ -0,0 +1,189 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:qr_flutter/qr_flutter.dart';
class SendPage extends StatefulWidget {
SendPage({Key? key, required this.uuid, required this.wallet})
: super(key: key);
final String uuid;
String wallet;
@override
_SendPageState createState() => _SendPageState();
}
class _SendPageState extends State<SendPage> {
late double walletBalance = 0;
bool balanceLoaded = false;
final TextEditingController addressController = TextEditingController();
final TextEditingController amountController = TextEditingController();
@override
void initState() {
super.initState();
}
@override
void dispose() {
// Dispose of the controllers when the widget is disposed
addressController.dispose();
amountController.dispose();
super.dispose();
}
Future<void> fetchWalletBalance() async {
if (widget.wallet.isEmpty) {
walletBalance = 0;
return;
}
if (balanceLoaded) {
return;
}
final response = await http.get(Uri.parse(
'https://api.firewallet.au/wallet/balance?uuid=${widget.uuid}&name=${widget.wallet}'));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
print(data);
setState(() {
walletBalance = data['available'];
});
} else {
// Handle error
print('Failed to load wallet balance');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('${widget.wallet} - Send HNS'),
),
// Get wallet list from api and display here
body: Center(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return ListView(
padding: const EdgeInsets.all(16),
children: <Widget>[
// Display the wallet balance
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Max ${(walletBalance - 0.02).toStringAsFixed(2)} HNS'),
TextButton(
onPressed: fetchWalletBalance,
child: const Icon(Icons.refresh)),
],
),
// Address input field
TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Address',
),
keyboardType: TextInputType.text,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9]')),
],
controller: addressController,
),
SizedBox(height: 16),
// Amount input field
TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Amount',
suffix: Text('HNS'),
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9.]')),
],
controller: amountController,
),
TextButton(
onPressed: () => amountController.text =
(walletBalance - 0.02).toStringAsFixed(2),
child: const Text('Max')),
SizedBox(height: 16),
// Send button
ElevatedButton(
onPressed: sendHNS,
child: const Text('Send'),
),
],
);
},
),
),
);
}
void sendHNS() {
// Send HNS to the address
String toAddress = addressController.text;
double amount = double.parse(amountController.text);
print('Send $amount HNS to $toAddress');
if (amount > (walletBalance - 0.01)) {
// Not enough balance
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Not enough balance'),
),
);
return;
}
bool sendMax = false;
if (amount >= (walletBalance - 0.02)) {
sendMax = true;
}
print(sendMax);
// https://api.firewallet.au/wallet/send?uuid=99879737-0c27-497b-b357-f75720feb32e&name=hot&address=hs1qca9n20ew7ph6l5galfnftmwme6kwmu26mzjgtx&amount=2
http
.post(
Uri.parse(
'https://api.firewallet.au/wallet/send?uuid=${widget.uuid}&name=${widget.wallet}&address=$toAddress&amount=$amount'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: '{}',
)
.then((response) {
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
print(data);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Sent $amount HNS to $toAddress'),
),
);
if (sendMax) {
amountController.text = '';
} else {
amountController.text =
min(amount, walletBalance - 0.02).toStringAsFixed(2);
}
fetchWalletBalance();
} else {
// Handle error
print('Failed to send HNS');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to send HNS'),
),
);
}
});
}
}

View File

@ -105,7 +105,14 @@ class _TransactionsPageState extends State<TransactionsPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('${widget.wallet} Transactions'), title: Text('${widget.wallet} - Transactions'),
// Add refresh button to the app bar
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: fetchTransactions,
),
],
), ),
// Get wallet list from api and display here // Get wallet list from api and display here
body: Center( body: Center(
@ -113,8 +120,6 @@ class _TransactionsPageState extends State<TransactionsPage> {
builder: (BuildContext context, BoxConstraints constraints) { builder: (BuildContext context, BoxConstraints constraints) {
return ListView( return ListView(
children: <Widget>[ children: <Widget>[
TextButton(
onPressed: fetchTransactions, child: Text('Refresh')),
...transactions.map((tx) { ...transactions.map((tx) {
return Card( return Card(
child: ListTile( child: ListTile(
@ -184,6 +189,7 @@ class Transaction {
int transfers = 0; int transfers = 0;
int finalizes = 0; int finalizes = 0;
int renews = 0; int renews = 0;
int revokes = 0;
int other = 0; int other = 0;
outputs.forEach((output) { outputs.forEach((output) {
@ -205,6 +211,8 @@ class Transaction {
finalizes++; finalizes++;
} else if (output.covenant.action == 'RENEW') { } else if (output.covenant.action == 'RENEW') {
renews++; renews++;
} else if (output.covenant.action == 'REVOKE') {
revokes++;
} else if (output.covenant.action == 'NONE') { } else if (output.covenant.action == 'NONE') {
other++; other++;
} else { } else {
@ -244,6 +252,9 @@ class Transaction {
if (renews > 0) { if (renews > 0) {
outputString += 'Renews: $renews, '; outputString += 'Renews: $renews, ';
} }
if (revokes > 0) {
outputString += 'Revokes: $revokes, ';
}
// Remove trailing comma // Remove trailing comma
if (outputString.isNotEmpty) { if (outputString.isNotEmpty) {
@ -251,8 +262,13 @@ class Transaction {
} }
if (outputString.isEmpty) { if (outputString.isEmpty) {
// Check if sent or received
if (value() > 0) {
outputString = 'Received HNS';
} else {
outputString = 'Sent HNS'; outputString = 'Sent HNS';
} }
}
return outputString; return outputString;
} }

View File

@ -288,6 +288,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
qr:
dependency: transitive
description:
name: qr
sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
qr_flutter:
dependency: "direct main"
description:
name: qr_flutter
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -38,6 +38,7 @@ dependencies:
shared_preferences: ^2.2.3 shared_preferences: ^2.2.3
http: ^1.2.1 http: ^1.2.1
url_launcher: ^6.2.6 url_launcher: ^6.2.6
qr_flutter: ^4.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: