mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-20 06:08:34 +02:00
Add rbf (#1323)
* Add initial checkbox for RBF * minor progress * minor progress * Minor progress * Debugging RBF * Minor fix * Fix RBF transaction inputs (now it's working) * New versions Fix issues with Monero.com * Add sending for Solana tokens exchanges * Add default keyword for P2WPKH [skip ci] * chore: Switch solana commitment to confirmed to reduced blockhash expiration (#1313) * Modify test workflow to send arm64-v8a build only * Fix workflow build path * Remove unnecessary reverse of txId * Update Replace by fee with the new bitcoin base implementation * btc custom fee priority * add feeRate to btc credential * UI fixes * add check if the change covers the fee * Update settings_store.dart * add check confirmation for rbf * add a check to see if the change is sufficient for the new fee * addressing PR comments * update localization files * addressing PR comments * minor fixes * Update transaction_details_view_model.dart * Minor Fix for building Monero.com [skip ci] * update localization files * add bump fee page * update localisation files * Update cw_bitcoin.dart * fix merge conflict * fix UI issues * Fix Conflicts, Fix RBF flow, some enhancements * prevent default custom fee rate * hide outputs and inputs items * minor fix [skip ci] * addressing PR comments * remove rbf checkbox * default picker value * minor ui change [skip ci] * min fee rate [skip ci] * Minor fix and some minor enhancements --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com> Co-authored-by: Adegoke David <64401859+Blazebrain@users.noreply.github.com>
This commit is contained in:
co-authored by
OmarHatem
Adegoke David
parent
071643b662
commit
291aa710c6
@@ -100,7 +100,10 @@ class SendPage extends BasePage {
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
double _sendCardHeight(BuildContext context) {
|
||||
final double initialHeight = sendViewModel.hasCoinControl ? 500 : 465;
|
||||
double initialHeight = 450;
|
||||
if (sendViewModel.hasCoinControl) {
|
||||
initialHeight += 35;
|
||||
}
|
||||
|
||||
if (!responsiveLayoutUtil.shouldRenderMobileUI) {
|
||||
return initialHeight - 66;
|
||||
@@ -190,7 +193,7 @@ class SendPage extends BasePage {
|
||||
},
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10),
|
||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 10),
|
||||
child: Container(
|
||||
height: 10,
|
||||
child: Observer(
|
||||
@@ -456,7 +459,7 @@ class SendPage extends BasePage {
|
||||
? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : '';
|
||||
|
||||
final newContactMessage = newContactAddress != null
|
||||
? '\n${S.of(context).add_contact_to_address_book}' : '';
|
||||
? '\n${S.of(_dialogContext).add_contact_to_address_book}' : '';
|
||||
|
||||
final alertContent =
|
||||
"$successMessage$waitMessage$newContactMessage";
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.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:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
@@ -456,7 +457,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||
Observer(
|
||||
builder: (_) => GestureDetector(
|
||||
onTap: sendViewModel.hasFeesPriority
|
||||
? () => _setTransactionPriority(context)
|
||||
? () => pickTransactionPriority(context)
|
||||
: () {},
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
@@ -669,22 +670,41 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||
_effectsInstalled = true;
|
||||
}
|
||||
|
||||
Future<void> _setTransactionPriority(BuildContext context) async {
|
||||
Future<void> pickTransactionPriority(BuildContext context) async {
|
||||
final items = priorityForWalletType(sendViewModel.walletType);
|
||||
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
||||
final customItemIndex = sendViewModel.getCustomPriorityIndex(items);
|
||||
final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin;
|
||||
double? customFeeRate = isBitcoinWallet ? sendViewModel.customBitcoinFeeRate.toDouble() : null;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => Picker(
|
||||
items: items,
|
||||
displayItem: sendViewModel.displayFeeRate,
|
||||
selectedAtIndex: selectedItem,
|
||||
title: S.of(context).please_select,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
onItemSelected: (TransactionPriority priority) =>
|
||||
sendViewModel.setTransactionPriority(priority),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
int selectedIdx = selectedItem;
|
||||
return StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Picker(
|
||||
items: items,
|
||||
displayItem: (TransactionPriority priority) =>
|
||||
sendViewModel.displayFeeRate(priority, customFeeRate?.round()),
|
||||
selectedAtIndex: selectedIdx,
|
||||
customItemIndex: customItemIndex,
|
||||
title: S.of(context).please_select,
|
||||
headerEnabled: !isBitcoinWallet,
|
||||
closeOnItemSelected: !isBitcoinWallet,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
sliderValue: customFeeRate,
|
||||
onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue),
|
||||
onItemSelected: (TransactionPriority priority) {
|
||||
sendViewModel.setTransactionPriority(priority);
|
||||
setState(() => selectedIdx = items.indexOf(priority));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (isBitcoinWallet) sendViewModel.customBitcoinFeeRate = customFeeRate!.round();
|
||||
}
|
||||
|
||||
void _presentPicker(BuildContext context) {
|
||||
|
||||
@@ -2,11 +2,12 @@ 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/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/setting_priority_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
@@ -27,13 +28,23 @@ class OtherSettingsPage extends BasePage {
|
||||
child: Column(
|
||||
children: [
|
||||
if (_otherSettingsViewModel.displayTransactionPriority)
|
||||
SettingsPickerCell(
|
||||
title: S.current.settings_fee_priority,
|
||||
items: priorityForWalletType(_otherSettingsViewModel.walletType),
|
||||
displayItem: _otherSettingsViewModel.getDisplayPriority,
|
||||
selectedItem: _otherSettingsViewModel.transactionPriority,
|
||||
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
|
||||
),
|
||||
_otherSettingsViewModel.walletType == WalletType.bitcoin ?
|
||||
SettingsPriorityPickerCell(
|
||||
title: S.current.settings_fee_priority,
|
||||
items: priorityForWalletType(_otherSettingsViewModel.walletType),
|
||||
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
|
||||
selectedItem: _otherSettingsViewModel.transactionPriority,
|
||||
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
|
||||
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
|
||||
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
|
||||
) :
|
||||
SettingsPickerCell(
|
||||
title: S.current.settings_fee_priority,
|
||||
items: priorityForWalletType(_otherSettingsViewModel.walletType),
|
||||
displayItem: _otherSettingsViewModel.getDisplayPriority,
|
||||
selectedItem: _otherSettingsViewModel.transactionPriority,
|
||||
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
|
||||
),
|
||||
if (_otherSettingsViewModel.changeRepresentativeEnabled)
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.change_rep,
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
|
||||
class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
|
||||
SettingsPriorityPickerCell(
|
||||
{required String title,
|
||||
required this.selectedItem,
|
||||
required this.items,
|
||||
this.displayItem,
|
||||
this.images,
|
||||
this.searchHintText,
|
||||
this.isGridView = false,
|
||||
this.matchingCriteria,
|
||||
this.customValue,
|
||||
this.customItemIndex,
|
||||
this.onItemSelected})
|
||||
: super(
|
||||
title: title,
|
||||
isSelected: false,
|
||||
onTap: (BuildContext context) async {
|
||||
var selectedAtIndex = items.indexOf(selectedItem);
|
||||
double sliderValue = customValue ?? 0.0;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Picker(
|
||||
items: items,
|
||||
displayItem: (ItemType item) => displayItem!(item, sliderValue.round()),
|
||||
selectedAtIndex: selectedAtIndex,
|
||||
customItemIndex: customItemIndex,
|
||||
headerEnabled: false,
|
||||
closeOnItemSelected: false,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
sliderValue: sliderValue,
|
||||
onSliderChanged: (double newValue) =>
|
||||
setState(() => sliderValue = newValue),
|
||||
onItemSelected: (ItemType priority) {
|
||||
setState(() => selectedAtIndex = items.indexOf(priority));
|
||||
onItemSelected?.call(priority, sliderValue);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
onItemSelected?.call(items[selectedAtIndex], sliderValue);
|
||||
});
|
||||
|
||||
final ItemType selectedItem;
|
||||
final List<ItemType> items;
|
||||
final void Function(ItemType item, double customValue)? onItemSelected;
|
||||
final String Function(ItemType item, int value)? displayItem;
|
||||
final List<Image>? images;
|
||||
final String? searchHintText;
|
||||
final bool isGridView;
|
||||
final bool Function(ItemType, String)? matchingCriteria;
|
||||
double? customValue;
|
||||
int? customItemIndex;
|
||||
|
||||
@override
|
||||
Widget buildTrailing(BuildContext context) {
|
||||
return Text(
|
||||
displayItem?.call(selectedItem,customValue?.round() ?? 0) ?? selectedItem.toString(),
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
|
||||
class StandardPickerListItem<T> extends TransactionDetailsListItem {
|
||||
StandardPickerListItem(
|
||||
{required String title,
|
||||
required String value,
|
||||
required this.items,
|
||||
required this.displayItem,
|
||||
required this.onSliderChanged,
|
||||
required this.onItemSelected,
|
||||
required this.selectedIdx,
|
||||
required this.customItemIndex,
|
||||
required this.customValue})
|
||||
: super(title: title, value: value);
|
||||
|
||||
final List<T> items;
|
||||
final String Function(T item, double sliderValue) displayItem;
|
||||
final Function(double) onSliderChanged;
|
||||
final Function(T) onItemSelected;
|
||||
final int selectedIdx;
|
||||
final int customItemIndex;
|
||||
double customValue;
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
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';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
|
||||
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/list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_expandable_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_picker_list.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class RBFDetailsPage extends BasePage {
|
||||
RBFDetailsPage({required this.transactionDetailsViewModel});
|
||||
|
||||
@override
|
||||
String get title => S.current.bump_fee;
|
||||
|
||||
final TransactionDetailsViewModel transactionDetailsViewModel;
|
||||
|
||||
bool _effectsInstalled = false;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
_setEffects(context);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => transactionDetailsViewModel.RBFListItems.length,
|
||||
itemBuilder: (__, index) {
|
||||
final item = transactionDetailsViewModel.RBFListItems[index];
|
||||
|
||||
if (item is StandartListItem) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: item.value));
|
||||
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child: ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is StandardExpandableListItem) {
|
||||
return StandardExpandableList(
|
||||
title: '${item.title}: ${item.expandableItems.length}',
|
||||
expandableItems: item.expandableItems,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is StandardPickerListItem) {
|
||||
return StandardPickerList(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
items: item.items,
|
||||
displayItem: item.displayItem,
|
||||
onSliderChanged: item.onSliderChanged,
|
||||
onItemSelected: item.onItemSelected,
|
||||
selectedIdx: item.selectedIdx,
|
||||
customItemIndex: item.customItemIndex,
|
||||
customValue: item.customValue,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
|
||||
return Container();
|
||||
}),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
transactionDetailsViewModel
|
||||
.replaceByFee(transactionDetailsViewModel.newFee.toString());
|
||||
},
|
||||
text: S.of(context).send,
|
||||
isLoading:
|
||||
transactionDetailsViewModel.sendViewModel.state is IsExecutingState,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
))),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop());
|
||||
});
|
||||
});
|
||||
}
|
||||
if (state is AwaitingConfirmationState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: state.title ?? '',
|
||||
alertContent: state.message ?? '',
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () {
|
||||
state.onConfirm?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
state.onCancel?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<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();
|
||||
// transactionStatePopup();
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(popupContext).pop(),
|
||||
feeFiatAmount:
|
||||
transactionDetailsViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
fiatAmountValue:
|
||||
transactionDetailsViewModel.pendingTransactionFiatAmountValueFormatted,
|
||||
outputs: transactionDetailsViewModel.sendViewModel.outputs);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (state is TransactionCommitted) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (context.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());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_effectsInstalled = true;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,18 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_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';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class TransactionDetailsPage extends BasePage {
|
||||
TransactionDetailsPage({required this.transactionDetailsViewModel});
|
||||
@@ -21,41 +24,62 @@ class TransactionDetailsPage extends BasePage {
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => transactionDetailsViewModel.items.length,
|
||||
itemBuilder: (__, index) {
|
||||
final item = transactionDetailsViewModel.items[index];
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => transactionDetailsViewModel.items.length,
|
||||
itemBuilder: (__, index) {
|
||||
final item = transactionDetailsViewModel.items[index];
|
||||
|
||||
if (item is StandartListItem) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: item.value));
|
||||
showBar<void>(context,
|
||||
S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child:
|
||||
ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
if (item is StandartListItem) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: item.value));
|
||||
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child: ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is BlockExplorerListItem) {
|
||||
return GestureDetector(
|
||||
onTap: item.onTap,
|
||||
child:
|
||||
ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
if (item is BlockExplorerListItem) {
|
||||
return GestureDetector(
|
||||
onTap: item.onTap,
|
||||
child: ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
|
||||
return Container();
|
||||
});
|
||||
return Container();
|
||||
}),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
if (transactionDetailsViewModel.canReplaceByFee) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: SelectButton(
|
||||
text: S.of(context).bump_fee,
|
||||
onTap: () async {
|
||||
Navigator.of(context).pushNamed(Routes.bumpFeePage,
|
||||
arguments: transactionDetailsViewModel.transactionInfo);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
|
||||
class StandardExpandableListItem<T> extends TransactionDetailsListItem {
|
||||
StandardExpandableListItem({required String title, required this.expandableItems})
|
||||
: super(title: title, value: '');
|
||||
final List<T> expandableItems;
|
||||
}
|
||||
@@ -31,7 +31,6 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 24.0,
|
||||
|
||||
+254
-173
@@ -24,6 +24,13 @@ class Picker<Item> extends StatefulWidget {
|
||||
this.isGridView = false,
|
||||
this.isSeparated = true,
|
||||
this.hintText,
|
||||
this.headerEnabled = true,
|
||||
this.closeOnItemSelected = true,
|
||||
this.sliderValue,
|
||||
this.customItemIndex,
|
||||
this.isWrapped = true,
|
||||
this.borderColor,
|
||||
this.onSliderChanged,
|
||||
this.matchingCriteria,
|
||||
}) : assert(hintText == null ||
|
||||
matchingCriteria !=
|
||||
@@ -40,6 +47,13 @@ class Picker<Item> extends StatefulWidget {
|
||||
final bool isGridView;
|
||||
final bool isSeparated;
|
||||
final String? hintText;
|
||||
final bool headerEnabled;
|
||||
final bool closeOnItemSelected;
|
||||
final double? sliderValue;
|
||||
final int? customItemIndex;
|
||||
final bool isWrapped;
|
||||
final Color? borderColor;
|
||||
final Function(double)? onSliderChanged;
|
||||
final bool Function(Item, String)? matchingCriteria;
|
||||
|
||||
@override
|
||||
@@ -124,8 +138,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
containerHeight = height * 0.75;
|
||||
}
|
||||
|
||||
return PickerWrapperWidget(
|
||||
hasTitle: widget.title?.isNotEmpty ?? false,
|
||||
final content = Column (
|
||||
children: [
|
||||
if (widget.title?.isNotEmpty ?? false)
|
||||
Container(
|
||||
@@ -144,61 +157,71 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
color: Theme.of(context).dialogTheme.backgroundColor,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: containerHeight,
|
||||
maxWidth: ResponsiveLayoutUtilBase.kPopupWidth,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.hintText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SearchBarWidget(searchController: searchController),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(
|
||||
color: widget.borderColor ?? Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
color: Theme.of(context).dialogTheme.backgroundColor,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: containerHeight,
|
||||
maxWidth: ResponsiveLayoutUtilBase.kPopupWidth,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.hintText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SearchBarWidget(
|
||||
searchController: searchController, hintText: widget.hintText),
|
||||
),
|
||||
Divider(
|
||||
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||
height: 1,
|
||||
),
|
||||
Divider(
|
||||
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||
height: 1,
|
||||
),
|
||||
if (widget.selectedAtIndex != -1) buildSelectedItem(widget.selectedAtIndex),
|
||||
Flexible(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
filteredItems.length > 3
|
||||
? Scrollbar(
|
||||
controller: controller,
|
||||
child: itemsList(),
|
||||
)
|
||||
: itemsList(),
|
||||
(widget.description?.isNotEmpty ?? false)
|
||||
? Positioned(
|
||||
bottom: padding,
|
||||
left: padding,
|
||||
right: padding,
|
||||
child: Text(
|
||||
widget.description!,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
if (widget.selectedAtIndex != -1 && widget.headerEnabled)
|
||||
buildSelectedItem(widget.selectedAtIndex),
|
||||
Flexible(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
filteredItems.length > 3
|
||||
? Scrollbar(
|
||||
controller: controller,
|
||||
child: itemsList(),
|
||||
)
|
||||
: itemsList(),
|
||||
(widget.description?.isNotEmpty ?? false)
|
||||
? Positioned(
|
||||
bottom: padding,
|
||||
left: padding,
|
||||
right: padding,
|
||||
child: Text(
|
||||
widget.description!,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Offstage(),
|
||||
],
|
||||
)
|
||||
: Offstage(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -206,9 +229,23 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
if (widget.isWrapped) {
|
||||
return PickerWrapperWidget(
|
||||
hasTitle: widget.title?.isNotEmpty ?? false,
|
||||
children: [content],
|
||||
);
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
Widget itemsList() {
|
||||
final itemCount = !widget.headerEnabled
|
||||
? items.length
|
||||
: filteredItems.isEmpty
|
||||
? 0
|
||||
: filteredItems.length;
|
||||
return Container(
|
||||
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||
child: widget.isGridView
|
||||
@@ -216,13 +253,16 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
padding: EdgeInsets.zero,
|
||||
controller: controller,
|
||||
shrinkWrap: true,
|
||||
itemCount: filteredItems.isEmpty ? 0 : filteredItems.length,
|
||||
itemCount: itemCount,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 2,
|
||||
childAspectRatio: 3,
|
||||
),
|
||||
itemBuilder: (context, index) => buildItem(index),
|
||||
itemBuilder: (context, index) =>
|
||||
!widget.headerEnabled && widget.selectedAtIndex == index
|
||||
? buildSelectedItem(index)
|
||||
: buildItem(index),
|
||||
)
|
||||
: ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
@@ -234,83 +274,97 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
height: 1,
|
||||
)
|
||||
: const SizedBox(),
|
||||
itemCount: filteredItems.isEmpty ? 0 : filteredItems.length,
|
||||
itemBuilder: (context, index) => buildItem(index),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) =>
|
||||
!widget.headerEnabled && widget.selectedAtIndex == index
|
||||
? buildSelectedItem(index)
|
||||
: buildItem(index),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildItem(int index) {
|
||||
final item = filteredItems[index];
|
||||
final item = widget.headerEnabled ? filteredItems[index] : items[index];
|
||||
|
||||
final tag = item is Currency ? item.tag : null;
|
||||
final icon = _getItemIcon(item);
|
||||
|
||||
final image = images.isNotEmpty ? filteredImages[index] : icon;
|
||||
|
||||
final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex;
|
||||
|
||||
final itemContent = Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: widget.mainAxisAlignment,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: image != null ? 12 : 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.displayItem?.call(item) ?? item.toString(),
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (tag != null)
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
width: 35.0,
|
||||
height: 18.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
tag,
|
||||
style: TextStyle(
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
if (widget.closeOnItemSelected) Navigator.of(context).pop();
|
||||
onItemSelected(item!);
|
||||
},
|
||||
child: Container(
|
||||
height: 55,
|
||||
height: isCustomItem ? 95 : 55,
|
||||
color: Theme.of(context).dialogTheme.backgroundColor,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: widget.mainAxisAlignment,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: image != null ? 12 : 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.displayItem?.call(item) ?? item.toString(),
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (tag != null)
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
width: 35.0,
|
||||
height: 18.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
tag,
|
||||
style: TextStyle(
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color:
|
||||
Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: isCustomItem
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
itemContent,
|
||||
buildSlider(index: index, isActivated: widget.selectedAtIndex == index)
|
||||
],
|
||||
)
|
||||
: itemContent,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -323,69 +377,80 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
|
||||
final image = images.isNotEmpty ? images[index] : icon;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
height: 55,
|
||||
color: Theme.of(context).dialogTheme.backgroundColor,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: widget.mainAxisAlignment,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: image != null ? 12 : 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.displayItem?.call(item) ?? item.toString(),
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex;
|
||||
|
||||
final itemContent = Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: widget.mainAxisAlignment,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: image != null ? 12 : 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.displayItem?.call(item) ?? item.toString(),
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (tag != null)
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
width: 35.0,
|
||||
height: 18.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
tag,
|
||||
style: TextStyle(
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
|
||||
),
|
||||
),
|
||||
if (tag != null)
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
width: 35.0,
|
||||
height: 18.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
tag,
|
||||
style: TextStyle(
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color:
|
||||
Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Icon(Icons.check_circle, color: Theme.of(context).primaryColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(Icons.check_circle, color: Theme.of(context).primaryColor),
|
||||
],
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (widget.closeOnItemSelected) Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
height: isCustomItem ? 95 : 55,
|
||||
color: Theme.of(context).dialogTheme.backgroundColor,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: isCustomItem
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
itemContent,
|
||||
buildSlider(index: index, isActivated: widget.selectedAtIndex == index)
|
||||
],
|
||||
)
|
||||
: itemContent,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -418,4 +483,20 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Widget buildSlider({required int index, required bool isActivated}) {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: widget.sliderValue ?? 1,
|
||||
onChanged: isActivated ? widget.onSliderChanged : null,
|
||||
min: 1,
|
||||
max: 100,
|
||||
divisions: 100,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class SearchBarWidget extends StatelessWidget {
|
||||
controller: searchController,
|
||||
style: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText ?? S.of(context).search_currency,
|
||||
hintText: hintText ?? S.of(context).search,
|
||||
hintStyle: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
|
||||
prefixIcon: Image.asset("assets/images/search_icon.png",
|
||||
color: Theme.of(context).extension<PickerTheme>()!.searchIconColor),
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StandardExpandableList<T> extends StatelessWidget {
|
||||
StandardExpandableList({
|
||||
required this.title,
|
||||
required this.expandableItems,
|
||||
this.decoration,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final List<T> expandableItems;
|
||||
final Decoration? decoration;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: decoration ??
|
||||
BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
iconColor: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
collapsedIconColor:
|
||||
Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
children: expandableItems.map((item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, bottom: 8.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
item.toString(),
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StandardPickerList<T> extends StatefulWidget {
|
||||
StandardPickerList({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.items,
|
||||
required this.displayItem,
|
||||
required this.onSliderChanged,
|
||||
required this.onItemSelected,
|
||||
required this.selectedIdx,
|
||||
required this.customItemIndex,
|
||||
required this.customValue,
|
||||
}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
final List<T> items;
|
||||
final int customItemIndex;
|
||||
final String Function(T item, double sliderValue) displayItem;
|
||||
final Function(double) onSliderChanged;
|
||||
final Function(T) onItemSelected;
|
||||
final String value;
|
||||
final int selectedIdx;
|
||||
final double customValue;
|
||||
|
||||
@override
|
||||
_StandardPickerListState<T> createState() => _StandardPickerListState<T>();
|
||||
}
|
||||
|
||||
class _StandardPickerListState<T> extends State<StandardPickerList<T>> {
|
||||
late String value;
|
||||
late int selectedIdx;
|
||||
late double customValue;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
value = widget.value;
|
||||
selectedIdx = widget.selectedIdx;
|
||||
customValue = widget.customValue;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String adaptedDisplayItem(T item) => widget.displayItem(item, customValue);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
ListRow(title: '${widget.title}:', value: value),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24, top: 0, bottom: 24),
|
||||
child: Picker(
|
||||
items: widget.items,
|
||||
displayItem: adaptedDisplayItem,
|
||||
selectedAtIndex: selectedIdx,
|
||||
customItemIndex: widget.customItemIndex,
|
||||
headerEnabled: false,
|
||||
closeOnItemSelected: false,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
sliderValue: customValue,
|
||||
isWrapped: false,
|
||||
borderColor: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||
onSliderChanged: (newValue) {
|
||||
setState(() => customValue = newValue);
|
||||
value = widget.onSliderChanged(newValue).toString();
|
||||
},
|
||||
onItemSelected: (T item) {
|
||||
setState(() => selectedIdx = widget.items.indexOf(item));
|
||||
value = widget.onItemSelected(item).toString();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user