Skip to content

Commit

Permalink
Convert payee info dialog to fullscreen page UI
Browse files Browse the repository at this point in the history
Closes #215
  • Loading branch information
erdemyerebasmaz committed Oct 30, 2024
1 parent ef815dd commit 21eaa16
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 194 deletions.
6 changes: 6 additions & 0 deletions lib/app/routes/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:l_breez/routes/chainswap/send/send_chainswap_page.dart';
import 'package:l_breez/routes/dev/developers_view.dart';
import 'package:l_breez/routes/fiat_currencies/fiat_currency_settings.dart';
import 'package:l_breez/routes/home/home_page.dart';
import 'package:l_breez/routes/home/widgets/bottom_actions_bar/enter_payment_info_dialog.dart';
import 'package:l_breez/routes/initial_walkthrough/initial_walkthrough.dart';
import 'package:l_breez/routes/initial_walkthrough/mnemonics/enter_mnemonics_page.dart';
import 'package:l_breez/routes/initial_walkthrough/mnemonics/mnemonics_confirmation_page.dart';
Expand Down Expand Up @@ -116,6 +117,11 @@ Route? onGenerateRoute({
),
settings: settings,
);
case EnterPaymentInfoPage.routeName:
return FadeInRoute(
builder: (_) => const EnterPaymentInfoPage(),
settings: settings,
);
case SendChainSwapPage.routeName:
return FadeInRoute(
builder: (_) => BlocProvider(
Expand Down
298 changes: 120 additions & 178 deletions lib/routes/home/widgets/bottom_actions_bar/enter_payment_info_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,238 +5,180 @@ import 'package:flutter_breez_liquid/flutter_breez_liquid.dart';
import 'package:l_breez/cubit/cubit.dart';
import 'package:l_breez/routes/qr_scan/qr_scan.dart';
import 'package:l_breez/theme/theme.dart';
import 'package:l_breez/widgets/back_button.dart' as back_button;
import 'package:l_breez/widgets/flushbar.dart';
import 'package:l_breez/widgets/loader.dart';
import 'package:l_breez/widgets/single_button_bottom_bar.dart';
import 'package:logging/logging.dart';

final _log = Logger("EnterPaymentInfoDialog");
final _log = Logger("EnterPaymentInfoPage");

class EnterPaymentInfoDialog extends StatefulWidget {
final GlobalKey paymentItemKey;

const EnterPaymentInfoDialog({super.key, required this.paymentItemKey});
class EnterPaymentInfoPage extends StatefulWidget {
static const routeName = "/enter_payment_info";
const EnterPaymentInfoPage({super.key});

@override
State<StatefulWidget> createState() => EnterPaymentInfoDialogState();
State<StatefulWidget> createState() => _EnterPaymentInfoPageState();
}

class EnterPaymentInfoDialogState extends State<EnterPaymentInfoDialog> {
class _EnterPaymentInfoPageState extends State<EnterPaymentInfoPage> {
final _formKey = GlobalKey<FormState>();
final _paymentInfoController = TextEditingController();
final _paymentInfoFocusNode = FocusNode();

String _scannerErrorMessage = "";
String _validatorErrorMessage = "";

String errorMessage = "";
ModalRoute? _loaderRoute;

@override
void initState() {
super.initState();
_paymentInfoController.addListener(() {
setState(() {});
});
}

@override
Widget build(BuildContext context) {
final texts = context.texts();

return AlertDialog(
titlePadding: const EdgeInsets.fromLTRB(24.0, 22.0, 0.0, 16.0),
title: Text(texts.payment_info_dialog_title),
contentPadding: const EdgeInsets.fromLTRB(24.0, 8.0, 24.0, 24.0),
content: _buildPaymentInfoForm(context),
actions: _buildActions(context),
);
}

Theme _buildPaymentInfoForm(BuildContext context) {
final themeData = Theme.of(context);
final texts = context.texts();

return Theme(
data: themeData.copyWith(
inputDecorationTheme: InputDecorationTheme(
enabledBorder: UnderlineInputBorder(
borderSide: greyBorderSide,
),
),
hintColor: themeData.dialogTheme.contentTextStyle!.color!,
primaryColor: themeData.textTheme.labelLarge!.color,
colorScheme: ColorScheme.dark(
primary: themeData.textTheme.labelLarge!.color!,
error: themeData.isLightTheme ? Colors.red : themeData.colorScheme.error,
),
return Scaffold(
appBar: AppBar(
leading: const back_button.BackButton(),
title: Text(texts.payment_info_dialog_title),
),
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
decoration: InputDecoration(
labelText: texts.payment_info_dialog_hint,
suffixIcon: IconButton(
padding: const EdgeInsets.only(top: 21.0),
alignment: Alignment.bottomRight,
icon: Image(
image: const AssetImage("assets/icons/qr_scan.png"),
color: themeData.primaryIconTheme.color,
width: 24.0,
height: 24.0,
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _paymentInfoController,
decoration: InputDecoration(
labelText: texts.payment_info_dialog_hint,
suffixIcon: IconButton(
padding: const EdgeInsets.only(top: 21.0),
alignment: Alignment.bottomRight,
icon: Image(
image: const AssetImage("assets/icons/qr_scan.png"),
color: themeData.primaryIconTheme.color,
width: 24.0,
height: 24.0,
),
tooltip: texts.payment_info_dialog_barcode,
onPressed: () => _scanBarcode(),
),
tooltip: texts.payment_info_dialog_barcode,
onPressed: () => _scanBarcode(context),
),
style: TextStyle(color: themeData.primaryTextTheme.headlineMedium!.color),
validator: (value) => errorMessage.isNotEmpty ? errorMessage : null,
onFieldSubmitted: (input) async {
if (input.isNotEmpty) {
setState(() {
_paymentInfoController.text = input;
});
await _validateInput();
}
},
),
focusNode: _paymentInfoFocusNode,
controller: _paymentInfoController,
style: TextStyle(
color: themeData.primaryTextTheme.headlineMedium!.color,
),
validator: (value) {
if (_validatorErrorMessage.isNotEmpty) {
return _validatorErrorMessage;
}
return null;
},
),
if (_scannerErrorMessage.isNotEmpty) ...[
Padding(
padding: const EdgeInsets.only(top: 8.0),
padding: const EdgeInsets.only(top: 8),
child: Text(
_scannerErrorMessage,
style: validatorStyle,
texts.payment_info_dialog_hint_expanded,
style: FieldTextStyle.labelStyle.copyWith(
fontSize: 13.0,
color: themeData.isLightTheme ? BreezColors.grey[500] : BreezColors.white[200],
),
),
),
],
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
texts.payment_info_dialog_hint_expanded,
style: FieldTextStyle.labelStyle.copyWith(
fontSize: 13.0,
color: themeData.isLightTheme ? BreezColors.grey[500] : BreezColors.white[200],
),
),
),
],
),
),
),
),
bottomNavigationBar: SingleButtonBottomBar(
text: _paymentInfoController.text.isNotEmpty && errorMessage.isEmpty
? texts.payment_info_dialog_action_approve
: texts.payment_info_dialog_action_cancel,
onPressed: _paymentInfoController.text.isNotEmpty && errorMessage.isEmpty
? _onApprovePressed
: () => Navigator.pop(context),
),
);
}

List<Widget> _buildActions(BuildContext context) {
final themeData = Theme.of(context);
void _scanBarcode() {
final texts = context.texts();

List<Widget> actions = [
SimpleDialogOption(
onPressed: () => Navigator.pop(context),
child: Text(
texts.payment_info_dialog_action_cancel,
style: themeData.primaryTextTheme.labelLarge,
),
)
];

if (_paymentInfoController.text.isNotEmpty) {
actions.add(
SimpleDialogOption(
onPressed: (() async {
final inputCubit = context.read<InputCubit>();
final navigator = Navigator.of(context);
_setLoading(true);

try {
await _validateInput(_paymentInfoController.text);
if (_formKey.currentState!.validate()) {
_setLoading(false);
navigator.pop();
inputCubit.addIncomingInput(_paymentInfoController.text, InputSource.inputField);
}
} catch (error) {
_setLoading(false);
_log.warning(error.toString(), error);
_setValidatorErrorMessage(texts.payment_info_dialog_error);
} finally {
_setLoading(false);
}
}),
child: Text(
texts.payment_info_dialog_action_approve,
style: themeData.primaryTextTheme.labelLarge,
),
),
);
}
return actions;
Focus.maybeOf(context)?.unfocus();
Navigator.pushNamed<String>(context, QRScan.routeName).then((barcode) async {
if (barcode == null || barcode.isEmpty) {
if (context.mounted) showFlushbar(context, message: texts.payment_info_dialog_error_qrcode);
return;
}
setState(() {
_paymentInfoController.text = barcode;
});
await _validateInput();
});
}

Future _scanBarcode(BuildContext context) async {
Future<void> _validateInput() async {
final texts = context.texts();

FocusScope.of(context).requestFocus(FocusNode());
String? barcode = await Navigator.pushNamed<String>(context, QRScan.routeName);
if (barcode == null) {
return;
}
if (barcode.isEmpty) {
if (!context.mounted) return;
showFlushbar(
context,
message: texts.payment_info_dialog_error_qrcode,
);
return;
}
final inputCubit = context.read<InputCubit>();
var errMsg = "";
setState(() {
_paymentInfoController.text = barcode;
_scannerErrorMessage = "";
_setValidatorErrorMessage("");
errorMessage = errMsg;
});
}

Future<void> _validateInput(String input) async {
final texts = context.texts();
try {
_setValidatorErrorMessage("");
final inputCubit = context.read<InputCubit>();
final inputType = await inputCubit.parseInput(input: input);
_log.info("Parsed input type: '${inputType.runtimeType.toString()}");
// Can't compare against a list of InputType as runtime type comparison is a bit tricky with binding generated enums
final inputType = await inputCubit.parseInput(input: _paymentInfoController.text);
if (!(inputType is InputType_Bolt11 ||
inputType is InputType_LnUrlPay ||
inputType is InputType_LnUrlWithdraw ||
inputType is InputType_LnUrlAuth ||
inputType is InputType_LnUrlError)) {
_setValidatorErrorMessage(texts.payment_info_dialog_error_unsupported_input);
inputType is InputType_LnUrlWithdraw)) {
errMsg = texts.payment_info_dialog_error_unsupported_input;
}
if (inputType is InputType_Bolt11 && inputType.invoice.amountMsat == BigInt.zero) {
errMsg = texts.payment_request_zero_amount_not_supported;
}
if (inputType is InputType_BitcoinAddress) {
errMsg = "Please use \"Send to BTC Address\" option from main menu.";
}
} catch (e) {
rethrow;
} catch (error) {
var errStr = error.toString();
errMsg = errStr.contains("Unrecognized") ? texts.payment_info_dialog_error_unsupported_input : errStr;
} finally {
setState(() {
errorMessage = errMsg;
});
}
}

_setValidatorErrorMessage(String errorMessage) {
setState(() {
_validatorErrorMessage = errorMessage;
});
_formKey.currentState?.validate();
}

Future<void> _onApprovePressed() async {
final inputCubit = context.read<InputCubit>();
_setLoading(true);

try {
await _validateInput();
if (_formKey.currentState!.validate()) {
if (mounted) {
Navigator.of(context).pop();
}
inputCubit.addIncomingInput(_paymentInfoController.text, InputSource.inputField);
}
} catch (error) {
_log.warning(error.toString(), error);
if (mounted) {
setState(() {
errorMessage = context.texts().payment_info_dialog_error;
});
}
} finally {
_setLoading(false);
}
}

void _setLoading(bool visible) {
if (visible && _loaderRoute == null) {
_loaderRoute = createLoaderRoute(context);
Navigator.of(context).push(_loaderRoute!);
return;
}

if (!visible && (_loaderRoute != null && _loaderRoute!.isActive)) {
_loaderRoute!.navigator?.removeRoute(_loaderRoute!);
} else if (!visible && _loaderRoute?.isActive == true) {
_loaderRoute?.navigator?.removeRoute(_loaderRoute!);
_loaderRoute = null;
}
}
Expand Down
Loading

0 comments on commit 21eaa16

Please sign in to comment.