desktop trade steps flow fade transition and state management updates

This commit is contained in:
julian 2022-11-30 15:15:06 -06:00
parent 7641539bf7
commit 779bf20cc4
10 changed files with 584 additions and 566 deletions

View file

@ -1,8 +1,9 @@
import 'package:decimal/decimal.dart';
import 'package:flutter/foundation.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
class IncompleteExchangeModel {
class IncompleteExchangeModel extends ChangeNotifier {
final String sendTicker;
final String receiveTicker;
@ -15,12 +16,49 @@ class IncompleteExchangeModel {
final bool reversed;
String? recipientAddress;
String? refundAddress;
String? _recipientAddress;
String? rateId;
String? get recipientAddress => _recipientAddress;
Trade? trade;
set recipientAddress(String? recipientAddress) {
if (_recipientAddress != recipientAddress) {
_recipientAddress = recipientAddress;
notifyListeners();
}
}
String? _refundAddress;
String? get refundAddress => _refundAddress;
set refundAddress(String? refundAddress) {
if (_refundAddress != refundAddress) {
_refundAddress = refundAddress;
notifyListeners();
}
}
String? _rateId;
String? get rateId => _rateId;
set rateId(String? rateId) {
if (_rateId != rateId) {
_rateId = rateId;
notifyListeners();
}
}
Trade? _trade;
Trade? get trade => _trade;
set trade(Trade? trade) {
if (_trade != trade) {
_trade = trade;
notifyListeners();
}
}
IncompleteExchangeModel({
required this.sendTicker,
@ -30,6 +68,6 @@ class IncompleteExchangeModel {
required this.receiveAmount,
required this.rateType,
required this.reversed,
this.rateId,
});
String? rateId,
}) : _rateId = rateId;
}

View file

@ -18,8 +18,6 @@ import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_op
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
@ -1022,19 +1020,16 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
ref.read(exchangeSendFromWalletIdStateProvider.state).state =
Tuple2(walletId!, coin!);
if (isDesktop) {
ref.read(ssss.state).state = model;
await showDialog<void>(
context: context,
barrierDismissible: false,
builder: (context) {
return DesktopDialog(
return const DesktopDialog(
maxWidth: 720,
maxHeight: double.infinity,
child: StepScaffold(
step: 2,
model: model,
body: DesktopStep2(
model: model,
),
initialStep: 2,
),
);
},
@ -1051,19 +1046,16 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
ref.read(exchangeSendFromWalletIdStateProvider.state).state = null;
if (isDesktop) {
ref.read(ssss.state).state = model;
await showDialog<void>(
context: context,
barrierDismissible: false,
builder: (context) {
return DesktopDialog(
return const DesktopDialog(
maxWidth: 720,
maxHeight: double.infinity,
child: StepScaffold(
step: 1,
model: model,
body: DesktopStep1(
model: model,
),
initialStep: 1,
),
);
},

View file

@ -1,39 +1,190 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'dart:async';
class StepScaffold extends StatefulWidget {
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart';
import 'package:stackwallet/providers/exchange/exchange_provider.dart';
import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/desktop/simple_desktop_dialog.dart';
import 'package:stackwallet/widgets/fade_stack.dart';
final ssss = StateProvider<IncompleteExchangeModel?>((_) => null);
final desktopExchangeModelProvider =
ChangeNotifierProvider<IncompleteExchangeModel?>(
(ref) => ref.watch(ssss.state).state);
class StepScaffold extends ConsumerStatefulWidget {
const StepScaffold({
Key? key,
required this.body,
required this.step,
required this.model,
required this.initialStep,
}) : super(key: key);
final Widget body;
final int step;
final IncompleteExchangeModel model;
final int initialStep;
@override
State<StepScaffold> createState() => _StepScaffoldState();
ConsumerState<StepScaffold> createState() => _StepScaffoldState();
}
class _StepScaffoldState extends State<StepScaffold> {
int currentStep = 0;
late final IncompleteExchangeModel model;
class _StepScaffoldState extends ConsumerState<StepScaffold> {
int currentStep = 1;
bool enableNext = false;
late final Duration duration;
void updateEnableNext(bool enableNext) {
if (enableNext != this.enableNext) {
setState(() => this.enableNext = enableNext);
}
}
Future<bool> createTrade() async {
unawaited(
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => WillPopScope(
onWillPop: () async => false,
child: Container(
color: Theme.of(context)
.extension<StackColors>()!
.overlay
.withOpacity(0.6),
child: const CustomLoadingOverlay(
message: "Creating a trade",
eventBus: null,
),
),
),
),
);
final ExchangeResponse<Trade> response = await ref
.read(exchangeProvider)
.createTrade(
from: ref.read(desktopExchangeModelProvider)!.sendTicker,
to: ref.read(desktopExchangeModelProvider)!.receiveTicker,
fixedRate: ref.read(desktopExchangeModelProvider)!.rateType !=
ExchangeRateType.estimated,
amount: ref.read(desktopExchangeModelProvider)!.reversed
? ref.read(desktopExchangeModelProvider)!.receiveAmount
: ref.read(desktopExchangeModelProvider)!.sendAmount,
addressTo: ref.read(desktopExchangeModelProvider)!.recipientAddress!,
extraId: null,
addressRefund: ref.read(desktopExchangeModelProvider)!.refundAddress!,
refundExtraId: "",
rateId: ref.read(desktopExchangeModelProvider)!.rateId,
reversed: ref.read(desktopExchangeModelProvider)!.reversed,
);
if (response.value == null) {
if (mounted) {
Navigator.of(context).pop();
}
unawaited(
showDialog<void>(
context: context,
barrierDismissible: true,
builder: (_) => SimpleDesktopDialog(
title: "Failed to create trade",
message: response.exception?.toString() ?? ""),
),
);
return false;
}
// save trade to hive
await ref.read(tradesServiceProvider).add(
trade: response.value!,
shouldNotifyListeners: true,
);
String status = response.value!.status;
ref.read(desktopExchangeModelProvider)!.trade = response.value!;
// extra info if status is waiting
if (status == "Waiting") {
status += " for deposit";
}
if (mounted) {
Navigator.of(context).pop();
}
unawaited(
NotificationApi.showNotification(
changeNowId: ref.read(desktopExchangeModelProvider)!.trade!.tradeId,
title: status,
body:
"Trade ID ${ref.read(desktopExchangeModelProvider)!.trade!.tradeId}",
walletId: "",
iconAssetName: Assets.svg.arrowRotate,
date: ref.read(desktopExchangeModelProvider)!.trade!.timestamp,
shouldWatchForUpdates: true,
coinName: "coinName",
),
);
return true;
// if (mounted) {
// unawaited(
// showDialog<void>(
// context: context,
// barrierColor: Colors.transparent,
// barrierDismissible: false,
// builder: (context) {
// return DesktopDialog(
// maxWidth: 720,
// maxHeight: double.infinity,
// child: StepScaffold(
// initialStep: 4,
// ),
// );
// },
// ),
// );
// }
}
void onBack() {
if (currentStep > 1 && currentStep < 4) {
setState(() => currentStep = currentStep - 1);
} else if (currentStep == 1) {
Navigator.of(context, rootNavigator: true).pop();
}
}
@override
void initState() {
currentStep = widget.step;
model = widget.model;
duration = const Duration(milliseconds: 250);
currentStep = widget.initialStep;
super.initState();
}
@override
Widget build(BuildContext context) {
final model = ref.watch(desktopExchangeModelProvider);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
@ -43,15 +194,16 @@ class _StepScaffoldState extends State<StepScaffold> {
Row(
children: [
currentStep != 4
? const AppBarBackButton(
? AppBarBackButton(
isCompact: true,
iconSize: 23,
onPressed: onBack,
)
: const SizedBox(
width: 32,
),
Text(
"Exchange ${model.sendTicker.toUpperCase()} to ${model.receiveTicker.toUpperCase()}",
"Exchange ${model?.sendTicker.toUpperCase()} to ${model?.receiveTicker.toUpperCase()}",
style: STextStyles.desktopH3(context),
),
],
@ -60,9 +212,6 @@ class _StepScaffoldState extends State<StepScaffold> {
DesktopDialogCloseButton(
onPressedOverride: () {
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context, rootNavigator: true).pop();
},
),
],
@ -85,7 +234,139 @@ class _StepScaffoldState extends State<StepScaffold> {
padding: const EdgeInsets.symmetric(
horizontal: 32,
),
child: widget.body,
child: FadeStack(
index: currentStep - 1,
children: [
const DesktopStep1(),
DesktopStep2(
enableNextChanged: updateEnableNext,
),
const DesktopStep3(),
const DesktopStep4(),
],
),
),
Padding(
padding: const EdgeInsets.only(
top: 20,
left: 32,
right: 32,
bottom: 32,
),
child: Row(
children: [
Expanded(
child: AnimatedCrossFade(
duration: const Duration(milliseconds: 250),
crossFadeState: currentStep == 4
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
firstChild: SecondaryButton(
label: "Back",
buttonHeight: ButtonHeight.l,
onPressed: onBack,
),
secondChild: SecondaryButton(
label: "Send from Stack Wallet",
buttonHeight: ButtonHeight.l,
onPressed: onBack,
),
),
),
const SizedBox(
width: 16,
),
Expanded(
child: AnimatedCrossFade(
duration: const Duration(milliseconds: 250),
crossFadeState: currentStep == 4
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
firstChild: AnimatedCrossFade(
duration: const Duration(milliseconds: 250),
crossFadeState: currentStep == 3
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
firstChild: PrimaryButton(
label: "Next",
enabled: currentStep != 2 ? true : enableNext,
buttonHeight: ButtonHeight.l,
onPressed: () async {
setState(() => currentStep = currentStep + 1);
},
),
secondChild: PrimaryButton(
label: "Confirm",
enabled: currentStep != 2 ? true : enableNext,
buttonHeight: ButtonHeight.l,
onPressed: () async {
if (currentStep == 3) {
final success = await createTrade();
if (!success) {
return;
}
}
setState(() => currentStep = currentStep + 1);
},
),
),
secondChild: PrimaryButton(
label: "Show QR code",
enabled: currentStep != 2 ? true : enableNext,
buttonHeight: ButtonHeight.l,
onPressed: () {
showDialog<dynamic>(
context: context,
barrierColor: Colors.transparent,
barrierDismissible: true,
builder: (_) {
return DesktopDialog(
maxHeight: 720,
maxWidth: 720,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Send ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendAmount.toStringAsFixed(8)))} ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker))} to this address",
style: STextStyles.desktopH3(context),
),
const SizedBox(
height: 48,
),
Center(
child: QrImage(
// TODO: grab coin uri scheme from somewhere
// data: "${coin.uriScheme}:$receivingAddress",
data: ref.watch(desktopExchangeModelProvider
.select((value) =>
value!.trade!.payInAddress)),
size: 290,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
const SizedBox(
height: 48,
),
SecondaryButton(
label: "Cancel",
width: 310,
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(context).pop,
),
],
),
);
},
);
},
),
),
),
],
),
),
],
);

View file

@ -1,26 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class DesktopStep1 extends ConsumerWidget {
const DesktopStep1({
Key? key,
required this.model,
}) : super(key: key);
final IncompleteExchangeModel model;
@override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
@ -55,7 +47,7 @@ class DesktopStep1 extends ConsumerWidget {
DesktopStepItem(
label: "You send",
value:
"${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}",
"${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendAmount.toStringAsFixed(8)))} ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))}",
),
Container(
height: 1,
@ -64,63 +56,20 @@ class DesktopStep1 extends ConsumerWidget {
DesktopStepItem(
label: "You receive",
value:
"~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}",
"~${ref.watch(desktopExchangeModelProvider.select((value) => value!.receiveAmount.toStringAsFixed(8)))} ${ref.watch(desktopExchangeModelProvider.select((value) => value!.receiveTicker.toUpperCase()))}",
),
Container(
height: 1,
color: Theme.of(context).extension<StackColors>()!.background,
),
DesktopStepItem(
label: model.rateType == ExchangeRateType.estimated
label: ref.watch(desktopExchangeModelProvider
.select((value) => value!.rateType)) ==
ExchangeRateType.estimated
? "Estimated rate"
: "Fixed rate",
value: model.rateInfo,
),
],
),
),
Padding(
padding: const EdgeInsets.only(
top: 20,
bottom: 32,
),
child: Row(
children: [
Expanded(
child: SecondaryButton(
label: "Back",
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(context).pop,
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Next",
buttonHeight: ButtonHeight.l,
onPressed: () async {
await showDialog<void>(
context: context,
barrierColor: Colors.transparent,
barrierDismissible: false,
builder: (context) {
return DesktopDialog(
maxWidth: 720,
maxHeight: double.infinity,
child: StepScaffold(
step: 2,
model: model,
body: DesktopStep2(
model: model,
),
),
);
},
);
},
),
value: ref.watch(desktopExchangeModelProvider
.select((value) => value!.rateInfo)),
),
],
),

View file

@ -2,9 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/contact_address_entry.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart';
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart';
import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart';
@ -18,31 +16,29 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:tuple/tuple.dart';
class DesktopStep2 extends ConsumerStatefulWidget {
const DesktopStep2({
Key? key,
required this.model,
required this.enableNextChanged,
this.clipboard = const ClipboardWrapper(),
}) : super(key: key);
final IncompleteExchangeModel model;
final ClipboardInterface clipboard;
final void Function(bool) enableNextChanged;
@override
ConsumerState<DesktopStep2> createState() => _DesktopStep2State();
}
class _DesktopStep2State extends ConsumerState<DesktopStep2> {
late final IncompleteExchangeModel model;
late final ClipboardInterface clipboard;
late final TextEditingController _toController;
@ -51,8 +47,6 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
late final FocusNode _toFocusNode;
late final FocusNode _refundFocusNode;
bool enableNext = false;
bool isStackCoin(String ticker) {
try {
coinFromTickerCaseInsensitive(ticker);
@ -65,10 +59,10 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
void selectRecipientAddressFromStack() async {
try {
final coin = coinFromTickerCaseInsensitive(
model.receiveTicker,
ref.read(desktopExchangeModelProvider)!.receiveTicker,
);
final address = await showDialog<String?>(
final info = await showDialog<Tuple2<String, String>?>(
context: context,
barrierColor: Colors.transparent,
builder: (context) => DesktopDialog(
@ -83,29 +77,25 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
),
);
if (address is String) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(address);
_toController.text = manager.walletName;
model.recipientAddress = await manager.currentReceivingAddress;
if (info is Tuple2<String, String>) {
_toController.text = info.item1;
ref.read(desktopExchangeModelProvider)!.recipientAddress = info.item2;
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Info);
}
setState(() {
enableNext =
_toController.text.isNotEmpty && _refundController.text.isNotEmpty;
});
widget.enableNextChanged.call(
_toController.text.isNotEmpty && _refundController.text.isNotEmpty);
}
void selectRefundAddressFromStack() async {
try {
final coin = coinFromTickerCaseInsensitive(
model.sendTicker,
ref.read(desktopExchangeModelProvider)!.sendTicker,
);
final address = await showDialog<String?>(
final info = await showDialog<Tuple2<String, String>?>(
context: context,
barrierColor: Colors.transparent,
builder: (context) => DesktopDialog(
@ -119,25 +109,20 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
),
),
);
if (address is String) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(address);
_refundController.text = manager.walletName;
model.refundAddress = await manager.currentReceivingAddress;
if (info is Tuple2<String, String>) {
_refundController.text = info.item1;
ref.read(desktopExchangeModelProvider)!.refundAddress = info.item2;
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Info);
}
setState(() {
enableNext =
_toController.text.isNotEmpty && _refundController.text.isNotEmpty;
});
widget.enableNextChanged.call(
_toController.text.isNotEmpty && _refundController.text.isNotEmpty);
}
void selectRecipientFromAddressBook() async {
final coin = coinFromTickerCaseInsensitive(
model.receiveTicker,
ref.read(desktopExchangeModelProvider)!.receiveTicker,
);
final entry = await showDialog<ContactAddressEntry?>(
@ -176,17 +161,15 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
if (entry != null) {
_toController.text = entry.address;
model.recipientAddress = entry.address;
setState(() {
enableNext =
_toController.text.isNotEmpty && _refundController.text.isNotEmpty;
});
ref.read(desktopExchangeModelProvider)!.recipientAddress = entry.address;
widget.enableNextChanged.call(
_toController.text.isNotEmpty && _refundController.text.isNotEmpty);
}
}
void selectRefundFromAddressBook() async {
final coin = coinFromTickerCaseInsensitive(
model.sendTicker,
ref.read(desktopExchangeModelProvider)!.sendTicker,
);
final entry = await showDialog<ContactAddressEntry?>(
@ -225,17 +208,14 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
if (entry != null) {
_refundController.text = entry.address;
model.refundAddress = entry.address;
setState(() {
enableNext =
_toController.text.isNotEmpty && _refundController.text.isNotEmpty;
});
ref.read(desktopExchangeModelProvider)!.refundAddress = entry.address;
widget.enableNextChanged.call(
_toController.text.isNotEmpty && _refundController.text.isNotEmpty);
}
}
@override
void initState() {
model = widget.model;
clipboard = widget.clipboard;
_toController = TextEditingController();
@ -246,7 +226,7 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
final tuple = ref.read(exchangeSendFromWalletIdStateProvider.state).state;
if (tuple != null) {
if (model.receiveTicker.toLowerCase() ==
if (ref.read(desktopExchangeModelProvider)!.receiveTicker.toLowerCase() ==
tuple.item2.ticker.toLowerCase()) {
ref
.read(walletsChangeNotifierProvider)
@ -254,10 +234,11 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
.currentReceivingAddress
.then((value) {
_toController.text = value;
model.recipientAddress = _toController.text;
ref.read(desktopExchangeModelProvider)!.recipientAddress =
_toController.text;
});
} else {
if (model.sendTicker.toUpperCase() ==
if (ref.read(desktopExchangeModelProvider)!.sendTicker.toUpperCase() ==
tuple.item2.ticker.toUpperCase()) {
ref
.read(walletsChangeNotifierProvider)
@ -265,7 +246,8 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
.currentReceivingAddress
.then((value) {
_refundController.text = value;
model.refundAddress = _refundController.text;
ref.read(desktopExchangeModelProvider)!.refundAddress =
_refundController.text;
});
}
}
@ -316,7 +298,8 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
.extension<StackColors>()!
.textFieldActiveSearchIconRight),
),
if (isStackCoin(model.receiveTicker))
if (isStackCoin(ref.watch(desktopExchangeModelProvider
.select((value) => value!.receiveTicker))))
BlueTextButton(
text: "Choose from stack",
onTap: selectRecipientAddressFromStack,
@ -349,13 +332,11 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
focusNode: _toFocusNode,
style: STextStyles.field(context),
onChanged: (value) {
setState(() {
enableNext = _toController.text.isNotEmpty &&
_refundController.text.isNotEmpty;
});
widget.enableNextChanged.call(_toController.text.isNotEmpty &&
_refundController.text.isNotEmpty);
},
decoration: standardInputDecoration(
"Enter the ${model.receiveTicker.toUpperCase()} payout address",
"Enter the ${ref.watch(desktopExchangeModelProvider.select((value) => value!.receiveTicker.toUpperCase()))} payout address",
_toFocusNode,
context,
desktopMed: true,
@ -380,11 +361,12 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
"sendViewClearAddressFieldButtonKey"),
onTap: () {
_toController.text = "";
model.recipientAddress = _toController.text;
setState(() {
enableNext = _toController.text.isNotEmpty &&
_refundController.text.isNotEmpty;
});
ref
.read(desktopExchangeModelProvider)!
.recipientAddress = _toController.text;
widget.enableNextChanged.call(
_toController.text.isNotEmpty &&
_refundController.text.isNotEmpty);
},
child: const XIcon(),
)
@ -398,12 +380,12 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
data!.text!.isNotEmpty) {
final content = data.text!.trim();
_toController.text = content;
model.recipientAddress = _toController.text;
setState(() {
enableNext =
_toController.text.isNotEmpty &&
_refundController.text.isNotEmpty;
});
ref
.read(desktopExchangeModelProvider)!
.recipientAddress = _toController.text;
widget.enableNextChanged.call(
_toController.text.isNotEmpty &&
_refundController.text.isNotEmpty);
}
},
child: _toController.text.isEmpty
@ -411,7 +393,8 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
: const XIcon(),
),
if (_toController.text.isEmpty &&
isStackCoin(model.receiveTicker))
isStackCoin(ref.watch(desktopExchangeModelProvider
.select((value) => value!.receiveTicker))))
TextFieldIconButton(
key: const Key("sendViewAddressBookButtonKey"),
onTap: selectRecipientFromAddressBook,
@ -430,7 +413,7 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
RoundedWhiteContainer(
borderColor: Theme.of(context).extension<StackColors>()!.background,
child: Text(
"This is the wallet where your ${model.receiveTicker.toUpperCase()} will be sent to.",
"This is the wallet where your ${ref.watch(desktopExchangeModelProvider.select((value) => value!.receiveTicker.toUpperCase()))} will be sent to.",
style: STextStyles.desktopTextExtraExtraSmall(context),
),
),
@ -447,7 +430,8 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
.extension<StackColors>()!
.textFieldActiveSearchIconRight),
),
if (isStackCoin(model.sendTicker))
if (isStackCoin(ref.watch(desktopExchangeModelProvider
.select((value) => value!.sendTicker))))
BlueTextButton(
text: "Choose from stack",
onTap: selectRefundAddressFromStack,
@ -479,13 +463,11 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
focusNode: _refundFocusNode,
style: STextStyles.field(context),
onChanged: (value) {
setState(() {
enableNext = _toController.text.isNotEmpty &&
_refundController.text.isNotEmpty;
});
widget.enableNextChanged.call(_toController.text.isNotEmpty &&
_refundController.text.isNotEmpty);
},
decoration: standardInputDecoration(
"Enter ${model.sendTicker.toUpperCase()} refund address",
"Enter ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))} refund address",
_refundFocusNode,
context,
desktopMed: true,
@ -510,12 +492,13 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
"sendViewClearAddressFieldButtonKey"),
onTap: () {
_refundController.text = "";
model.refundAddress = _refundController.text;
ref
.read(desktopExchangeModelProvider)!
.refundAddress = _refundController.text;
setState(() {
enableNext = _toController.text.isNotEmpty &&
_refundController.text.isNotEmpty;
});
widget.enableNextChanged.call(
_toController.text.isNotEmpty &&
_refundController.text.isNotEmpty);
},
child: const XIcon(),
)
@ -530,13 +513,13 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
final content = data.text!.trim();
_refundController.text = content;
model.refundAddress = _refundController.text;
ref
.read(desktopExchangeModelProvider)!
.refundAddress = _refundController.text;
setState(() {
enableNext =
_toController.text.isNotEmpty &&
_refundController.text.isNotEmpty;
});
widget.enableNextChanged.call(
_toController.text.isNotEmpty &&
_refundController.text.isNotEmpty);
}
},
child: _refundController.text.isEmpty
@ -544,7 +527,8 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
: const XIcon(),
),
if (_refundController.text.isEmpty &&
isStackCoin(model.sendTicker))
isStackCoin(ref.watch(desktopExchangeModelProvider
.select((value) => value!.sendTicker))))
TextFieldIconButton(
key: const Key("sendViewAddressBookButtonKey"),
onTap: selectRefundFromAddressBook,
@ -567,53 +551,6 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
style: STextStyles.desktopTextExtraExtraSmall(context),
),
),
Padding(
padding: const EdgeInsets.only(
top: 20,
bottom: 32,
),
child: Row(
children: [
Expanded(
child: SecondaryButton(
label: "Back",
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(context).pop,
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Next",
enabled: enableNext,
buttonHeight: ButtonHeight.l,
onPressed: () async {
await showDialog<void>(
context: context,
barrierColor: Colors.transparent,
barrierDismissible: false,
builder: (context) {
return DesktopDialog(
maxWidth: 720,
maxHeight: double.infinity,
child: StepScaffold(
step: 3,
model: model,
body: DesktopStep3(
model: model,
),
),
);
},
);
},
),
),
],
),
),
],
);
}

View file

@ -1,157 +1,23 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart';
import 'package:stackwallet/providers/exchange/current_exchange_name_state_provider.dart';
import 'package:stackwallet/providers/exchange/exchange_provider.dart';
import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/desktop/simple_desktop_dialog.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class DesktopStep3 extends ConsumerStatefulWidget {
const DesktopStep3({
Key? key,
required this.model,
}) : super(key: key);
final IncompleteExchangeModel model;
@override
ConsumerState<DesktopStep3> createState() => _DesktopStep3State();
}
class _DesktopStep3State extends ConsumerState<DesktopStep3> {
late final IncompleteExchangeModel model;
Future<void> createTrade() async {
unawaited(
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => WillPopScope(
onWillPop: () async => false,
child: Container(
color: Theme.of(context)
.extension<StackColors>()!
.overlay
.withOpacity(0.6),
child: const CustomLoadingOverlay(
message: "Creating a trade",
eventBus: null,
),
),
),
),
);
final ExchangeResponse<Trade> response =
await ref.read(exchangeProvider).createTrade(
from: model.sendTicker,
to: model.receiveTicker,
fixedRate: model.rateType != ExchangeRateType.estimated,
amount: model.reversed ? model.receiveAmount : model.sendAmount,
addressTo: model.recipientAddress!,
extraId: null,
addressRefund: model.refundAddress!,
refundExtraId: "",
rateId: model.rateId,
reversed: model.reversed,
);
if (response.value == null) {
if (mounted) {
Navigator.of(context).pop();
}
unawaited(
showDialog<void>(
context: context,
barrierDismissible: true,
builder: (_) => SimpleDesktopDialog(
title: "Failed to create trade",
message: response.exception?.toString() ?? ""),
),
);
return;
}
// save trade to hive
await ref.read(tradesServiceProvider).add(
trade: response.value!,
shouldNotifyListeners: true,
);
String status = response.value!.status;
model.trade = response.value!;
// extra info if status is waiting
if (status == "Waiting") {
status += " for deposit";
}
if (mounted) {
Navigator.of(context).pop();
}
unawaited(
NotificationApi.showNotification(
changeNowId: model.trade!.tradeId,
title: status,
body: "Trade ID ${model.trade!.tradeId}",
walletId: "",
iconAssetName: Assets.svg.arrowRotate,
date: model.trade!.timestamp,
shouldWatchForUpdates: true,
coinName: "coinName",
),
);
if (mounted) {
unawaited(
showDialog<void>(
context: context,
barrierColor: Colors.transparent,
barrierDismissible: false,
builder: (context) {
return DesktopDialog(
maxWidth: 720,
maxHeight: double.infinity,
child: StepScaffold(
step: 4,
model: model,
body: DesktopStep4(
model: model,
),
),
);
},
),
);
}
}
@override
void initState() {
model = widget.model;
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(
@ -179,7 +45,7 @@ class _DesktopStep3State extends ConsumerState<DesktopStep3> {
DesktopStepItem(
label: "You send",
value:
"${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}",
"${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendAmount.toStringAsFixed(8)))} ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))}",
),
Container(
height: 1,
@ -188,17 +54,20 @@ class _DesktopStep3State extends ConsumerState<DesktopStep3> {
DesktopStepItem(
label: "You receive",
value:
"~${model.receiveAmount.toStringAsFixed(8)} ${model.receiveTicker.toUpperCase()}",
"~${ref.watch(desktopExchangeModelProvider.select((value) => value!.receiveAmount.toStringAsFixed(8)))} ${ref.watch(desktopExchangeModelProvider.select((value) => value!.receiveTicker.toUpperCase()))}",
),
Container(
height: 1,
color: Theme.of(context).extension<StackColors>()!.background,
),
DesktopStepItem(
label: model.rateType == ExchangeRateType.estimated
label: ref.watch(desktopExchangeModelProvider
.select((value) => value!.rateType)) ==
ExchangeRateType.estimated
? "Estimated rate"
: "Fixed rate",
value: model.rateInfo,
value: ref.watch(desktopExchangeModelProvider
.select((value) => value!.rateInfo)),
),
Container(
height: 1,
@ -206,8 +75,11 @@ class _DesktopStep3State extends ConsumerState<DesktopStep3> {
),
DesktopStepItem(
vertical: true,
label: "Recipient ${model.receiveTicker.toUpperCase()} address",
value: model.recipientAddress!,
label:
"Recipient ${ref.watch(desktopExchangeModelProvider.select((value) => value!.receiveTicker.toUpperCase()))} address",
value: ref.watch(desktopExchangeModelProvider
.select((value) => value!.recipientAddress)) ??
"Error",
),
Container(
height: 1,
@ -215,35 +87,11 @@ class _DesktopStep3State extends ConsumerState<DesktopStep3> {
),
DesktopStepItem(
vertical: true,
label: "Refund ${model.sendTicker.toUpperCase()} address",
value: model.refundAddress!,
),
],
),
),
Padding(
padding: const EdgeInsets.only(
top: 20,
bottom: 32,
),
child: Row(
children: [
Expanded(
child: SecondaryButton(
label: "Back",
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(context).pop,
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Confirm",
buttonHeight: ButtonHeight.l,
onPressed: createTrade,
),
label:
"Refund ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))} address",
value: ref.watch(desktopExchangeModelProvider
.select((value) => value!.refundAddress)) ??
"Error",
),
],
),

View file

@ -1,38 +1,26 @@
import 'dart:async';
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/pages/exchange_view/send_from_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class DesktopStep4 extends ConsumerStatefulWidget {
const DesktopStep4({
Key? key,
required this.model,
}) : super(key: key);
final IncompleteExchangeModel model;
@override
ConsumerState<DesktopStep4> createState() => _DesktopStep4State();
}
class _DesktopStep4State extends ConsumerState<DesktopStep4> {
late final IncompleteExchangeModel model;
String _statusString = "New";
Timer? _statusTimer;
@ -51,8 +39,13 @@ class _DesktopStep4State extends ConsumerState<DesktopStep4> {
}
Future<void> _updateStatus() async {
final statusResponse =
await ref.read(exchangeProvider).updateTrade(model.trade!);
final trade = ref.read(desktopExchangeModelProvider)?.trade;
if (trade == null) {
return;
}
final statusResponse = await ref.read(exchangeProvider).updateTrade(trade);
String status = "Waiting";
if (statusResponse.value != null) {
status = statusResponse.value!.status;
@ -72,8 +65,6 @@ class _DesktopStep4State extends ConsumerState<DesktopStep4> {
@override
void initState() {
model = widget.model;
_statusTimer = Timer.periodic(const Duration(seconds: 60), (_) {
_updateStatus();
});
@ -93,14 +84,14 @@ class _DesktopStep4State extends ConsumerState<DesktopStep4> {
return Column(
children: [
Text(
"Send ${model.sendTicker.toUpperCase()} to the address below",
"Send ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))} to the address below",
style: STextStyles.desktopTextMedium(context),
),
const SizedBox(
height: 8,
),
Text(
"Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ${model.trade!.exchangeName} will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.",
"Send ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))} to the address below. Once it is received, ${ref.watch(desktopExchangeModelProvider.select((value) => value!.trade?.exchangeName))} will send the ${ref.watch(desktopExchangeModelProvider.select((value) => value!.receiveTicker.toUpperCase()))} to the recipient address you provided. You can find this trade details and check its status in the list of trades.",
style: STextStyles.desktopTextExtraExtraSmall(context),
),
const SizedBox(
@ -111,7 +102,7 @@ class _DesktopStep4State extends ConsumerState<DesktopStep4> {
child: RichText(
text: TextSpan(
text:
"You must send at least ${model.sendAmount.toString()} ${model.sendTicker}. ",
"You must send at least ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendAmount.toString()))} ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker))}. ",
style: STextStyles.label700(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
@ -121,7 +112,7 @@ class _DesktopStep4State extends ConsumerState<DesktopStep4> {
children: [
TextSpan(
text:
"If you send less than ${model.sendAmount.toString()} ${model.sendTicker}, your transaction may not be converted and it may not be refunded.",
"If you send less than ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendAmount.toString()))} ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker))}, your transaction may not be converted and it may not be refunded.",
style: STextStyles.label(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
@ -143,8 +134,11 @@ class _DesktopStep4State extends ConsumerState<DesktopStep4> {
children: [
DesktopStepItem(
vertical: true,
label: "Send ${model.sendTicker.toUpperCase()} to this address",
value: model.trade!.payInAddress,
label:
"Send ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))} to this address",
value: ref.watch(desktopExchangeModelProvider
.select((value) => value!.trade?.payInAddress)) ??
"Error",
),
Container(
height: 1,
@ -153,7 +147,7 @@ class _DesktopStep4State extends ConsumerState<DesktopStep4> {
DesktopStepItem(
label: "Amount",
value:
"${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker.toUpperCase()}",
"${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendAmount.toStringAsFixed(8)))} ${ref.watch(desktopExchangeModelProvider.select((value) => value!.sendTicker.toUpperCase()))}",
),
Container(
height: 1,
@ -161,7 +155,9 @@ class _DesktopStep4State extends ConsumerState<DesktopStep4> {
),
DesktopStepItem(
label: "Trade ID",
value: model.trade!.tradeId,
value: ref.watch(desktopExchangeModelProvider
.select((value) => value!.trade?.tradeId)) ??
"Error",
),
Container(
height: 1,
@ -191,110 +187,6 @@ class _DesktopStep4State extends ConsumerState<DesktopStep4> {
],
),
),
Padding(
padding: const EdgeInsets.only(
top: 20,
bottom: 32,
),
child: Row(
children: [
Expanded(
child: SecondaryButton(
label: "Send from Stack Wallet",
buttonHeight: ButtonHeight.l,
onPressed: () {
final trade = model.trade!;
final amount = Decimal.parse(trade.payInAmount);
final address = trade.payInAddress;
final coin =
coinFromTickerCaseInsensitive(trade.payInCurrency);
showDialog<void>(
context: context,
builder: (context) => Navigator(
initialRoute: SendFromView.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
FadePageRoute(
SendFromView(
coin: coin,
trade: trade,
amount: amount,
address: address,
shouldPopRoot: true,
fromDesktopStep4: true,
),
const RouteSettings(
name: SendFromView.routeName,
),
),
];
},
),
);
},
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Show QR code",
buttonHeight: ButtonHeight.l,
onPressed: () {
showDialog<dynamic>(
context: context,
barrierColor: Colors.transparent,
barrierDismissible: true,
builder: (_) {
return DesktopDialog(
maxHeight: 720,
maxWidth: 720,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Send ${model.sendAmount.toStringAsFixed(8)} ${model.sendTicker} to this address",
style: STextStyles.desktopH3(context),
),
const SizedBox(
height: 48,
),
Center(
child: QrImage(
// TODO: grab coin uri scheme from somewhere
// data: "${coin.uriScheme}:$receivingAddress",
data: model.trade!.payInAddress,
size: 290,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
const SizedBox(
height: 48,
),
SecondaryButton(
label: "Cancel",
width: 310,
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(context).pop,
),
],
),
);
},
);
},
),
),
],
),
),
],
);
}

View file

@ -17,6 +17,7 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart';
import 'package:tuple/tuple.dart';
class DesktopChooseFromStack extends ConsumerStatefulWidget {
const DesktopChooseFromStack({
@ -222,8 +223,18 @@ class _DesktopChooseFromStackState
),
BlueTextButton(
text: "Select wallet",
onTap: () {
Navigator.of(context).pop(manager.walletId);
onTap: () async {
final address =
await manager.currentReceivingAddress;
if (mounted) {
Navigator.of(context).pop(
Tuple2(
manager.walletName,
address,
),
);
}
},
),
],

View file

@ -22,41 +22,54 @@ class SimpleDesktopDialog extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: STextStyles.desktopH3(context),
),
const DesktopDialogCloseButton(),
],
Padding(
padding: const EdgeInsets.only(left: 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: STextStyles.desktopH3(context),
),
const DesktopDialogCloseButton(),
],
),
),
const Spacer(),
Text(
message,
style: STextStyles.desktopTextSmall(context),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
message,
style: STextStyles.desktopTextSmall(context),
),
),
const Spacer(
flex: 2,
),
Row(
children: [
const Spacer(),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Ok",
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(
context,
rootNavigator: true,
).pop,
Padding(
padding: const EdgeInsets.only(
left: 32,
right: 32,
bottom: 32,
),
child: Row(
children: [
const Spacer(),
const SizedBox(
width: 16,
),
),
],
Expanded(
child: PrimaryButton(
label: "Ok",
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(
context,
rootNavigator: true,
).pop,
),
),
],
),
)
],
),

View file

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
class FadeStack extends StatefulWidget {
final int index;
final List<Widget> children;
const FadeStack({
super.key,
required this.index,
required this.children,
});
@override
FadeStackState createState() => FadeStackState();
}
class FadeStackState extends State<FadeStack>
with SingleTickerProviderStateMixin {
late final AnimationController animationController;
@override
void didUpdateWidget(FadeStack oldWidget) {
if (widget.index != oldWidget.index) {
animationController.forward(from: 0.0);
}
super.didUpdateWidget(oldWidget);
}
@override
void initState() {
animationController = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 250,
),
);
animationController.forward();
super.initState();
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: animationController,
child: IndexedStack(
index: widget.index,
children: widget.children,
),
);
}
}