mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
Cw 921 create new bottom sheet sending confirmation UI (#2099)
* init commit * add slide button * address book popup * fix colors for themes * update exchange page * update cake pay purchase card page * Update rbf_details_page.dart * refactored code * add generating tx bottom sheet * add show scrollbar * minor fixes
This commit is contained in:
parent
00642e6027
commit
626e532fce
12 changed files with 1192 additions and 271 deletions
31
assets/images/birthday_cake.svg
Normal file
31
assets/images/birthday_cake.svg
Normal file
|
@ -0,0 +1,31 @@
|
|||
<svg width="163" height="178" viewBox="0 0 163 178" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M47.5763 28.5814C46.1363 28.5814 44.9689 27.414 44.9689 25.974V22.2461C44.9689 20.8061 46.1363 19.6387 47.5763 19.6387C49.0163 19.6387 50.1837 20.8061 50.1837 22.2461V25.974C50.1837 27.414 49.0163 28.5814 47.5763 28.5814Z" fill="#14C846"/>
|
||||
<path d="M81.252 28.5814C79.812 28.5814 78.6445 27.414 78.6445 25.974V22.2461C78.6445 20.8061 79.812 19.6387 81.252 19.6387C82.6919 19.6387 83.8594 20.8061 83.8594 22.2461V25.974C83.8594 27.414 82.6919 28.5814 81.252 28.5814Z" fill="#14C846"/>
|
||||
<path d="M114.928 28.5814C113.488 28.5814 112.32 27.414 112.32 25.974V22.2461C112.32 20.8061 113.488 19.6387 114.928 19.6387C116.368 19.6387 117.535 20.8061 117.535 22.2461V25.974C117.535 27.414 116.368 28.5814 114.928 28.5814Z" fill="#14C846"/>
|
||||
<path d="M119.675 5.23777C118.208 2.28443 115.374 -2.62586 112.117 1.72088C109.646 5.02118 107.251 11.045 111.084 14.2782C113.143 16.0161 116.707 16.0196 118.771 14.2782C121.501 11.9753 121.084 8.07603 119.675 5.23777Z" fill="#FC914A"/>
|
||||
<path d="M85.9992 5.23777C84.5328 2.28443 81.698 -2.62586 78.4419 1.72088C75.9701 5.02118 73.5754 11.045 77.408 14.2782C79.4678 16.0161 83.0317 16.0196 85.0957 14.2782C87.8255 11.9753 87.4086 8.07603 85.9992 5.23777Z" fill="#FC914A"/>
|
||||
<path d="M147.295 113.917H15.2089V60.2027C15.2089 54.4747 19.8525 49.8311 25.5805 49.8311H136.923C142.651 49.8311 147.295 54.4747 147.295 60.2027V113.917Z" fill="#A06047"/>
|
||||
<path d="M120.67 56.9909H109.185C108.196 56.9909 107.395 56.1892 107.395 55.2005V27.7645C107.395 26.7758 108.196 25.9741 109.185 25.9741H120.67C121.659 25.9741 122.461 26.7758 122.461 27.7645V55.2008C122.461 56.1896 121.659 56.9909 120.67 56.9909Z" fill="#14C846"/>
|
||||
<path d="M86.9945 56.9909H75.5094C74.5206 56.9909 73.7189 56.1892 73.7189 55.2005V27.7645C73.7189 26.7758 74.5206 25.9741 75.5094 25.9741H86.9945C87.9832 25.9741 88.7849 26.7758 88.7849 27.7645V55.2008C88.7846 56.1896 87.9832 56.9909 86.9945 56.9909Z" fill="#14C846"/>
|
||||
<path d="M52.3235 5.23777C50.8571 2.28443 48.0223 -2.62586 44.7662 1.72088C42.2943 5.02118 39.8997 11.045 43.7322 14.2782C45.7921 16.0161 49.3559 16.0196 51.4199 14.2782C54.1497 11.9753 53.7326 8.07603 52.3235 5.23777Z" fill="#FC914A"/>
|
||||
<path d="M51.4183 14.2785C49.8107 15.6354 47.2898 15.9327 45.2776 15.1737C45.8432 14.9589 46.3696 14.6616 46.8243 14.2785C49.5527 11.9764 49.1373 8.07705 47.7265 5.2374C47.1 3.96985 46.2191 2.34421 45.1591 1.24145C48.249 -2.23546 50.9131 2.40158 52.3239 5.2374C53.7316 8.07705 54.1505 11.9764 51.4183 14.2785Z" fill="#EA7636"/>
|
||||
<path d="M85.0943 14.2785C83.4867 15.6354 80.9659 15.9327 78.9536 15.1737C79.5193 14.9589 80.0456 14.6616 80.5004 14.2785C83.2288 11.9764 82.8133 8.07705 81.4025 5.2374C80.776 3.96985 79.8951 2.34421 78.8351 1.24145C81.925 -2.23546 84.5891 2.40158 85.9999 5.2374C87.4076 8.07705 87.8265 11.9764 85.0943 14.2785Z" fill="#EA7636"/>
|
||||
<path d="M118.77 14.2785C117.163 15.6354 114.642 15.9327 112.63 15.1737C113.195 14.9589 113.722 14.6616 114.176 14.2785C116.905 11.9764 116.489 8.07705 115.078 5.2374C114.452 3.96985 113.571 2.34421 112.511 1.24145C115.601 -2.23546 118.265 2.40158 119.676 5.2374C121.083 8.07705 121.502 11.9764 118.77 14.2785Z" fill="#EA7636"/>
|
||||
<path d="M53.3189 56.9909H41.8338C40.845 56.9909 40.0433 56.1892 40.0433 55.2005V27.7645C40.0433 26.7758 40.845 25.9741 41.8338 25.9741H53.3189C54.3076 25.9741 55.1093 26.7758 55.1093 27.7645V55.2008C55.109 56.1896 54.3076 56.9909 53.3189 56.9909Z" fill="#14C846"/>
|
||||
<path d="M55.11 27.7659V55.2005C55.11 56.1888 54.3079 56.9909 53.3196 56.9909H48.8331V25.9758H53.3196C54.3079 25.9758 55.11 26.7744 55.11 27.7659Z" fill="#0EA939"/>
|
||||
<path d="M88.7846 27.7659V55.2005C88.7846 56.1888 87.9826 56.9909 86.9942 56.9909H82.5077V25.9758H86.9942C87.9826 25.9758 88.7846 26.7744 88.7846 27.7659Z" fill="#0EA939"/>
|
||||
<path d="M122.461 27.7659V55.2005C122.461 56.1888 121.659 56.9909 120.67 56.9909H116.184V25.9758H120.67C121.659 25.9758 122.461 26.7744 122.461 27.7659Z" fill="#0EA939"/>
|
||||
<path d="M147.295 60.2028V113.916H134.809V60.2028C134.809 54.4738 130.169 49.8298 124.44 49.8298H136.922C142.651 49.8298 147.295 54.4738 147.295 60.2028Z" fill="#824730"/>
|
||||
<path d="M147.296 60.2028V65.6885C144.844 67.6973 142.502 69.7953 137.607 69.7953C136.569 69.7953 135.645 69.7022 134.811 69.5304V60.2028C134.811 54.4738 130.17 49.8298 124.441 49.8298H136.923C142.652 49.8298 147.296 54.4738 147.296 60.2028Z" fill="#F7D443"/>
|
||||
<path d="M162.503 178H0.000671387V124.286C0.000671387 118.558 4.64431 113.915 10.3723 113.915H152.131C157.859 113.915 162.503 118.558 162.503 124.286L162.503 178Z" fill="#A06047"/>
|
||||
<path d="M162.503 124.286V177.999H147.296V124.286C147.296 118.557 142.652 113.916 136.923 113.916H152.13C157.859 113.916 162.503 118.557 162.503 124.286Z" fill="#824730"/>
|
||||
<path d="M162.503 124.286V129.771C159.488 131.78 156.602 133.882 150.583 133.882C149.37 133.882 148.281 133.796 147.296 133.642V124.286C147.296 118.557 142.652 113.917 136.923 113.917H152.13C157.859 113.916 162.503 118.557 162.503 124.286Z" fill="#F7D443"/>
|
||||
<path d="M147.293 83.5281H15.2078V96.4157H147.293V83.5281Z" fill="white"/>
|
||||
<path d="M147.294 83.5281H134.808V96.4157H147.294V83.5281Z" fill="#C7ABA1"/>
|
||||
<path d="M162.502 147.615H0.000671387V160.502H162.502V147.615Z" fill="white"/>
|
||||
<path d="M162.504 147.615H147.296V160.502H162.504V147.615Z" fill="#C7ABA1"/>
|
||||
<path d="M147.295 60.2028V65.6868C144.844 67.6973 142.501 69.7971 137.606 69.7971C128.214 69.7971 128.214 62.0638 118.822 62.0638C109.43 62.0638 109.43 69.7971 100.034 69.7971C90.6422 69.7971 90.6422 62.0638 81.2502 62.0638C71.8583 62.0638 71.8583 69.7971 62.4664 69.7971C53.0745 69.7971 53.0745 62.0638 43.6826 62.0638C34.2907 62.0638 34.2907 69.7971 24.8988 69.7971C20.0076 69.7971 17.6644 67.6976 15.2131 65.6868V60.2028C15.2131 54.4738 19.8574 49.8298 25.5861 49.8298H136.922C142.651 49.8298 147.295 54.4738 147.295 60.2028Z" fill="#2194FF"/>
|
||||
<path d="M147.296 61.03V65.6883C144.844 67.697 142.502 69.7951 137.607 69.7951C136.569 69.7951 135.645 69.7019 134.811 69.5302V60.2026C134.811 54.4736 130.17 49.8296 124.441 49.8296H136.096C143.689 49.8299 147.296 54.6637 147.296 61.03Z" fill="#1585EC"/>
|
||||
<path d="M162.504 125.114V129.77C159.488 131.781 156.604 133.881 150.584 133.881C139.026 133.881 139.026 126.148 127.469 126.148C115.916 126.148 115.916 133.881 104.359 133.881C92.8053 133.881 92.8053 126.148 81.2481 126.148C69.6949 126.148 69.6949 133.881 58.1377 133.881C46.5845 133.881 46.5845 126.148 35.0273 126.148C23.4741 126.148 23.4741 133.881 11.9169 133.881C5.89659 133.881 3.01591 131.781 0 129.77V124.287C0 118.558 4.64433 113.914 10.373 113.914H151.305C157.49 113.914 162.504 118.928 162.504 125.114Z" fill="#2194FF"/>
|
||||
<path d="M162.503 125.113V129.771C159.488 131.78 156.602 133.882 150.583 133.882C149.37 133.882 148.281 133.796 147.296 133.642V124.286C147.296 118.557 142.652 113.916 136.923 113.916H151.593C158.474 113.916 162.503 118.048 162.503 125.113Z" fill="#1585EC"/>
|
||||
</svg>
|
After Width: | Height: | Size: 6.9 KiB |
3
assets/images/contact_icon.svg
Normal file
3
assets/images/contact_icon.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="126" height="97" viewBox="0 0 126 97" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.56 0.640015C3.41578 0.640015 0 4.0558 0 8.20001V88.84C0 92.9842 3.41578 96.4 7.56 96.4H25.2V91.36H30.24V96.4H95.76V91.36H100.8V96.4H118.44C122.584 96.4 126 92.9842 126 88.84V18.28C126 14.1358 122.584 10.72 118.44 10.72H65.52V8.20001C65.52 4.0558 62.1042 0.640015 57.96 0.640015H7.56ZM7.56 5.68001H57.96C59.3775 5.68001 60.48 6.78251 60.48 8.20001V15.76H118.44C119.857 15.76 120.96 16.8625 120.96 18.28V88.84C120.96 90.2575 119.857 91.36 118.44 91.36H105.84V86.32H90.72V91.36H35.28V86.32H20.16V91.36H7.56C6.1425 91.36 5.04 90.2575 5.04 88.84V8.20001C5.04 6.78251 6.1425 5.68001 7.56 5.68001ZM33.9412 28.36C25.4756 28.5175 24.4519 34.9553 26.3025 42.3775C25.9777 42.5842 25.4559 43.3225 25.5938 44.5038C25.8497 46.6989 26.6962 47.2108 27.2475 47.26C27.4542 49.3075 28.7536 51.7586 29.4525 52.1425C29.4525 53.5994 29.5116 54.7117 29.3738 56.3163C27.7003 60.992 15.6417 59.6631 15.12 68.68H52.92C52.3983 59.6631 40.4184 60.992 38.745 56.3163C38.6072 54.7117 38.6662 53.5994 38.6662 52.1425C39.3652 51.7586 40.5759 49.3075 40.7925 47.26C41.3438 47.2108 42.1903 46.6989 42.4463 44.5038C42.5841 43.3225 42.0623 42.663 41.7375 42.4563C42.6234 39.6705 44.4445 30.9883 38.2725 30.0925C37.6327 28.931 36.0577 28.36 33.9412 28.36ZM65.52 38.44V43.48H110.88V38.44H65.52ZM65.52 51.04V56.08H110.88V51.04H65.52ZM65.52 63.64V68.68H110.88V63.64H65.52Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -2,6 +2,8 @@ abstract class ExecutionState {}
|
|||
|
||||
class InitialExecutionState extends ExecutionState {}
|
||||
|
||||
class LoadingTemplateExecutingState extends ExecutionState {}
|
||||
|
||||
class IsExecutingState extends ExecutionState {}
|
||||
|
||||
class ExecutedSuccessfullyState extends ExecutionState {
|
||||
|
|
|
@ -7,10 +7,11 @@ import 'package:cake_wallet/src/screens/cake_pay/widgets/cake_pay_alert_modal.da
|
|||
import 'package:cake_wallet/src/screens/cake_pay/widgets/image_placeholder.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/link_extractor.dart';
|
||||
import 'package:cake_wallet/src/screens/cake_pay/widgets/text_icon_button.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
|
||||
|
@ -371,39 +372,39 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
});
|
||||
|
||||
final order = cakePayPurchaseViewModel.order;
|
||||
final pendingTransaction = cakePayPurchaseViewModel.sendViewModel.pendingTransaction!;
|
||||
|
||||
await showPopUp<void>(
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
builder: (popupContext) {
|
||||
return Observer(
|
||||
builder: (_) => ConfirmSendingAlert(
|
||||
alertTitle: S.of(popupContext).confirm_sending,
|
||||
paymentId: S.of(popupContext).payment_id,
|
||||
paymentIdValue: order?.orderId,
|
||||
expirationTime: cakePayPurchaseViewModel.formattedRemainingTime,
|
||||
onDispose: () => _handleDispose(disposer),
|
||||
amount: S.of(popupContext).send_amount,
|
||||
amountValue: pendingTransaction.amountFormatted,
|
||||
fiatAmountValue:
|
||||
cakePayPurchaseViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(popupContext).send_fee,
|
||||
feeValue: pendingTransaction.feeFormatted,
|
||||
feeFiatAmount:
|
||||
cakePayPurchaseViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
feeRate: pendingTransaction.feeRate,
|
||||
outputs: cakePayPurchaseViewModel.sendViewModel.outputs,
|
||||
rightButtonText: S.of(popupContext).send,
|
||||
leftButtonText: S.of(popupContext).cancel,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(context).pop();
|
||||
await cakePayPurchaseViewModel.sendViewModel.commitTransaction(context);
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(popupContext).pop()));
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext popupContext) {
|
||||
return ConfirmSendingBottomSheet(
|
||||
key: ValueKey('send_page_confirm_sending_dialog_key'),
|
||||
currentTheme: currentTheme,
|
||||
paymentId: S.of(popupContext).payment_id,
|
||||
paymentIdValue: order?.orderId,
|
||||
expirationTime: cakePayPurchaseViewModel.formattedRemainingTime,
|
||||
titleText: 'Confirm Transaction',
|
||||
titleIconPath: cakePayPurchaseViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
|
||||
currency: cakePayPurchaseViewModel.sendViewModel.selectedCryptoCurrency,
|
||||
amount: S.of(popupContext).send_amount,
|
||||
amountValue: cakePayPurchaseViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fiatAmountValue: cakePayPurchaseViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(popupContext).send_fee,
|
||||
feeValue: cakePayPurchaseViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: cakePayPurchaseViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: cakePayPurchaseViewModel.sendViewModel.outputs,
|
||||
onSlideComplete: () async {
|
||||
Navigator.of(popupContext).pop();
|
||||
cakePayPurchaseViewModel.sendViewModel.commitTransaction(context);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
BuildContext? loadingBottomSheetContext;
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
|
@ -416,6 +417,29 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
});
|
||||
}
|
||||
|
||||
if (state is! IsExecutingState &&
|
||||
loadingBottomSheetContext != null &&
|
||||
loadingBottomSheetContext!.mounted) {
|
||||
Navigator.of(loadingBottomSheetContext!).pop();
|
||||
}
|
||||
|
||||
if (state is IsExecutingState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (context.mounted) {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
loadingBottomSheetContext = context;
|
||||
return LoadingBottomSheet(
|
||||
titleText: 'Generating transaction',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _showConfirmSendingAlert(context);
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/widgets/exchange_trade_card_item_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'dart:ui';
|
||||
|
@ -13,7 +16,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
|
@ -209,6 +211,8 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
|||
);
|
||||
}
|
||||
|
||||
BuildContext? loadingBottomSheetContext;
|
||||
|
||||
void _setEffects() {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
|
@ -216,6 +220,13 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
|||
|
||||
_exchangeStateReaction = reaction((_) => this.widget.exchangeTradeViewModel.sendViewModel.state,
|
||||
(ExecutionState state) {
|
||||
|
||||
if (state is! IsExecutingState &&
|
||||
loadingBottomSheetContext != null &&
|
||||
loadingBottomSheetContext!.mounted) {
|
||||
Navigator.of(loadingBottomSheetContext!).pop();
|
||||
}
|
||||
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
|
@ -232,163 +243,92 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
|||
});
|
||||
}
|
||||
|
||||
if (state is IsExecutingState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (context.mounted) {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
loadingBottomSheetContext = context;
|
||||
return LoadingBottomSheet(
|
||||
titleText: 'Generating transaction',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
if (context.mounted) {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return ConfirmSendingAlert(
|
||||
key: ValueKey('exchange_trade_page_confirm_sending_dialog_key'),
|
||||
alertLeftActionButtonKey: ValueKey('exchange_trade_page_confirm_sending_dialog_cancel_button_key'),
|
||||
alertRightActionButtonKey:
|
||||
ValueKey('exchange_trade_page_confirm_sending_dialog_send_button_key'),
|
||||
alertTitle: S.of(popupContext).confirm_sending,
|
||||
amount: S.of(popupContext).send_amount,
|
||||
amountValue: widget.exchangeTradeViewModel.sendViewModel
|
||||
.pendingTransaction!.amountFormatted,
|
||||
fee: S.of(popupContext).send_fee,
|
||||
feeValue: widget.exchangeTradeViewModel.sendViewModel
|
||||
.pendingTransaction!.feeFormatted,
|
||||
feeRate: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeRate,
|
||||
rightButtonText: S.of(popupContext).send,
|
||||
leftButtonText: S.of(popupContext).cancel,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(popupContext).pop();
|
||||
await widget.exchangeTradeViewModel.sendViewModel
|
||||
.commitTransaction(context);
|
||||
transactionStatePopup();
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(popupContext).pop(),
|
||||
feeFiatAmount: widget.exchangeTradeViewModel
|
||||
.pendingTransactionFeeFiatAmountFormatted,
|
||||
fiatAmountValue: widget.exchangeTradeViewModel
|
||||
.pendingTransactionFiatAmountValueFormatted,
|
||||
outputs: widget.exchangeTradeViewModel.sendViewModel
|
||||
.outputs);
|
||||
});
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext bottomSheetContext) {
|
||||
return ConfirmSendingBottomSheet(
|
||||
key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'),
|
||||
currentTheme: widget.currentTheme,
|
||||
titleText: 'Confirm Transaction',
|
||||
titleIconPath: widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
|
||||
currency: widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency,
|
||||
amount: S.of(bottomSheetContext).send_amount,
|
||||
amountValue: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fiatAmountValue: widget.exchangeTradeViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: isEVMCompatibleChain(widget.exchangeTradeViewModel.sendViewModel.walletType)
|
||||
? S.of(bottomSheetContext).send_estimated_fee
|
||||
: S.of(bottomSheetContext).send_fee,
|
||||
feeValue: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: widget.exchangeTradeViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: widget.exchangeTradeViewModel.sendViewModel.outputs,
|
||||
onSlideComplete: () async {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
widget.exchangeTradeViewModel.sendViewModel.commitTransaction(context);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (state is TransactionCommitted) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).sending,
|
||||
alertContent: S.of(popupContext).transaction_sent,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop());
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext bottomSheetContext) {
|
||||
return InfoBottomSheet(
|
||||
currentTheme: widget.currentTheme,
|
||||
titleText: 'Transaction Sent',
|
||||
contentImage: 'assets/images/birthday_cake.svg',
|
||||
actionButtonText: S.of(bottomSheetContext).close,
|
||||
actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'),
|
||||
actionButton: () {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
Routes.dashboard,
|
||||
(route) => false,
|
||||
);
|
||||
RequestReviewHandler.requestReview();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
_effectsInstalled = true;
|
||||
}
|
||||
|
||||
void transactionStatePopup() {
|
||||
if (this.mounted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return Observer(builder: (_) {
|
||||
final state = widget
|
||||
.exchangeTradeViewModel.sendViewModel.state;
|
||||
|
||||
if (state is TransactionCommitted) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
color: Theme.of(popupContext).colorScheme.background,
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
'assets/images/birthday_cake.png'),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 220, left: 24, right: 24),
|
||||
child: Text(
|
||||
S.of(popupContext).send_success(widget
|
||||
.exchangeTradeViewModel
|
||||
.wallet
|
||||
.currency
|
||||
.toString()),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(popupContext).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 24,
|
||||
right: 24,
|
||||
bottom: 24,
|
||||
child: PrimaryButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
popupContext,
|
||||
Routes.dashboard,
|
||||
(route) => false,
|
||||
);
|
||||
RequestReviewHandler.requestReview();
|
||||
},
|
||||
text: S.of(popupContext).got_it,
|
||||
color: Theme.of(popupContext).primaryColor,
|
||||
textColor: Colors.white))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
color: Theme.of(popupContext).colorScheme.background,
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
'assets/images/birthday_cake.png'),
|
||||
),
|
||||
),
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 3.0, sigmaY: 3.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(popupContext)
|
||||
.colorScheme
|
||||
.background
|
||||
.withOpacity(0.25)),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 220),
|
||||
child: Text(
|
||||
S.of(popupContext).send_sending,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(popupContext).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ExchangeTradeItemsCardSection extends StatelessWidget {
|
||||
|
|
|
@ -12,18 +12,20 @@ import 'package:cake_wallet/routes.dart';
|
|||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
|
||||
import 'package:cake_wallet/src/widgets/adaptable_page_view.dart';
|
||||
import 'package:cake_wallet/src/widgets/add_template_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/template_tile.dart';
|
||||
import 'package:cake_wallet/src/widgets/trail_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/seed_widget_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
|
@ -162,6 +164,8 @@ class SendPage extends BasePage {
|
|||
});
|
||||
});
|
||||
|
||||
bool _bottomSheetOpened = false;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
_setEffects(context);
|
||||
|
@ -290,7 +294,7 @@ class SendPage extends BasePage {
|
|||
? template.cryptoCurrency
|
||||
: template.fiatCurrency,
|
||||
onTap: () async {
|
||||
sendViewModel.state = IsExecutingState();
|
||||
sendViewModel.state = LoadingTemplateExecutingState();
|
||||
if (template.additionalRecipients?.isNotEmpty ??
|
||||
false) {
|
||||
sendViewModel.clearOutputs();
|
||||
|
@ -464,7 +468,8 @@ class SendPage extends BasePage {
|
|||
textColor: Colors.white,
|
||||
isLoading: sendViewModel.state is IsExecutingState ||
|
||||
sendViewModel.state is TransactionCommitting ||
|
||||
sendViewModel.state is IsAwaitingDeviceResponseState,
|
||||
sendViewModel.state is IsAwaitingDeviceResponseState ||
|
||||
sendViewModel.state is LoadingTemplateExecutingState,
|
||||
isDisabled: !sendViewModel.isReadyForSend,
|
||||
);
|
||||
},
|
||||
|
@ -479,6 +484,7 @@ class SendPage extends BasePage {
|
|||
}
|
||||
|
||||
BuildContext? dialogContext;
|
||||
BuildContext? loadingBottomSheetContext;
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) {
|
||||
|
@ -494,6 +500,13 @@ class SendPage extends BasePage {
|
|||
Navigator.of(dialogContext!).pop();
|
||||
}
|
||||
|
||||
if (state is! IsExecutingState &&
|
||||
loadingBottomSheetContext != null &&
|
||||
loadingBottomSheetContext!.mounted) {
|
||||
Navigator.of(loadingBottomSheetContext!).pop();
|
||||
}
|
||||
|
||||
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
|
@ -510,92 +523,110 @@ class SendPage extends BasePage {
|
|||
});
|
||||
}
|
||||
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
if (state is IsExecutingState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (context.mounted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext _dialogContext) {
|
||||
return ConfirmSendingAlert(
|
||||
key: ValueKey('send_page_confirm_sending_dialog_key'),
|
||||
alertTitle: S.of(_dialogContext).confirm_sending,
|
||||
amount: S.of(_dialogContext).send_amount,
|
||||
amountValue: sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: isEVMCompatibleChain(sendViewModel.walletType)
|
||||
? S.of(_dialogContext).send_estimated_fee
|
||||
: S.of(_dialogContext).send_fee,
|
||||
feeRate: sendViewModel.pendingTransaction!.feeRate,
|
||||
feeValue: sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: sendViewModel.outputs,
|
||||
change: sendViewModel.pendingTransaction!.change,
|
||||
rightButtonText: S.of(_dialogContext).send,
|
||||
leftButtonText: S.of(_dialogContext).cancel,
|
||||
alertRightActionButtonKey:
|
||||
ValueKey('send_page_confirm_sending_dialog_send_button_key'),
|
||||
alertLeftActionButtonKey:
|
||||
ValueKey('send_page_confirm_sending_dialog_cancel_button_key'),
|
||||
actionRightButton: () async {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
sendViewModel.commitTransaction(context);
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(_dialogContext).pop());
|
||||
});
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
loadingBottomSheetContext = context;
|
||||
return LoadingBottomSheet(
|
||||
titleText: 'Generating transaction',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (context.mounted) {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext bottomSheetContext) {
|
||||
return ConfirmSendingBottomSheet(
|
||||
key: ValueKey('send_page_confirm_sending_dialog_key'),
|
||||
titleText: 'Confirm Transaction',
|
||||
currentTheme: currentTheme,
|
||||
titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath,
|
||||
currency: sendViewModel.selectedCryptoCurrency,
|
||||
amount: S.of(bottomSheetContext).send_amount,
|
||||
amountValue: sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: isEVMCompatibleChain(sendViewModel.walletType)
|
||||
? S.of(bottomSheetContext).send_estimated_fee
|
||||
: S.of(bottomSheetContext).send_fee,
|
||||
feeValue: sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: sendViewModel.outputs,
|
||||
onSlideComplete: () async {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
sendViewModel.commitTransaction(context);
|
||||
},
|
||||
change: sendViewModel.pendingTransaction!.change,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (state is TransactionCommitted) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final successMessage =
|
||||
S.of(context).send_success(sendViewModel.selectedCryptoCurrency.toString());
|
||||
|
||||
final waitMessage = sendViewModel.walletType == WalletType.solana
|
||||
? '. ${S.of(context).waitFewSecondForTxUpdate}'
|
||||
: '';
|
||||
|
||||
String alertContent = "$successMessage$waitMessage";
|
||||
|
||||
await Navigator.of(context)
|
||||
.pushNamed(Routes.transactionSuccessPage, arguments: alertContent);
|
||||
|
||||
newContactAddress = newContactAddress ?? sendViewModel.newContactAddress();
|
||||
|
||||
if (newContactAddress?.address != null && isRegularElectrumAddress(newContactAddress!.address)) {
|
||||
newContactAddress = null;
|
||||
}
|
||||
|
||||
if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) newContactAddress = null;
|
||||
|
||||
if (newContactAddress != null && sendViewModel.showAddressBookPopup) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext _dialogContext) => AlertWithTwoActions(
|
||||
alertDialogKey: ValueKey('send_page_sent_dialog_key'),
|
||||
alertTitle: '',
|
||||
alertContent: S.of(_dialogContext).add_contact_to_address_book,
|
||||
rightButtonText: S.of(_dialogContext).add_contact,
|
||||
leftButtonText: S.of(_dialogContext).ignor,
|
||||
alertLeftActionButtonKey: ValueKey('send_page_sent_dialog_ignore_button_key'),
|
||||
alertRightActionButtonKey:
|
||||
ValueKey('send_page_sent_dialog_add_contact_button_key'),
|
||||
actionRightButton: () {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
RequestReviewHandler.requestReview();
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.addressBookAddContact, arguments: newContactAddress);
|
||||
newContactAddress = null;
|
||||
},
|
||||
actionLeftButton: () {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
RequestReviewHandler.requestReview();
|
||||
newContactAddress = null;
|
||||
}));
|
||||
}
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
builder: (BuildContext bottomSheetContext) {
|
||||
return newContactAddress != null && sendViewModel.showAddressBookPopup
|
||||
? InfoBottomSheet(
|
||||
currentTheme: currentTheme,
|
||||
showDontAskMeCheckbox: true,
|
||||
onCheckboxChanged: (value) => sendViewModel.setShowAddressBookPopup(!value),
|
||||
titleText: 'Transaction Sent',
|
||||
contentImage: 'assets/images/contact_icon.svg',
|
||||
contentImageColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
content: S.of(bottomSheetContext).add_contact_to_address_book,
|
||||
isTwoAction: true,
|
||||
leftButtonText: 'No',
|
||||
rightButtonText: 'Yes',
|
||||
actionLeftButton: () {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
RequestReviewHandler.requestReview();
|
||||
newContactAddress = null;
|
||||
},
|
||||
actionRightButton: () {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
RequestReviewHandler.requestReview();
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.addressBookAddContact, arguments: newContactAddress);
|
||||
newContactAddress = null;
|
||||
},
|
||||
)
|
||||
: InfoBottomSheet(
|
||||
currentTheme: currentTheme,
|
||||
titleText: 'Transaction Sent',
|
||||
contentImage: 'assets/images/birthday_cake.svg',
|
||||
actionButtonText: S.of(bottomSheetContext).close,
|
||||
actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'),
|
||||
actionButton: () => Navigator.of(bottomSheetContext).pop());
|
||||
},
|
||||
);
|
||||
|
||||
if (initialPaymentRequest?.callbackUrl?.isNotEmpty ?? false) {
|
||||
// wait a second so it's not as jarring:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
|
@ -9,6 +8,8 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_expandab
|
|||
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_expandable_list.dart';
|
||||
|
@ -110,12 +111,21 @@ class RBFDetailsPage extends BasePage {
|
|||
);
|
||||
}
|
||||
|
||||
BuildContext? loadingBottomSheetContext;
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) {
|
||||
|
||||
if (state is! IsExecutingState &&
|
||||
loadingBottomSheetContext != null &&
|
||||
loadingBottomSheetContext!.mounted) {
|
||||
Navigator.of(loadingBottomSheetContext!).pop();
|
||||
}
|
||||
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
|
@ -151,35 +161,56 @@ class RBFDetailsPage extends BasePage {
|
|||
});
|
||||
}
|
||||
|
||||
if (state is IsExecutingState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (context.mounted) {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
loadingBottomSheetContext = context;
|
||||
return LoadingBottomSheet(
|
||||
titleText: 'Generating transaction',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
if (context.mounted) {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return ConfirmSendingAlert(
|
||||
alertTitle: S.of(popupContext).confirm_sending,
|
||||
amount: S.of(popupContext).send_amount,
|
||||
amountValue: transactionDetailsViewModel
|
||||
.sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fee: S.of(popupContext).send_fee,
|
||||
feeValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||
rightButtonText: S.of(popupContext).send,
|
||||
leftButtonText: S.of(popupContext).cancel,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(popupContext).pop();
|
||||
await transactionDetailsViewModel.sendViewModel.commitTransaction(context);
|
||||
try {
|
||||
Navigator.of(popupContext).pop();
|
||||
} catch (_) {}
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(popupContext).pop(),
|
||||
feeFiatAmount:
|
||||
transactionDetailsViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
fiatAmountValue:
|
||||
transactionDetailsViewModel.pendingTransactionFiatAmountValueFormatted,
|
||||
outputs: transactionDetailsViewModel.sendViewModel.outputs);
|
||||
});
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext bottomSheetContext) {
|
||||
return ConfirmSendingBottomSheet(
|
||||
key: ValueKey('rbf_confirm_sending_bottom_sheet'),
|
||||
titleText: 'Confirm Transaction',
|
||||
currentTheme: currentTheme,
|
||||
titleIconPath: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
|
||||
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
|
||||
amount: S.of(bottomSheetContext).send_amount,
|
||||
amountValue: transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fiatAmountValue: transactionDetailsViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(bottomSheetContext).send_fee,
|
||||
feeValue: transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: transactionDetailsViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: transactionDetailsViewModel.sendViewModel.outputs,
|
||||
onSlideComplete: () async {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
await transactionDetailsViewModel.sendViewModel.commitTransaction(context);
|
||||
try {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
} catch (_) {}
|
||||
},
|
||||
change: transactionDetailsViewModel.sendViewModel.pendingTransaction!.change,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
83
lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart
Normal file
83
lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart
Normal file
|
@ -0,0 +1,83 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
|
||||
abstract class BaseBottomSheet extends StatelessWidget {
|
||||
final String titleText;
|
||||
final String? titleIconPath;
|
||||
|
||||
const BaseBottomSheet({required this.titleText, this.titleIconPath});
|
||||
|
||||
Widget headerWidget(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(flex: 4),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(flex: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (titleIconPath != null)
|
||||
Image.asset(titleIconPath!, height: 24, width: 24)
|
||||
else
|
||||
Container(),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
titleText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 13),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget contentWidget(BuildContext context);
|
||||
|
||||
Widget footerWidget(BuildContext context);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: 600),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(30.0), topRight: Radius.circular(30.0)),
|
||||
child: Container(
|
||||
color: Theme.of(context).dialogBackgroundColor,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
headerWidget(context),
|
||||
contentWidget(context),
|
||||
footerWidget(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,450 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_slide_button_widget.dart';
|
||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'base_bottom_sheet_widget.dart';
|
||||
|
||||
class ConfirmSendingBottomSheet extends BaseBottomSheet {
|
||||
final CryptoCurrency currency;
|
||||
final ThemeBase currentTheme;
|
||||
final String? paymentId;
|
||||
final String? paymentIdValue;
|
||||
final String? expirationTime;
|
||||
final String amount;
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
final String fee;
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
final VoidCallback onSlideComplete;
|
||||
final PendingChange? change;
|
||||
|
||||
ConfirmSendingBottomSheet({
|
||||
required String titleText,
|
||||
String? titleIconPath,
|
||||
required this.currency,
|
||||
required this.currentTheme,
|
||||
this.paymentId,
|
||||
this.paymentIdValue,
|
||||
this.expirationTime,
|
||||
required this.amount,
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
required this.fee,
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs,
|
||||
required this.onSlideComplete,
|
||||
this.change,
|
||||
Key? key,
|
||||
}) : showScrollbar = outputs.length > 3,
|
||||
super(titleText: titleText, titleIconPath: titleIconPath);
|
||||
|
||||
final bool showScrollbar;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) {
|
||||
final itemTitleTextStyle = TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
final itemSubTitleTextStyle = TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: currentTheme.type == ThemeType.bright
|
||||
? Theme.of(context).extension<CakeTextTheme>()!.titleColor
|
||||
: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
|
||||
Widget content = Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, 0, showScrollbar ? 16 : 8, 8),
|
||||
child: Column(
|
||||
children: [
|
||||
if (paymentId != null && paymentIdValue != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: AddressTile(
|
||||
itemTitle: paymentId!,
|
||||
currentTheme: currentTheme,
|
||||
itemTitleTextStyle: itemTitleTextStyle,
|
||||
isBatchSending: false,
|
||||
amount: '',
|
||||
address: paymentIdValue!,
|
||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||
),
|
||||
),
|
||||
StandardTile(
|
||||
itemTitle: amount,
|
||||
itemValue: amountValue + ' ${currency.title}',
|
||||
itemTitleTextStyle: itemTitleTextStyle,
|
||||
itemSubTitle: fiatAmountValue,
|
||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
StandardTile(
|
||||
itemTitle: fee,
|
||||
itemValue: feeValue,
|
||||
itemTitleTextStyle: itemTitleTextStyle,
|
||||
itemSubTitle: feeFiatAmount,
|
||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Column(
|
||||
children: [
|
||||
ListView.separated(
|
||||
padding: const EdgeInsets.only(top: 0),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: outputs.length,
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final bool isBatchSending = outputs.length > 1;
|
||||
final item = outputs[index];
|
||||
final contactName = item.parsedAddress.name;
|
||||
final batchContactTitle =
|
||||
'${index + 1}/${outputs.length} - ${contactName.isEmpty ? 'Address' : contactName}';
|
||||
final _address = item.isParsedAddress ? item.extractedAddress : item.address;
|
||||
final _amount = item.cryptoAmount.replaceAll(',', '.') + ' ${currency.title}';
|
||||
return isBatchSending || contactName.isNotEmpty
|
||||
? AddressExpansionTile(
|
||||
contactType: 'Contact',
|
||||
currentTheme: currentTheme,
|
||||
name: isBatchSending ? batchContactTitle : contactName,
|
||||
address: _address,
|
||||
amount: _amount,
|
||||
isBatchSending: isBatchSending,
|
||||
itemTitleTextStyle: itemTitleTextStyle,
|
||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||
)
|
||||
: AddressTile(
|
||||
itemTitle: 'Address',
|
||||
currentTheme: currentTheme,
|
||||
itemTitleTextStyle: itemTitleTextStyle,
|
||||
isBatchSending: isBatchSending,
|
||||
amount: _amount,
|
||||
address: _address,
|
||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||
);
|
||||
},
|
||||
),
|
||||
if (change != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: AddressExpansionTile(
|
||||
contactType: 'Change',
|
||||
currentTheme: currentTheme,
|
||||
name: S.of(context).send_change_to_you,
|
||||
address: change!.address,
|
||||
amount: change!.amount + ' ${currency.title}',
|
||||
isBatchSending: true,
|
||||
itemTitleTextStyle: itemTitleTextStyle,
|
||||
itemSubTitleTextStyle: itemSubTitleTextStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (showScrollbar) {
|
||||
return SizedBox(
|
||||
height: 380,
|
||||
child: Scrollbar(
|
||||
controller: scrollController,
|
||||
thumbVisibility: true,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget footerWidget(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(40, 12, 40, 34),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).dialogBackgroundColor,
|
||||
boxShadow: [
|
||||
if (showScrollbar)
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: StandardSlideButton(
|
||||
onSlideComplete: onSlideComplete,
|
||||
buttonText: 'Swipe to send',
|
||||
currentTheme: currentTheme,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StandardTile extends StatelessWidget {
|
||||
const StandardTile({
|
||||
super.key,
|
||||
required this.itemTitle,
|
||||
required this.itemValue,
|
||||
required this.itemTitleTextStyle,
|
||||
this.itemSubTitle,
|
||||
required this.itemSubTitleTextStyle,
|
||||
});
|
||||
|
||||
final String itemTitle;
|
||||
final String itemValue;
|
||||
final TextStyle itemTitleTextStyle;
|
||||
final String? itemSubTitle;
|
||||
final TextStyle itemSubTitleTextStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Theme.of(context).extension<FilterTheme>()!.buttonColor),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(itemTitle, style: itemTitleTextStyle),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(itemValue, style: itemTitleTextStyle),
|
||||
itemSubTitle == null
|
||||
? Container()
|
||||
: Text(itemSubTitle!, style: itemSubTitleTextStyle),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressTile extends StatelessWidget {
|
||||
const AddressTile({
|
||||
super.key,
|
||||
required this.itemTitle,
|
||||
required this.currentTheme,
|
||||
required this.itemTitleTextStyle,
|
||||
required this.isBatchSending,
|
||||
required this.amount,
|
||||
required this.address,
|
||||
required this.itemSubTitleTextStyle,
|
||||
});
|
||||
|
||||
final String itemTitle;
|
||||
final ThemeBase currentTheme;
|
||||
final TextStyle itemTitleTextStyle;
|
||||
final bool isBatchSending;
|
||||
final String amount;
|
||||
final String address;
|
||||
final TextStyle itemSubTitleTextStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addressTextStyle = TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: currentTheme.type == ThemeType.bright
|
||||
? Theme.of(context).extension<BalancePageTheme>()!.labelTextColor
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Theme.of(context).extension<FilterTheme>()!.buttonColor,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(itemTitle, style: itemTitleTextStyle),
|
||||
if (isBatchSending) Text(amount, style: itemTitleTextStyle),
|
||||
],
|
||||
),
|
||||
buildSegmentedAddress(
|
||||
address: address,
|
||||
evenTextStyle: currentTheme.type == ThemeType.bright
|
||||
? itemSubTitleTextStyle
|
||||
: addressTextStyle,
|
||||
oddTextStyle: itemSubTitleTextStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSegmentedAddress({
|
||||
required String address,
|
||||
int chunkSize = 6,
|
||||
required TextStyle evenTextStyle,
|
||||
required TextStyle oddTextStyle,
|
||||
}) {
|
||||
final spans = <TextSpan>[];
|
||||
|
||||
int index = 0;
|
||||
for (int i = 0; i < address.length; i += chunkSize) {
|
||||
final chunk = address.substring(i, math.min(i + chunkSize, address.length));
|
||||
final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle;
|
||||
|
||||
spans.add(
|
||||
TextSpan(text: '$chunk ', style: style),
|
||||
);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(children: spans, style: evenTextStyle),
|
||||
overflow: TextOverflow.visible,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressExpansionTile extends StatelessWidget {
|
||||
const AddressExpansionTile({
|
||||
super.key,
|
||||
required this.contactType,
|
||||
required this.currentTheme,
|
||||
required this.name,
|
||||
required this.address,
|
||||
required this.amount,
|
||||
required this.isBatchSending,
|
||||
required this.itemTitleTextStyle,
|
||||
required this.itemSubTitleTextStyle,
|
||||
});
|
||||
|
||||
final String contactType;
|
||||
final ThemeBase currentTheme;
|
||||
final String name;
|
||||
final String address;
|
||||
final String amount;
|
||||
final bool isBatchSending;
|
||||
final TextStyle itemTitleTextStyle;
|
||||
final TextStyle itemSubTitleTextStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final addressTextStyle = TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: currentTheme.type == ThemeType.bright
|
||||
? Theme.of(context).extension<BalancePageTheme>()!.labelTextColor
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
);
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
color: Theme.of(context).extension<FilterTheme>()!.buttonColor,
|
||||
),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 14, vertical: isBatchSending ? 0 : 8),
|
||||
child: ExpansionTile(
|
||||
childrenPadding: EdgeInsets.zero,
|
||||
tilePadding: EdgeInsets.zero,
|
||||
dense: true,
|
||||
visualDensity: VisualDensity.compact,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(isBatchSending ? name : contactType, style: itemTitleTextStyle),
|
||||
Text(isBatchSending ? amount : name,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
)),
|
||||
],
|
||||
),
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
buildSegmentedAddress(
|
||||
address: address,
|
||||
evenTextStyle: currentTheme.type == ThemeType.bright
|
||||
? itemSubTitleTextStyle
|
||||
: addressTextStyle,
|
||||
oddTextStyle: itemSubTitleTextStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSegmentedAddress({
|
||||
required String address,
|
||||
int chunkSize = 6,
|
||||
required TextStyle evenTextStyle,
|
||||
required TextStyle oddTextStyle,
|
||||
}) {
|
||||
final spans = <TextSpan>[];
|
||||
|
||||
int index = 0;
|
||||
for (int i = 0; i < address.length; i += chunkSize) {
|
||||
final chunk = address.substring(i, math.min(i + chunkSize, address.length));
|
||||
final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle;
|
||||
|
||||
spans.add(
|
||||
TextSpan(text: '$chunk ', style: style),
|
||||
);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(children: spans, style: evenTextStyle),
|
||||
overflow: TextOverflow.visible,
|
||||
);
|
||||
}
|
||||
}
|
231
lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart
Normal file
231
lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart
Normal file
|
@ -0,0 +1,231 @@
|
|||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import 'base_bottom_sheet_widget.dart';
|
||||
|
||||
class LoadingBottomSheet extends BaseBottomSheet {
|
||||
LoadingBottomSheet({required String titleText, String? titleIconPath})
|
||||
: super(titleText: titleText, titleIconPath: titleIconPath);
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget footerWidget(BuildContext context) => const SizedBox(height: 94);
|
||||
}
|
||||
|
||||
class InfoBottomSheet extends BaseBottomSheet {
|
||||
final ThemeBase currentTheme;
|
||||
final String? contentImage;
|
||||
final Color? contentImageColor;
|
||||
final String? content;
|
||||
final bool isTwoAction;
|
||||
final bool showDontAskMeCheckbox;
|
||||
final Function(bool)? onCheckboxChanged;
|
||||
final String? actionButtonText;
|
||||
final VoidCallback? actionButton;
|
||||
final Key? actionButtonKey;
|
||||
final String? leftButtonText;
|
||||
final String? rightButtonText;
|
||||
final VoidCallback? actionLeftButton;
|
||||
final VoidCallback? actionRightButton;
|
||||
final Key? rightActionButtonKey;
|
||||
final Key? leftActionButtonKey;
|
||||
|
||||
InfoBottomSheet({
|
||||
required String titleText,
|
||||
String? titleIconPath,
|
||||
required this.currentTheme,
|
||||
this.contentImage,
|
||||
this.contentImageColor,
|
||||
this.content,
|
||||
this.isTwoAction = false,
|
||||
this.showDontAskMeCheckbox = false,
|
||||
this.onCheckboxChanged,
|
||||
this.actionButtonText,
|
||||
this.actionButton,
|
||||
this.actionButtonKey,
|
||||
this.leftButtonText,
|
||||
this.rightButtonText,
|
||||
this.actionLeftButton,
|
||||
this.actionRightButton,
|
||||
this.rightActionButtonKey,
|
||||
this.leftActionButtonKey,
|
||||
}) : super(titleText: titleText, titleIconPath: titleIconPath);
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: Column(
|
||||
children: [
|
||||
if (contentImage != null)
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: getImage(contentImage!, imageColor: contentImageColor),
|
||||
)
|
||||
else
|
||||
Container(),
|
||||
if (content != null)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Spacer(flex: 2),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: Text(
|
||||
content!,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(flex: 2),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (showDontAskMeCheckbox)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 34.0),
|
||||
child: Row(
|
||||
children: [
|
||||
SimpleCheckbox(onChanged: onCheckboxChanged),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Don’t ask me next time',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget footerWidget(BuildContext context) {
|
||||
if (isTwoAction) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 34),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(right: 8.0, top: 8.0),
|
||||
child: PrimaryButton(
|
||||
key: leftActionButtonKey,
|
||||
onPressed: actionLeftButton,
|
||||
text: leftButtonText ?? '',
|
||||
color: currentTheme.type == ThemeType.dark
|
||||
? Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor
|
||||
: Theme.of(context).cardColor,
|
||||
textColor: currentTheme.type == ThemeType.dark
|
||||
? Theme.of(context).extension<DashboardPageTheme>()!.textColor
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 8.0, top: 8.0),
|
||||
child: PrimaryButton(
|
||||
key: rightActionButtonKey,
|
||||
onPressed: actionRightButton,
|
||||
text: rightButtonText ?? '',
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: currentTheme.type == ThemeType.dark
|
||||
? Theme.of(context).extension<DashboardPageTheme>()!.textColor
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 34),
|
||||
child: LoadingPrimaryButton(
|
||||
key: actionButtonKey,
|
||||
onPressed: actionButton ?? () {},
|
||||
text: actionButtonText ?? '',
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget getImage(String imagePath, {Color? imageColor}) {
|
||||
final bool isSvg = imagePath.endsWith('.svg');
|
||||
if (isSvg) {
|
||||
return SvgPicture.asset(
|
||||
imagePath,
|
||||
colorFilter: imageColor != null ? ColorFilter.mode(imageColor, BlendMode.srcIn) : null,
|
||||
);
|
||||
} else {
|
||||
return Image.asset(imagePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleCheckbox extends StatefulWidget {
|
||||
SimpleCheckbox({this.onChanged});
|
||||
|
||||
final Function(bool)? onChanged;
|
||||
|
||||
@override
|
||||
State<SimpleCheckbox> createState() => _SimpleCheckboxState();
|
||||
}
|
||||
|
||||
class _SimpleCheckboxState extends State<SimpleCheckbox> {
|
||||
bool initialValue = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
child: Checkbox(
|
||||
value: initialValue,
|
||||
onChanged: (value) => setState(() {
|
||||
initialValue = value!;
|
||||
widget.onChanged?.call(value);
|
||||
}),
|
||||
checkColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
activeColor: Colors.transparent,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: WidgetStateBorderSide.resolveWith((states) => BorderSide(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, width: 1.0)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
90
lib/src/widgets/standard_slide_button_widget.dart
Normal file
90
lib/src/widgets/standard_slide_button_widget.dart
Normal file
|
@ -0,0 +1,90 @@
|
|||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StandardSlideButton extends StatefulWidget {
|
||||
const StandardSlideButton({
|
||||
Key? key,
|
||||
required this.onSlideComplete,
|
||||
this.buttonText = '',
|
||||
this.height = 48.0,
|
||||
required this.currentTheme,
|
||||
}) : super(key: key);
|
||||
|
||||
final VoidCallback onSlideComplete;
|
||||
final String buttonText;
|
||||
final double height;
|
||||
final ThemeBase currentTheme;
|
||||
|
||||
@override
|
||||
_StandardSlideButtonState createState() => _StandardSlideButtonState();
|
||||
}
|
||||
|
||||
class _StandardSlideButtonState extends State<StandardSlideButton> {
|
||||
double _dragPosition = 0.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
final double maxWidth = constraints.maxWidth;
|
||||
const double sideMargin = 4.0;
|
||||
final double effectiveMaxWidth = maxWidth - 2 * sideMargin;
|
||||
const double sliderWidth = 42.0;
|
||||
|
||||
return Container(
|
||||
height: widget.height,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: widget.currentTheme.type == ThemeType.light || widget.currentTheme.type == ThemeType.bright
|
||||
? Theme.of(context).disabledColor
|
||||
: widget.currentTheme.type == ThemeType.oled ? Colors.black : Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor),
|
||||
child: Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(widget.buttonText,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor))),
|
||||
Positioned(
|
||||
left: sideMargin + _dragPosition,
|
||||
child: GestureDetector(
|
||||
onHorizontalDragUpdate: (details) {
|
||||
setState(() {
|
||||
_dragPosition += details.delta.dx;
|
||||
if (_dragPosition < 0) _dragPosition = 0;
|
||||
if (_dragPosition > effectiveMaxWidth - sliderWidth) {
|
||||
_dragPosition = effectiveMaxWidth - sliderWidth;
|
||||
}
|
||||
});
|
||||
},
|
||||
onHorizontalDragEnd: (details) {
|
||||
if (_dragPosition >= effectiveMaxWidth - sliderWidth - 10) {
|
||||
widget.onSlideComplete();
|
||||
} else {
|
||||
setState(() => _dragPosition = 0);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: sliderWidth,
|
||||
height: widget.height - 8,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: widget.currentTheme.type == ThemeType.bright ? Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor : Theme.of(context).extension<FilterTheme>()!.buttonColor,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(Icons.arrow_forward,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -106,6 +106,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
|
||||
bool get showAddressBookPopup => _settingsStore.showAddressBookPopupEnabled;
|
||||
|
||||
@action
|
||||
void setShowAddressBookPopup(bool value) {
|
||||
_settingsStore.showAddressBookPopupEnabled = value;
|
||||
}
|
||||
|
||||
@action
|
||||
void addOutput() {
|
||||
outputs
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue