mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-11-14 09:14:05 +01:00
CW-942-New-Swap-UI-Flow (#2036)
* feat: Implement New Swap UI including fee items and coin control and extract common fee logic to a feeViewModel, use that in SendViewModel and ExchangeViewModel * feat: Implement New Swap UI with fee items and coin control and extract common fee logic to a feeViewModel for use in SendViewModel and ExchangeViewModel * Update colors * Show warning if user choose a below default transaction priority * fix: Add warning when user select less than default transaction priority and add condition to show extraId * Complete Swap UI flow * Handle edgecases for XRP * Switch swap page button texts * Handle edgecases for XLM * Switch prompt text and handle edgecases --------- Co-authored-by: tuxpizza <tuxsudo@tux.pizza> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
tuxpizza
Omar Hatem
parent
0f301a71a2
commit
9f3078740e
@@ -248,7 +248,7 @@ class ExchangePage extends BasePage {
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
key: ValueKey('exchange_page_exchange_button_key'),
|
||||
text: exchangeViewModel.isAvailableInSelected ? S.of(context).exchange : S.of(context).change_selected_exchanges,
|
||||
text: exchangeViewModel.isAvailableInSelected ? S.of(context).swap : S.of(context).change_selected_exchanges,
|
||||
onPressed: exchangeViewModel.isAvailableInSelected ? () {
|
||||
FocusScope.of(context).unfocus();
|
||||
|
||||
@@ -377,7 +377,7 @@ class ExchangePage extends BasePage {
|
||||
return;
|
||||
}
|
||||
|
||||
if (exchangeViewModel.isLowFee) {
|
||||
if (exchangeViewModel.feesViewModel.isLowFee) {
|
||||
_showFeeAlert(context);
|
||||
}
|
||||
|
||||
@@ -640,7 +640,7 @@ class ExchangePage extends BasePage {
|
||||
}) ??
|
||||
false;
|
||||
if (confirmed) {
|
||||
exchangeViewModel.setDefaultTransactionPriority();
|
||||
exchangeViewModel.feesViewModel .setDefaultTransactionPriority();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,10 +35,11 @@ class PresentProviderPicker extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(S.of(context).exchange,
|
||||
Text(S.of(context).swap,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
color: Colors.white)),
|
||||
Observer(
|
||||
builder: (_) => Text(
|
||||
|
||||
@@ -21,7 +21,7 @@ class ExchangeConfirmPage extends BasePage {
|
||||
final Trade trade;
|
||||
|
||||
@override
|
||||
String get title => S.current.copy_id;
|
||||
String get title => S.current.swap;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
@@ -40,7 +40,7 @@ class ExchangeConfirmPage extends BasePage {
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
|
||||
),
|
||||
)),
|
||||
Container(
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/widgets/exchange_trade_card_item_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/core/execution_state.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';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
|
||||
class ExchangeTradeExternalSendPage extends BasePage {
|
||||
ExchangeTradeExternalSendPage({required this.exchangeTradeViewModel});
|
||||
|
||||
final ExchangeTradeViewModel exchangeTradeViewModel;
|
||||
@override
|
||||
String get title => S.current.swap;
|
||||
|
||||
@override
|
||||
bool get gradientBackground => true;
|
||||
|
||||
@override
|
||||
bool get gradientAll => true;
|
||||
|
||||
@override
|
||||
bool get resizeToAvoidBottomInset => false;
|
||||
|
||||
@override
|
||||
bool get extendBodyBehindAppBar => true;
|
||||
|
||||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
final fetchingLabel = S.current.fetching;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final copyImage = Image.asset(
|
||||
'assets/images/copy_content.png',
|
||||
height: 16,
|
||||
width: 16,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.estimatedFeeColor,
|
||||
);
|
||||
return Container(
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(top: 36, bottom: 24),
|
||||
content: Observer(
|
||||
builder: (_) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).extension<SendPageTheme>()!.firstGradientColor,
|
||||
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(24, 110, 24, 32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Spacer(flex: 3),
|
||||
Flexible(
|
||||
flex: 6,
|
||||
child: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 3,
|
||||
color: Theme.of(context)
|
||||
.extension<ExchangePageTheme>()!
|
||||
.qrCodeColor,
|
||||
),
|
||||
),
|
||||
child: QrImage(
|
||||
data:
|
||||
exchangeTradeViewModel.trade.inputAddress ?? fetchingLabel,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Spacer(flex: 3)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
...exchangeTradeViewModel.items
|
||||
.where((item) => item.isExternalSendDetail)
|
||||
.map(
|
||||
(item) => TradeItemRowWidget(
|
||||
currentTheme: currentTheme,
|
||||
title: item.title,
|
||||
value: item.data,
|
||||
isCopied: true,
|
||||
copyImage: copyImage,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomSection: Observer(
|
||||
builder: (_) {
|
||||
final trade = exchangeTradeViewModel.trade;
|
||||
final sendingState = exchangeTradeViewModel.sendViewModel.state;
|
||||
|
||||
return exchangeTradeViewModel.isSendable && !(sendingState is TransactionCommitted)
|
||||
? LoadingPrimaryButton(
|
||||
key: ValueKey('exchange_trade_external_send_page_continue_button_key'),
|
||||
isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty,
|
||||
isLoading: sendingState is IsExecutingState,
|
||||
onPressed: () {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
},
|
||||
text: S.current.continue_text,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
)
|
||||
: Offstage();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class ExchangeTradeItem {
|
||||
ExchangeTradeItem({
|
||||
required this.title,
|
||||
required this.data,
|
||||
required this.isCopied,
|
||||
required this.isReceiveDetail,
|
||||
required this.isExternalSendDetail,
|
||||
});
|
||||
|
||||
String title;
|
||||
String data;
|
||||
bool isCopied;
|
||||
bool isReceiveDetail;
|
||||
bool isExternalSendDetail;
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
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/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/utils/request_review_handler.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.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';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/widgets/timer_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
@@ -30,8 +30,7 @@ void showInformation(
|
||||
final walletName = exchangeTradeViewModel.wallet.name;
|
||||
|
||||
final information = exchangeTradeViewModel.isSendable
|
||||
? S.current.exchange_result_confirm(
|
||||
trade.amount, trade.from.toString(), walletName) +
|
||||
? S.current.exchange_trade_result_confirm(trade.amount, trade.from.toString(), walletName) +
|
||||
exchangeTradeViewModel.extraInfo
|
||||
: S.current.exchange_result_description(
|
||||
trade.amount, trade.from.toString()) +
|
||||
@@ -50,7 +49,22 @@ class ExchangeTradePage extends BasePage {
|
||||
final ExchangeTradeViewModel exchangeTradeViewModel;
|
||||
|
||||
@override
|
||||
String get title => S.current.exchange;
|
||||
String get title => S.current.swap;
|
||||
|
||||
@override
|
||||
bool get gradientBackground => true;
|
||||
|
||||
@override
|
||||
bool get gradientAll => true;
|
||||
|
||||
@override
|
||||
bool get resizeToAvoidBottomInset => false;
|
||||
|
||||
@override
|
||||
bool get extendBodyBehindAppBar => true;
|
||||
|
||||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
@@ -74,14 +88,20 @@ class ExchangeTradePage extends BasePage {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) =>
|
||||
ExchangeTradeForm(exchangeTradeViewModel);
|
||||
Widget body(BuildContext context) => ExchangeTradeForm(
|
||||
exchangeTradeViewModel,
|
||||
currentTheme,
|
||||
);
|
||||
}
|
||||
|
||||
class ExchangeTradeForm extends StatefulWidget {
|
||||
ExchangeTradeForm(this.exchangeTradeViewModel);
|
||||
ExchangeTradeForm(
|
||||
this.exchangeTradeViewModel,
|
||||
this.currentTheme,
|
||||
);
|
||||
|
||||
final ExchangeTradeViewModel exchangeTradeViewModel;
|
||||
final ThemeBase currentTheme;
|
||||
|
||||
@override
|
||||
ExchangeTradeState createState() => ExchangeTradeState();
|
||||
@@ -115,119 +135,77 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final copyImage = Image.asset('assets/images/copy_content.png',
|
||||
height: 16,
|
||||
width: 16,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor);
|
||||
|
||||
_setEffects();
|
||||
|
||||
return Container(
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(top: 10, bottom: 16),
|
||||
content: Observer(builder: (_) {
|
||||
final trade = widget.exchangeTradeViewModel.trade;
|
||||
contentPadding: EdgeInsets.only(top: 10, bottom: 16),
|
||||
content: Observer(builder: (_) {
|
||||
final trade = widget.exchangeTradeViewModel.trade;
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
trade.expiredAt != null
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).offer_expires_in,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
|
||||
),
|
||||
if (trade.expiredAt != null)
|
||||
TimerWidget(trade.expiredAt!,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)
|
||||
])
|
||||
: Offstage(),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 32),
|
||||
child: Row(children: <Widget>[
|
||||
Spacer(flex: 3),
|
||||
Flexible(
|
||||
flex: 4,
|
||||
child: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 3,
|
||||
color: Theme.of(context).extension<ExchangePageTheme>()!.qrCodeColor
|
||||
)
|
||||
),
|
||||
child: QrImage(data: trade.inputAddress ?? fetchingLabel),
|
||||
)))),
|
||||
Spacer(flex: 3)
|
||||
]),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 16),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: widget.exchangeTradeViewModel.items.length,
|
||||
separatorBuilder: (context, index) => Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).extension<ExchangePageTheme>()!.dividerCodeColor,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final item = widget.exchangeTradeViewModel.items[index];
|
||||
final value = item.data;
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
trade.expiredAt != null
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).offer_expires_in,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.extension<TransactionTradeTheme>()!
|
||||
.detailsTitlesColor),
|
||||
),
|
||||
if (trade.expiredAt != null)
|
||||
TimerWidget(trade.expiredAt!,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)
|
||||
])
|
||||
: Offstage(),
|
||||
_ExchangeTradeItemsCardSection(
|
||||
viewModel: widget.exchangeTradeViewModel,
|
||||
currentTheme: widget.currentTheme,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24),
|
||||
bottomSection: Column(
|
||||
children: [
|
||||
PrimaryButton(
|
||||
key: ValueKey('exchange_trade_page_send_from_external_button_key'),
|
||||
text: S.current.send_from_external_wallet,
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pushNamed(Routes.exchangeTradeExternalSendPage);
|
||||
},
|
||||
color: Theme.of(context).cardColor,
|
||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
final trade = widget.exchangeTradeViewModel.trade;
|
||||
final sendingState = widget.exchangeTradeViewModel.sendViewModel.state;
|
||||
|
||||
final content = ListRow(
|
||||
title: item.title,
|
||||
value: value,
|
||||
valueFontSize: 14,
|
||||
image: item.isCopied ? copyImage : null,
|
||||
);
|
||||
|
||||
return item.isCopied
|
||||
? Builder(
|
||||
builder: (context) => GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(text: value));
|
||||
showBar<void>(context,
|
||||
S.of(context).copied_to_clipboard);
|
||||
},
|
||||
child: content,
|
||||
))
|
||||
: content;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24),
|
||||
bottomSection: Observer(builder: (_) {
|
||||
final trade = widget.exchangeTradeViewModel.trade;
|
||||
final sendingState =
|
||||
widget.exchangeTradeViewModel.sendViewModel.state;
|
||||
|
||||
return widget.exchangeTradeViewModel.isSendable &&
|
||||
!(sendingState is TransactionCommitted)
|
||||
? LoadingPrimaryButton(
|
||||
key: ValueKey('exchange_trade_page_confirm_sending_button_key'),
|
||||
isDisabled: trade.inputAddress == null ||
|
||||
trade.inputAddress!.isEmpty,
|
||||
isLoading: sendingState is IsExecutingState,
|
||||
onPressed: () =>
|
||||
widget.exchangeTradeViewModel.confirmSending(),
|
||||
text: S.of(context).confirm,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white)
|
||||
: Offstage();
|
||||
})),
|
||||
return widget.exchangeTradeViewModel.isSendable &&
|
||||
!(sendingState is TransactionCommitted)
|
||||
? LoadingPrimaryButton(
|
||||
key: ValueKey('exchange_trade_page_send_from_cake_button_key'),
|
||||
isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty,
|
||||
isLoading: sendingState is IsExecutingState,
|
||||
onPressed: () => widget.exchangeTradeViewModel.confirmSending(),
|
||||
text:S.current.send_from_cake_wallet,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
)
|
||||
: Offstage();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -412,3 +390,40 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ExchangeTradeItemsCardSection extends StatelessWidget {
|
||||
const _ExchangeTradeItemsCardSection({
|
||||
required this.viewModel,
|
||||
required this.currentTheme,
|
||||
});
|
||||
|
||||
final ExchangeTradeViewModel viewModel;
|
||||
final ThemeBase currentTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final firstExchangeCard = ExchangeTradeCardItemWidget(
|
||||
currentTheme: currentTheme,
|
||||
isReceiveDetailsCard: true,
|
||||
exchangeTradeViewModel: viewModel,
|
||||
);
|
||||
|
||||
final secondExchangeCard = ExchangeTradeCardItemWidget(
|
||||
currentTheme: currentTheme,
|
||||
isReceiveDetailsCard: false,
|
||||
exchangeTradeViewModel: viewModel,
|
||||
);
|
||||
|
||||
if (responsiveLayoutUtil.shouldRenderMobileUI) {
|
||||
return MobileExchangeCardsSection(
|
||||
firstExchangeCard: firstExchangeCard,
|
||||
secondExchangeCard: secondExchangeCard,
|
||||
);
|
||||
}
|
||||
|
||||
return DesktopExchangeCardsSection(
|
||||
firstExchangeCard: firstExchangeCard,
|
||||
secondExchangeCard: secondExchangeCard,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.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/fees_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import '../../../../themes/extensions/cake_text_theme.dart';
|
||||
import '../../../../themes/extensions/transaction_trade_theme.dart';
|
||||
import '../../../../themes/theme_base.dart';
|
||||
|
||||
class ExchangeTradeCardItemWidget extends StatelessWidget {
|
||||
ExchangeTradeCardItemWidget({
|
||||
required this.isReceiveDetailsCard,
|
||||
required this.exchangeTradeViewModel,
|
||||
required this.currentTheme,
|
||||
Key? key,
|
||||
}) : feesViewModel = exchangeTradeViewModel.feesViewModel,
|
||||
output = exchangeTradeViewModel.output;
|
||||
|
||||
final Output output;
|
||||
final bool isReceiveDetailsCard;
|
||||
final FeesViewModel feesViewModel;
|
||||
final ExchangeTradeViewModel exchangeTradeViewModel;
|
||||
final ThemeBase currentTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final copyImage = Image.asset(
|
||||
'assets/images/copy_content.png',
|
||||
height: 16,
|
||||
width: 16,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.estimatedFeeColor,
|
||||
);
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
SizedBox(height: 10),
|
||||
...exchangeTradeViewModel.items
|
||||
.where((item) => item.isReceiveDetail == isReceiveDetailsCard)
|
||||
.map(
|
||||
(item) => TradeItemRowWidget(
|
||||
currentTheme: currentTheme,
|
||||
title: item.title,
|
||||
value: item.data,
|
||||
isCopied: item.isCopied,
|
||||
copyImage: copyImage,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
if (!isReceiveDetailsCard && exchangeTradeViewModel.isSendable) ...[
|
||||
if (feesViewModel.hasFees)
|
||||
FeeSelectionWidget(
|
||||
feesViewModel: feesViewModel,
|
||||
output: output,
|
||||
onTap: () => pickTransactionPriority(context),
|
||||
),
|
||||
if (exchangeTradeViewModel.sendViewModel.hasCoinControl)
|
||||
CoinControlWidget(
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.unspentCoinsList,
|
||||
arguments: exchangeTradeViewModel.sendViewModel.coinTypeToSpendFrom,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> pickTransactionPriority(BuildContext context) async {
|
||||
final items = priorityForWalletType(feesViewModel.walletType);
|
||||
final selectedItem = items.indexOf(feesViewModel.transactionPriority);
|
||||
final customItemIndex = feesViewModel.getCustomPriorityIndex(items);
|
||||
final isBitcoinWallet = feesViewModel.walletType == WalletType.bitcoin;
|
||||
final maxCustomFeeRate = feesViewModel.maxCustomFeeRate?.toDouble();
|
||||
double? customFeeRate = isBitcoinWallet ? feesViewModel.customBitcoinFeeRate.toDouble() : null;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
int selectedIdx = selectedItem;
|
||||
return StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Picker(
|
||||
items: items,
|
||||
displayItem: (TransactionPriority priority) =>
|
||||
feesViewModel.displayFeeRate(priority, customFeeRate?.round()),
|
||||
selectedAtIndex: selectedIdx,
|
||||
customItemIndex: customItemIndex,
|
||||
maxValue: maxCustomFeeRate,
|
||||
title: S.of(context).please_select,
|
||||
headerEnabled: !isBitcoinWallet,
|
||||
closeOnItemSelected: !isBitcoinWallet,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
sliderValue: customFeeRate,
|
||||
onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue),
|
||||
onItemSelected: (TransactionPriority priority) {
|
||||
feesViewModel.setTransactionPriority(priority);
|
||||
setState(() => selectedIdx = items.indexOf(priority));
|
||||
|
||||
if (feesViewModel.isLowFee) {
|
||||
_showFeeAlert(context);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (isBitcoinWallet) {
|
||||
feesViewModel.customBitcoinFeeRate = customFeeRate!.round();
|
||||
if (feesViewModel.showAlertForCustomFeeRate()) {
|
||||
_showFeeAlert(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showFeeAlert(BuildContext context) async {
|
||||
final confirmed = await showPopUp<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).low_fee,
|
||||
alertContent: S.of(context).low_fee_alert,
|
||||
leftButtonText: S.of(context).ignor,
|
||||
rightButtonText: S.of(context).use_suggested,
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
|
||||
actionRightButton: () => Navigator.of(dialogContext).pop(true));
|
||||
}) ??
|
||||
false;
|
||||
if (confirmed) {
|
||||
feesViewModel.setDefaultTransactionPriority();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TradeItemRowWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final String value;
|
||||
final bool isCopied;
|
||||
final Image copyImage;
|
||||
final ThemeBase currentTheme;
|
||||
|
||||
const TradeItemRowWidget({
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.isCopied,
|
||||
required this.copyImage,
|
||||
required this.currentTheme,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
final hintTextColor = currentTheme == ThemeType.bright
|
||||
? Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor
|
||||
: Colors.white.withAlpha(175);
|
||||
|
||||
final mainTextColor = currentTheme == ThemeType.bright
|
||||
? Theme.of(context).extension<CakeTextTheme>()!.titleColor
|
||||
: Colors.white;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (!isCopied) return;
|
||||
Clipboard.setData(ClipboardData(text: value));
|
||||
showBar<void>(context, S.of(context).transaction_details_copied(title));
|
||||
},
|
||||
child: ListRow(
|
||||
padding: EdgeInsets.zero,
|
||||
title: title,
|
||||
value: value,
|
||||
image: isCopied ? copyImage : null,
|
||||
color: Colors.transparent,
|
||||
hintTextColor: hintTextColor,
|
||||
mainTextColor: mainTextColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FeeSelectionWidget extends StatelessWidget {
|
||||
final FeesViewModel feesViewModel;
|
||||
final Output output;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const FeeSelectionWidget({
|
||||
required this.feesViewModel,
|
||||
required this.output,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) => GestureDetector(
|
||||
key: ValueKey('exchange_trade_card_item_widget_select_fee_priority_button_key'),
|
||||
onTap: feesViewModel.hasFeesPriority ? onTap : () {},
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).send_estimated_fee,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${output.estimatedFee} ${feesViewModel.currency}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
if (!feesViewModel.isFiatDisabled)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
'${output.estimatedFeeFiatAmount} ${feesViewModel.fiat.title}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 2, left: 5),
|
||||
child: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CoinControlWidget extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
|
||||
const CoinControlWidget({required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
key: ValueKey('exchange_trade_card_item_widget_unspent_coin_button_key'),
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).coin_control,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -308,11 +308,11 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
|
||||
),
|
||||
),
|
||||
if (sendViewModel.hasFees)
|
||||
if (sendViewModel.feesViewModel.hasFees)
|
||||
Observer(
|
||||
builder: (_) => GestureDetector(
|
||||
key: ValueKey('send_page_select_fee_priority_button_key'),
|
||||
onTap: sendViewModel.hasFeesPriority
|
||||
onTap: sendViewModel.feesViewModel.hasFeesPriority
|
||||
? () => pickTransactionPriority(context)
|
||||
: () {},
|
||||
child: Container(
|
||||
@@ -570,11 +570,11 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||
|
||||
Future<void> pickTransactionPriority(BuildContext context) async {
|
||||
final items = priorityForWalletType(sendViewModel.walletType);
|
||||
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
||||
final customItemIndex = sendViewModel.getCustomPriorityIndex(items);
|
||||
final selectedItem = items.indexOf(sendViewModel.feesViewModel.transactionPriority);
|
||||
final customItemIndex = sendViewModel.feesViewModel.getCustomPriorityIndex(items);
|
||||
final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin;
|
||||
final maxCustomFeeRate = sendViewModel.maxCustomFeeRate?.toDouble();
|
||||
double? customFeeRate = isBitcoinWallet ? sendViewModel.customBitcoinFeeRate.toDouble() : null;
|
||||
final maxCustomFeeRate = sendViewModel.feesViewModel.maxCustomFeeRate?.toDouble();
|
||||
double? customFeeRate = isBitcoinWallet ? sendViewModel.feesViewModel.customBitcoinFeeRate.toDouble() : null;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
@@ -585,7 +585,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||
return Picker(
|
||||
items: items,
|
||||
displayItem: (TransactionPriority priority) =>
|
||||
sendViewModel.displayFeeRate(priority, customFeeRate?.round()),
|
||||
sendViewModel.feesViewModel.displayFeeRate(priority, customFeeRate?.round()),
|
||||
selectedAtIndex: selectedIdx,
|
||||
customItemIndex: customItemIndex,
|
||||
maxValue: maxCustomFeeRate,
|
||||
@@ -596,7 +596,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||
sliderValue: customFeeRate,
|
||||
onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue),
|
||||
onItemSelected: (TransactionPriority priority) {
|
||||
sendViewModel.setTransactionPriority(priority);
|
||||
sendViewModel.feesViewModel.setTransactionPriority(priority);
|
||||
setState(() => selectedIdx = items.indexOf(priority));
|
||||
},
|
||||
);
|
||||
@@ -604,7 +604,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||
);
|
||||
},
|
||||
);
|
||||
if (isBitcoinWallet) sendViewModel.customBitcoinFeeRate = customFeeRate!.round();
|
||||
if (isBitcoinWallet) sendViewModel.feesViewModel.customBitcoinFeeRate = customFeeRate!.round();
|
||||
}
|
||||
|
||||
void _presentPicker(BuildContext context) {
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
|
||||
class ListRow extends StatelessWidget {
|
||||
ListRow(
|
||||
{required this.title,
|
||||
required this.value,
|
||||
this.titleFontSize = 14,
|
||||
this.valueFontSize = 16,
|
||||
this.image});
|
||||
ListRow({
|
||||
required this.title,
|
||||
required this.value,
|
||||
this.titleFontSize = 14,
|
||||
this.valueFontSize = 16,
|
||||
this.image,
|
||||
this.padding,
|
||||
this.color,
|
||||
this.hintTextColor,
|
||||
this.mainTextColor
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String value;
|
||||
final double titleFontSize;
|
||||
final double valueFontSize;
|
||||
final Image? image;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final Color? color;
|
||||
final Color? hintTextColor;
|
||||
final Color? mainTextColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: color ?? Theme.of(context).colorScheme.background,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
|
||||
padding: padding ?? const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
@@ -32,7 +39,7 @@ class ListRow extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: titleFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
|
||||
color: hintTextColor ?? Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
|
||||
textAlign: TextAlign.left),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
@@ -46,7 +53,7 @@ class ListRow extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: valueFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
color: mainTextColor ?? Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
),
|
||||
image != null
|
||||
? Padding(
|
||||
|
||||
Reference in New Issue
Block a user