Skip to content

Commit

Permalink
Merge pull request #2177 from get10101/feat/tx-history
Browse files Browse the repository at this point in the history
feat(web): show transaction details in a dialog
  • Loading branch information
bonomat authored Mar 7, 2024
2 parents e47519f + 93f19b4 commit cbdd5f7
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 108 deletions.
1 change: 1 addition & 0 deletions webapp/frontend/devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extensions:
5 changes: 5 additions & 0 deletions webapp/frontend/lib/common/truncate_text.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
String truncateWithEllipsis(int cutoff, String text) {
return (text.length <= cutoff)
? text
: '${text.substring(0, (cutoff / 2).floor())}...${text.substring(text.length - (cutoff / 2).ceil(), text.length)}';
}
17 changes: 16 additions & 1 deletion webapp/frontend/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,24 @@ import 'package:get_10101/trade/quote_service.dart';
import 'package:get_10101/settings/settings_service.dart';
import 'package:get_10101/wallet/wallet_change_notifier.dart';
import 'package:get_10101/wallet/wallet_service.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl_browser.dart';
import 'package:provider/provider.dart';

import 'common/color.dart';
import 'common/theme.dart';

void main() {
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
buildLogger(false);
logger.i("Logger initialized");

// Get the system's default locale
String defaultLocale = await findSystemLocale();

// Initialize the date format data for the system's default locale
await initializeDateFormatting(defaultLocale, null);

const walletService = WalletService();
const channelService = ChannelService();

Expand Down Expand Up @@ -63,6 +72,12 @@ class _TenTenOneAppState extends State<TenTenOneApp> {
return MaterialApp.router(
title: "10101",
scaffoldMessengerKey: scaffoldMessengerKey,
supportedLocales: const [
Locale('en', 'US'),
Locale('es', 'ES'),
Locale('fr', 'FR'),
Locale('de', 'DE'),
],
theme: ThemeData(
primarySwatch: swatch,
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
Expand Down
7 changes: 1 addition & 6 deletions webapp/frontend/lib/settings/channel_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get_10101/common/channel_state_label.dart';
import 'package:get_10101/common/snack_bar.dart';
import 'package:get_10101/common/truncate_text.dart';
import 'package:get_10101/settings/channel_change_notifier.dart';
import 'package:get_10101/settings/channel_service.dart';
import 'package:get_10101/settings/dlc_channel.dart';
Expand Down Expand Up @@ -286,12 +287,6 @@ class _ChannelDetailWidgetState extends State<ChannelDetailWidget> {
}
}

String truncateWithEllipsis(int cutoff, String text) {
return (text.length <= cutoff)
? text
: '${text.substring(0, (cutoff / 2).floor())}...${text.substring(text.length - (cutoff / 2).ceil(), text.length)}';
}

Uri buildUri(String txId) {
// TODO: support different networks
return Uri(
Expand Down
79 changes: 35 additions & 44 deletions webapp/frontend/lib/wallet/history_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,50 +21,41 @@ class _HistoryScreenState extends State<HistoryScreen> {

final history = walletChangeNotifier.getHistory();

return Container(
padding: const EdgeInsets.only(top: 25),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: ElevatedButton(
onPressed: refreshing
? null
: () async {
setState(() {
refreshing = true;
});
await service.sync();
setState(() {
refreshing = false;
});
},
child:
refreshing ? const CircularProgressIndicator() : const Text("Refresh")),
),
],
return Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: history == null
? [
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(),
)
]
: history.map((item) => OnChainPaymentHistoryItem(data: item)).toList(),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
children: history == null
? [
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(),
)
]
: history.map((item) => OnChainPaymentHistoryItem(data: item)).toList(),
),
),
],
),
],
));
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: ElevatedButton(
onPressed: refreshing
? null
: () async {
setState(() {
refreshing = true;
});
await service.sync();
await walletChangeNotifier.refresh();
setState(() {
refreshing = false;
});
},
child: refreshing ? const CircularProgressIndicator() : const Text("Refresh")),
),
],
);
}
}
20 changes: 18 additions & 2 deletions webapp/frontend/lib/wallet/onchain_payment_history_item.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get_10101/common/payment.dart';
import 'package:get_10101/wallet/walle_history_detail_dialog.dart';
import 'package:intl/intl.dart';
import 'package:timeago/timeago.dart' as timeago;

Expand All @@ -10,6 +11,8 @@ class OnChainPaymentHistoryItem extends StatelessWidget {

@override
Widget build(BuildContext context) {
final formattedDate = DateFormat.yMd().add_jm().format(data.timestamp);

final statusIcon = switch (data.confirmations) {
>= 3 => const Icon(Icons.check_circle, color: Colors.green, size: 18),
_ => const Icon(Icons.pending, size: 18)
Expand All @@ -29,7 +32,11 @@ class OnChainPaymentHistoryItem extends StatelessWidget {
elevation: 0,
child: ListTile(
onTap: () async {
// todo
showDialog(
context: context,
builder: (context) {
return WalletHistoryDetailDialog(data: data);
});
},
leading: Stack(children: [
Container(
Expand All @@ -53,7 +60,9 @@ class OnChainPaymentHistoryItem extends StatelessWidget {
textWidthBasis: TextWidthBasis.longestLine,
text: TextSpan(style: DefaultTextStyle.of(context).style, children: <TextSpan>[
TextSpan(
text: timeago.format(data.timestamp),
text: wasMoreThanHalfAnHourAgo(data.timestamp)
? formattedDate
: timeago.format(data.timestamp),
style: const TextStyle(color: Colors.grey)),
])),
trailing: Padding(
Expand Down Expand Up @@ -90,3 +99,10 @@ class OnChainPaymentHistoryItem extends StatelessWidget {
);
}
}

bool wasMoreThanHalfAnHourAgo(DateTime timestamp) {
DateTime now = DateTime.now();
DateTime oneHourAgo = now.subtract(const Duration(minutes: 30));

return timestamp.isBefore(oneHourAgo);
}
7 changes: 4 additions & 3 deletions webapp/frontend/lib/wallet/receive_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get_10101/common/color.dart';
import 'package:get_10101/common/snack_bar.dart';
import 'package:get_10101/common/truncate_text.dart';
import 'package:get_10101/wallet/wallet_change_notifier.dart';
import 'package:provider/provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
Expand Down Expand Up @@ -41,9 +42,9 @@ class _ReceiveScreenState extends State<ReceiveScreen> {
children: [
address == null
? const SizedBox.square(
dimension: 350, child: Center(child: CircularProgressIndicator()))
dimension: 200, child: Center(child: CircularProgressIndicator()))
: SizedBox.square(
dimension: 350,
dimension: 200,
child: QrImageView(
data: address!,
eyeStyle: const QrEyeStyle(
Expand Down Expand Up @@ -71,7 +72,7 @@ class _ReceiveScreenState extends State<ReceiveScreen> {
child: address == null
? const Center(child: CircularProgressIndicator())
: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text(address!),
Text(truncateWithEllipsis(30, address!)),
GestureDetector(
child: const Icon(Icons.copy, size: 20),
onTap: () async {
Expand Down
144 changes: 144 additions & 0 deletions webapp/frontend/lib/wallet/walle_history_detail_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get_10101/common/amount_text.dart';
import 'package:get_10101/common/model.dart';
import 'package:get_10101/common/payment.dart';
import 'package:get_10101/common/snack_bar.dart';
import 'package:get_10101/common/truncate_text.dart';
import 'package:intl/intl.dart';
import 'package:url_launcher/url_launcher.dart';

class WalletHistoryDetailDialog extends StatelessWidget {
final OnChainPayment data;

const WalletHistoryDetailDialog({
Key? key,
required this.data,
}) : super(key: key);

@override
Widget build(BuildContext context) {
final formattedDate = DateFormat.yMd().add_jm().format(data.timestamp);
final (directionMultiplier, verb) = switch ((data.flow, data.confirmations)) {
(PaymentFlow.inbound, 0) => (1, "are receiving"),
(PaymentFlow.inbound, _) => (1, "received"),
(PaymentFlow.outbound, 0) => (-1, "are sending"),
(PaymentFlow.outbound, _) => (-1, "sent"),
};

return AlertDialog(
content: SizedBox(
width: 440,
child: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Column(
children: [
SizedBox(
width: 50, height: 50, child: SvgPicture.asset("assets/Bitcoin_logo.svg")),
Text("You $verb"),
AmountText(
amount: Amount(data.amount.sats * directionMultiplier),
textStyle: const TextStyle(fontSize: 25, fontWeight: FontWeight.bold)),
],
),
HistoryDetail(
label: "When",
value: formattedDate,
truncate: false,
),
HistoryDetail(
label: "Transaction Id",
value: data.txid,
displayWidget: TransactionIdText(data.txid)),
HistoryDetail(label: "Confirmations", value: data.confirmations.toString()),
HistoryDetail(
label: "Fee",
value: data.fee.toString(),
truncate: false,
),
],
),
),
),
actions: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Close'),
),
],
);
}
}

class HistoryDetail extends StatelessWidget {
final String label;
final String value;
final Widget? displayWidget;
final bool truncate;

static const TextStyle defaultValueStyle = TextStyle(fontSize: 16);

const HistoryDetail(
{super.key,
required this.label,
required this.value,
this.displayWidget,
this.truncate = true});

@override
Widget build(BuildContext context) {
return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text(label, style: defaultValueStyle.copyWith(fontWeight: FontWeight.bold)),
Expanded(
child: Row(children: [
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: displayWidget ??
Text(truncate ? truncateWithEllipsis(10, value) : value,
style: defaultValueStyle))),
IconButton(
padding: EdgeInsets.zero,
onPressed: () {
Clipboard.setData(ClipboardData(text: value)).then((_) {
showSnackBar(ScaffoldMessenger.of(context), '$label copied to clipboard');
});
},
icon: const Icon(Icons.copy, size: 18))
]),
)
]);
}
}

class TransactionIdText extends StatelessWidget {
final String txId;

const TransactionIdText(this.txId, {super.key});

@override
Widget build(BuildContext context) {
Uri uri = Uri(
scheme: 'https',
host: 'mempool.space',
pathSegments: ['tx', txId],
);

return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(truncateWithEllipsis(10, txId)),
IconButton(
padding: EdgeInsets.zero,
onPressed: () => launchUrl(uri, mode: LaunchMode.externalApplication),
icon: const Icon(Icons.open_in_new, size: 18))
],
);
}
}
6 changes: 3 additions & 3 deletions webapp/frontend/lib/wallet/wallet_change_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ class WalletChangeNotifier extends ChangeNotifier {
List<OnChainPayment>? _history;

WalletChangeNotifier(this.service) {
_refresh();
refresh();
Timer.periodic(const Duration(seconds: 30), (timer) async {
_refresh();
await refresh();
});
}

void _refresh() async {
Future<void> refresh() async {
try {
final data =
await Future.wait<dynamic>([service.getBalance(), service.getOnChainPaymentHistory()]);
Expand Down
Loading

0 comments on commit cbdd5f7

Please sign in to comment.