diff --git a/lib/pages/stream.dart b/lib/pages/stream.dart index 2a347d1..61d205c 100644 --- a/lib/pages/stream.dart +++ b/lib/pages/stream.dart @@ -138,16 +138,19 @@ class _StreamPage extends State { context: context, constraints: BoxConstraints.expand(), builder: (ctx) { - return ZapWidget( - pubkey: stream.info.host, - target: stream.event, - zapTags: - // tag goal onto zap request - stream.info.goal != null - ? [ - ["e", stream.info.goal!], - ] - : null, + return SingleChildScrollView( + primary: false, + child: ZapWidget( + pubkey: stream.info.host, + target: stream.event, + zapTags: + // tag goal onto zap request + stream.info.goal != null + ? [ + ["e", stream.info.goal!], + ] + : null, + ), ); }, ); diff --git a/lib/widgets/button.dart b/lib/widgets/button.dart index 02a2782..d88ca1a 100644 --- a/lib/widgets/button.dart +++ b/lib/widgets/button.dart @@ -7,6 +7,7 @@ class BasicButton extends StatelessWidget { final EdgeInsetsGeometry? padding; final EdgeInsetsGeometry? margin; final void Function()? onTap; + final bool? disabled; const BasicButton( this.child, { @@ -15,6 +16,7 @@ class BasicButton extends StatelessWidget { this.padding, this.margin, this.onTap, + this.disabled, }); static text( @@ -24,6 +26,7 @@ class BasicButton extends StatelessWidget { EdgeInsetsGeometry? margin, void Function()? onTap, double? fontSize, + bool? disabled, }) { return BasicButton( Text( @@ -34,6 +37,7 @@ class BasicButton extends StatelessWidget { fontWeight: FontWeight.bold, ), ), + disabled: disabled, decoration: decoration, padding: padding ?? EdgeInsets.symmetric(vertical: 4, horizontal: 12), margin: margin, @@ -44,16 +48,20 @@ class BasicButton extends StatelessWidget { @override Widget build(BuildContext context) { final defaultBr = BorderRadius.all(Radius.circular(100)); + final inner = Container( + padding: padding, + margin: margin, + decoration: + decoration ?? BoxDecoration(color: LAYER_2, borderRadius: defaultBr), + child: Center(child: child), + ); return GestureDetector( - onTap: onTap, - child: Container( - padding: padding, - margin: margin, - decoration: - decoration ?? - BoxDecoration(color: LAYER_2, borderRadius: defaultBr), - child: Center(child: child), - ), + onTap: () { + if (!(disabled ?? false) && onTap != null) { + onTap!(); + } + }, + child: (disabled ?? false) ? Opacity(opacity: 0.3, child: inner) : inner, ); } } diff --git a/lib/widgets/zap.dart b/lib/widgets/zap.dart index 6330660..9fe4379 100644 --- a/lib/widgets/zap.dart +++ b/lib/widgets/zap.dart @@ -1,6 +1,7 @@ import 'package:clipboard/clipboard.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:ndk/domain_layer/usecases/lnurl/lnurl.dart'; import 'package:ndk/ndk.dart'; import 'package:qr_flutter/qr_flutter.dart'; @@ -31,6 +32,9 @@ class ZapWidget extends StatefulWidget { class _ZapWidget extends State { final TextEditingController _comment = TextEditingController(); + final TextEditingController _customAmount = TextEditingController(); + final FocusNode _customAmountFocus = FocusNode(); + bool _loading = false; String? _error; String? _pr; int? _amount; @@ -67,8 +71,9 @@ class _ZapWidget extends State { ), ], ), - if (_pr == null) ..._inputs(), + if (_pr == null && !_loading) ..._inputs(), if (_pr != null) ..._invoice(), + if (_loading) CircularProgressIndicator(), ], ), ); @@ -83,32 +88,78 @@ class _ZapWidget extends State { crossAxisCount: 5, mainAxisSpacing: 5, crossAxisSpacing: 5, - childAspectRatio: 1.5, + childAspectRatio: 1.9, ), itemBuilder: (ctx, idx) => _zapAmount(_zapAmounts[idx]), ), + Row( + spacing: 8, + children: [ + Expanded( + child: TextFormField( + controller: _customAmount, + focusNode: _customAmountFocus, + keyboardType: TextInputType.number, + decoration: InputDecoration(labelText: "Custom Amount"), + ), + ), + BasicButton.text( + "Confirm", + onTap: () { + final newAmount = int.tryParse(_customAmount.text); + if (newAmount != null) { + setState(() { + _error = null; + _amount = newAmount; + _customAmountFocus.unfocus(); + }); + } else { + setState(() { + _error = "Invalid custom amount"; + _amount = null; + }); + } + }, + ), + ], + ), TextFormField( controller: _comment, decoration: InputDecoration(labelText: "Comment"), ), BasicButton.text( - "Zap", + _amount != null ? "Zap ${formatSats(_amount!)} sats" : "Zap", + disabled: _amount == null, decoration: BoxDecoration(color: LAYER_3, borderRadius: DEFAULT_BR), - onTap: () { + onTap: () async { try { - _loadZap(); + setState(() { + _error = null; + _loading = true; + }); + await _loadZap(); } catch (e) { setState(() { _error = e.toString(); }); + } finally { + setState(() { + _loading = false; + }); } }, ), - if (_error != null) Text(_error!), + if (_error != null) + Text( + _error!, + style: TextStyle(color: WARNING, fontWeight: FontWeight.bold), + ), ]; } List _invoice() { + final prLink = "lightning:${_pr!}"; + return [ QrImageView( data: _pr!, @@ -124,21 +175,50 @@ class _ZapWidget extends State { onTap: () async { await FlutterClipboard.copy(_pr!); }, - child: Text(_pr!, overflow: TextOverflow.ellipsis), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration(borderRadius: DEFAULT_BR, color: LAYER_2), + child: Row( + spacing: 4, + children: [ + Icon(Icons.copy, size: 16), + Expanded(child: Text(_pr!, overflow: TextOverflow.ellipsis)), + ], + ), + ), ), - BasicButton.text( - "Open in Wallet", - onTap: () async { - try { - await launchUrlString("lightning:${_pr!}"); - } catch (e) { - setState(() { - _error = e is String ? e : e.toString(); - }); - } + FutureBuilder( + future: canLaunchUrlString(prLink), + builder: (context, v) { + if (!(v.data ?? false)) return SizedBox(); + return BasicButton.text( + "Open in Wallet", + onTap: () async { + try { + await launchUrlString(prLink); + } catch (e) { + if (e is PlatformException) { + if (e.code == "ACTIVITY_NOT_FOUND") { + setState(() { + _error = "No lightning wallet installed"; + }); + return; + } + } + setState(() { + _error = e is String ? e : e.toString(); + }); + } + }, + ); }, ), - if (_error != null) Text(_error!), + + if (_error != null) + Text( + _error!, + style: TextStyle(color: WARNING, fontWeight: FontWeight.bold), + ), ]; } @@ -204,6 +284,9 @@ class _ZapWidget extends State { return GestureDetector( onTap: () => setState(() { + _error = null; + _customAmount.clear(); + _customAmountFocus.unfocus(); _amount = n; }), child: Container(