Merge pull request #105 from cypherstack/ui-fixes

UI fixes
This commit is contained in:
julian-CStack 2022-09-30 17:34:12 -06:00 committed by GitHub
commit d6a91b78c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 766 additions and 173 deletions

View file

@ -42,6 +42,11 @@ class _RestoreFromDatePickerState extends State<RestoreFromDatePicker> {
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Restore from...",
hintStyle: STextStyles.fieldLabel(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultSearchIconLeft,
),
suffixIcon: UnconstrainedBox(
child: Row(
children: [

View file

@ -393,13 +393,15 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
padding: const EdgeInsets.all(4),
child: SvgPicture.asset(Assets.svg.pencil,
width: 12,
height: 12,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
padding: const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.pencil,
width: 14,
height: 14,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
const SizedBox(
@ -421,13 +423,15 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
padding: const EdgeInsets.all(4),
child: SvgPicture.asset(Assets.svg.copy,
width: 12,
height: 12,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
padding: const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.copy,
width: 16,
height: 16,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
],

View file

@ -5,6 +5,7 @@ import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/address_book_views/subviews/contact_details_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view.dart';
import 'package:stackwallet/pages/send_view/send_view.dart';
import 'package:stackwallet/providers/exchange/exchange_flow_is_active_state_provider.dart';
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
@ -19,6 +20,9 @@ import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart';
final exchangeFromAddressBookAddressStateProvider =
StateProvider<String>((ref) => "");
class ContactPopUp extends ConsumerWidget {
const ContactPopUp({
Key? key,
@ -268,11 +272,11 @@ class ContactPopUp extends ConsumerWidget {
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
padding: const EdgeInsets.all(4),
padding: const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.copy,
width: 12,
height: 12,
width: 16,
height: 16,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
@ -280,6 +284,45 @@ class ContactPopUp extends ConsumerWidget {
),
],
),
if (isExchangeFlow)
const SizedBox(
width: 6,
),
if (isExchangeFlow)
Column(
children: [
const SizedBox(
height: 2,
),
GestureDetector(
onTap: () {
ref
.read(
exchangeFromAddressBookAddressStateProvider
.state)
.state = e.address;
Navigator.of(context).popUntil(
ModalRoute.withName(
Step2View.routeName));
},
child: RoundedContainer(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
padding:
const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.chevronRight,
width: 16,
height: 16,
color: Theme.of(context)
.extension<
StackColors>()!
.accentColorDark),
),
),
],
),
if (contact.id != "default" &&
hasActiveWallet &&
!isExchangeFlow)

View file

@ -0,0 +1,128 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/utilities/constants.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/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance_future.dart';
import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart';
class ChooseFromStackView extends ConsumerStatefulWidget {
const ChooseFromStackView({
Key? key,
required this.coin,
}) : super(key: key);
final Coin coin;
static const String routeName = "/chooseFromStack";
@override
ConsumerState<ChooseFromStackView> createState() =>
_ChooseFromStackViewState();
}
class _ChooseFromStackViewState extends ConsumerState<ChooseFromStackView> {
late final Coin coin;
@override
void initState() {
coin = widget.coin;
super.initState();
}
@override
Widget build(BuildContext context) {
final walletIds = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getWalletIdsFor(coin: coin)));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: const AppBarBackButton(),
title: Text(
"Choose your ${coin.ticker.toUpperCase()} wallet",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: walletIds.isEmpty
? Column(
children: [
RoundedWhiteContainer(
child: Center(
child: Text(
"No ${coin.ticker.toUpperCase()} wallets",
style: STextStyles.itemSubtitle(context),
),
),
),
],
)
: ListView.builder(
itemCount: walletIds.length,
itemBuilder: (context, index) {
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletIds[index])));
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: RawMaterialButton(
splashColor:
Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
padding: const EdgeInsets.all(0),
// color: Theme.of(context).extension<StackColors>()!.popupBG,
elevation: 0,
onPressed: () async {
if (mounted) {
Navigator.of(context).pop(manager.walletId);
}
},
child: RoundedWhiteContainer(
// color: Colors.transparent,
child: Row(
children: [
WalletInfoCoinIcon(coin: coin),
const SizedBox(
width: 12,
),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
manager.walletName,
style: STextStyles.titleBold12(context),
overflow: TextOverflow.ellipsis,
),
const SizedBox(
height: 2,
),
WalletInfoRowBalanceFuture(
walletId: walletIds[index],
),
],
),
)
],
),
),
),
);
},
),
),
);
}
}

View file

@ -132,6 +132,7 @@ class _ConfirmChangeNowSendViewState
final managerProvider = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManagerProvider(walletId)));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
leading: AppBarBackButton(
@ -327,7 +328,12 @@ class _ConfirmChangeNowSendViewState
children: [
Text(
"Total amount",
style: STextStyles.titleBold12(context),
style:
STextStyles.titleBold12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textConfirmTotalAmount,
),
),
Text(
"${Format.satoshiAmountToPrettyString(
@ -341,7 +347,12 @@ class _ConfirmChangeNowSendViewState
managerProvider
.select((value) => value.coin),
).ticker}",
style: STextStyles.itemSubtitle12(context),
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textConfirmTotalAmount,
),
textAlign: TextAlign.right,
),
],

View file

@ -3,6 +3,8 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/pages/address_book_views/address_book_view.dart';
import 'package:stackwallet/pages/address_book_views/subviews/contact_popup.dart';
import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_3_view.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart';
import 'package:stackwallet/providers/exchange/exchange_flow_is_active_state_provider.dart';
@ -17,6 +19,7 @@ import 'package:stackwallet/utilities/logger.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_buttons/blue_text_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/qrcode_icon.dart';
@ -54,6 +57,15 @@ class _Step2ViewState extends ConsumerState<Step2View> {
late final FocusNode _toFocusNode;
late final FocusNode _refundFocusNode;
bool isStackCoin(String ticker) {
try {
coinFromTickerCaseInsensitive(ticker);
return true;
} on ArgumentError catch (_) {
return false;
}
}
@override
void initState() {
model = widget.model;
@ -74,7 +86,22 @@ class _Step2ViewState extends ConsumerState<Step2View> {
.read(walletsChangeNotifierProvider)
.getManager(tuple.item1)
.currentReceivingAddress
.then((value) => _toController.text = value);
.then((value) {
_toController.text = value;
model.recipientAddress = _toController.text;
});
} else {
if (model.sendTicker.toUpperCase() ==
tuple.item2.ticker.toUpperCase()) {
ref
.read(walletsChangeNotifierProvider)
.getManager(tuple.item1)
.currentReceivingAddress
.then((value) {
_refundController.text = value;
model.refundAddress = _refundController.text;
});
}
}
}
@ -158,15 +185,36 @@ class _Step2ViewState extends ConsumerState<Step2View> {
"Recipient Wallet",
style: STextStyles.smallMed12(context),
),
// GestureDetector(
// onTap: () {
// // TODO: choose from stack?
// },
// child: Text(
// "Choose from Stack",
// style: STextStyles.link2(context),
// ),
// ),
if (isStackCoin(model.receiveTicker))
BlueTextButton(
text: "Choose from stack",
onTap: () {
try {
final coin = coinFromTickerCaseInsensitive(
model.receiveTicker,
);
Navigator.of(context)
.pushNamed(
ChooseFromStackView.routeName,
arguments: coin,
)
.then((value) async {
if (value is String) {
final manager = ref
.read(walletsChangeNotifierProvider)
.getManager(value);
_toController.text = manager.walletName;
model.recipientAddress = await manager
.currentReceivingAddress;
}
});
} catch (e, s) {
Logging.instance
.log("$e\n$s", level: LogLevel.Info);
}
},
),
],
),
const SizedBox(
@ -195,6 +243,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
),
focusNode: _toFocusNode,
style: STextStyles.field(context),
onChanged: (value) {
setState(() {});
},
decoration: standardInputDecoration(
"Enter the ${model.receiveTicker.toUpperCase()} payout address",
_toFocusNode,
@ -221,6 +272,8 @@ class _Step2ViewState extends ConsumerState<Step2View> {
"sendViewClearAddressFieldButtonKey"),
onTap: () {
_toController.text = "";
model.recipientAddress =
_toController.text;
setState(() {});
},
@ -239,6 +292,8 @@ class _Step2ViewState extends ConsumerState<Step2View> {
data.text!.trim();
_toController.text = content;
model.recipientAddress =
_toController.text;
setState(() {});
}
@ -259,13 +314,31 @@ class _Step2ViewState extends ConsumerState<Step2View> {
.state = true;
Navigator.of(context)
.pushNamed(
AddressBookView.routeName,
)
.then((_) => ref
AddressBookView.routeName,
)
.then((_) {
ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = false;
final address = ref
.read(
exchangeFromAddressBookAddressStateProvider
.state)
.state;
if (address.isNotEmpty) {
_toController.text = address;
model.recipientAddress =
_toController.text;
ref
.read(
exchangeFlowIsActiveStateProvider
exchangeFromAddressBookAddressStateProvider
.state)
.state = false);
.state = "";
}
});
},
child: const AddressBookIcon(),
),
@ -299,11 +372,15 @@ class _Step2ViewState extends ConsumerState<Step2View> {
// auto fill address
_toController.text =
results["address"] ?? "";
model.recipientAddress =
_toController.text;
setState(() {});
} else {
_toController.text =
qrResult.rawContent;
model.recipientAddress =
_toController.text;
setState(() {});
}
@ -348,15 +425,37 @@ class _Step2ViewState extends ConsumerState<Step2View> {
"Refund Wallet (required)",
style: STextStyles.smallMed12(context),
),
// GestureDetector(
// onTap: () {
// // TODO: choose from stack?
// },
// child: Text(
// "Choose from Stack",
// style: STextStyles.link2(context),
// ),
// ),
if (isStackCoin(model.sendTicker))
BlueTextButton(
text: "Choose from stack",
onTap: () {
try {
final coin = coinFromTickerCaseInsensitive(
model.sendTicker,
);
Navigator.of(context)
.pushNamed(
ChooseFromStackView.routeName,
arguments: coin,
)
.then((value) async {
if (value is String) {
final manager = ref
.read(walletsChangeNotifierProvider)
.getManager(value);
_refundController.text =
manager.walletName;
model.refundAddress = await manager
.currentReceivingAddress;
}
});
} catch (e, s) {
Logging.instance
.log("$e\n$s", level: LogLevel.Info);
}
},
),
],
),
const SizedBox(
@ -384,6 +483,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
),
focusNode: _refundFocusNode,
style: STextStyles.field(context),
onChanged: (value) {
setState(() {});
},
decoration: standardInputDecoration(
"Enter ${model.sendTicker.toUpperCase()} refund address",
_refundFocusNode,
@ -410,6 +512,8 @@ class _Step2ViewState extends ConsumerState<Step2View> {
"sendViewClearAddressFieldButtonKey"),
onTap: () {
_refundController.text = "";
model.refundAddress =
_refundController.text;
setState(() {});
},
@ -429,6 +533,8 @@ class _Step2ViewState extends ConsumerState<Step2View> {
_refundController.text =
content;
model.refundAddress =
_refundController.text;
setState(() {});
}
@ -450,13 +556,26 @@ class _Step2ViewState extends ConsumerState<Step2View> {
.state = true;
Navigator.of(context)
.pushNamed(
AddressBookView.routeName,
)
.then((_) => ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = false);
AddressBookView.routeName,
)
.then((_) {
ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = false;
final address = ref
.read(
exchangeFromAddressBookAddressStateProvider
.state)
.state;
if (address.isNotEmpty) {
_refundController.text =
address;
model.refundAddress =
_refundController.text;
}
});
},
child: const AddressBookIcon(),
),
@ -490,11 +609,15 @@ class _Step2ViewState extends ConsumerState<Step2View> {
// auto fill address
_refundController.text =
results["address"] ?? "";
model.refundAddress =
_refundController.text;
setState(() {});
} else {
_refundController.text =
qrResult.rawContent;
model.refundAddress =
_refundController.text;
setState(() {});
}
@ -556,9 +679,6 @@ class _Step2ViewState extends ConsumerState<Step2View> {
Expanded(
child: TextButton(
onPressed: () {
model.recipientAddress = _toController.text;
model.refundAddress = _refundController.text;
Navigator.of(context).pushNamed(
Step3View.routeName,
arguments: model);

View file

@ -16,6 +16,7 @@ import 'package:stackwallet/utilities/clipboard_interface.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/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
@ -222,6 +223,26 @@ class _Step3ViewState extends ConsumerState<Step3View> {
Expanded(
child: TextButton(
onPressed: () 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,
),
),
),
),
);
ChangeNowResponse<ExchangeTransaction>
response;
if (model.rateType ==
@ -251,6 +272,10 @@ class _Step3ViewState extends ConsumerState<Step3View> {
}
if (response.value == null) {
if (mounted) {
Navigator.of(context).pop();
}
unawaited(showDialog<void>(
context: context,
barrierDismissible: true,
@ -273,8 +298,6 @@ class _Step3ViewState extends ConsumerState<Step3View> {
.getTransactionStatus(
id: response.value!.id);
debugPrint("WTF: $statusResponse");
String status = "Waiting";
if (statusResponse.value != null) {
status = statusResponse.value!.status.name;
@ -290,6 +313,10 @@ class _Step3ViewState extends ConsumerState<Step3View> {
status += " for deposit";
}
if (mounted) {
Navigator.of(context).pop();
}
unawaited(NotificationApi.showNotification(
changeNowId: model.trade!.id,
title: status,

View file

@ -232,7 +232,7 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
? ref.read(estimatedRateExchangeFormProvider).fromAmountString
: ref.read(fixedRateExchangeFormProvider).fromAmountString;
_receiveController.text = isEstimated
? ref.read(estimatedRateExchangeFormProvider).toAmountString
? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString
: ref.read(fixedRateExchangeFormProvider).toAmountString;
_sendFocusNode.addListener(() async {
@ -325,7 +325,7 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
: fixedRateExchangeFormProvider.select(
(value) => value.toAmountString), (previous, String next) {
if (!_receiveFocusNode.hasFocus) {
_receiveController.text = next;
_receiveController.text = isEstimated ? "-" : next;
debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED");
if (_swapLock) {
_sendController.text = isEstimated
@ -345,7 +345,7 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
debugPrint("SEND AMOUNT LISTENER ACTIVATED");
if (_swapLock) {
_receiveController.text = isEstimated
? ref.read(estimatedRateExchangeFormProvider).toAmountString
? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString
: ref.read(fixedRateExchangeFormProvider).toAmountString;
}
}
@ -737,7 +737,7 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
.exchangeRateType ==
ExchangeRateType.estimated,
onTap: () {
if (_receiveController.text == "-") {
if (!isEstimated && _receiveController.text == "-") {
_receiveController.text = "";
}
},

View file

@ -48,6 +48,26 @@ class _SendFromViewState extends ConsumerState<SendFromView> {
late final String address;
late final ExchangeTransaction trade;
String formatAmount(Decimal amount, Coin coin) {
switch (coin) {
case Coin.bitcoin:
case Coin.bitcoincash:
case Coin.dogecoin:
case Coin.epicCash:
case Coin.firo:
case Coin.namecoin:
case Coin.bitcoinTestNet:
case Coin.bitcoincashTestnet:
case Coin.dogecoinTestNet:
case Coin.firoTestNet:
return amount.toStringAsFixed(Constants.decimalPlaces);
case Coin.monero:
return amount.toStringAsFixed(Constants.decimalPlacesMonero);
case Coin.wownero:
return amount.toStringAsFixed(Constants.decimalPlacesWownero);
}
}
@override
void initState() {
coin = widget.coin;
@ -59,6 +79,11 @@ class _SendFromViewState extends ConsumerState<SendFromView> {
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
final walletIds = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getWalletIdsFor(coin: coin)));
return Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
@ -68,44 +93,41 @@ class _SendFromViewState extends ConsumerState<SendFromView> {
},
),
title: Text(
"Send ",
"Send from",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Wrap(
// crossAxisAlignment: CrossAxisAlignment.stretch,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Choose your ${coin.ticker} wallet",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 8,
),
Text(
"You need to send ${amount.toStringAsFixed(coin == Coin.monero ? Constants.satsPerCoinMonero : coin == Coin.wownero ? Constants.satsPerCoinWownero : Constants.satsPerCoin)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
Row(
children: [
Text(
"You need to send ${formatAmount(amount, coin)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
),
],
),
const SizedBox(
height: 16,
),
ListView(
shrinkWrap: true,
children: [
...ref
.watch(walletsChangeNotifierProvider
.select((value) => value.managers))
.where((element) => element.coin == coin)
.map((e) => SendFromCard(
walletId: e.walletId,
amount: amount,
address: address,
trade: trade,
))
.toList(growable: false)
],
Expanded(
child: ListView.builder(
itemCount: walletIds.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: SendFromCard(
walletId: walletIds[index],
amount: amount,
address: address,
trade: trade,
),
);
},
),
),
],
),
@ -163,7 +185,7 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
child: MaterialButton(
splashColor: Theme.of(context).extension<StackColors>()!.highlight,
key: Key("walletsSheetItemButtonKey_$walletId"),
padding: const EdgeInsets.all(5),
padding: const EdgeInsets.all(8),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
@ -276,59 +298,61 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
),
),
child: Padding(
padding: const EdgeInsets.all(4),
padding: const EdgeInsets.all(6),
child: SvgPicture.asset(
Assets.svg.iconFor(coin: coin),
width: 20,
height: 20,
width: 24,
height: 24,
),
),
),
const SizedBox(
width: 12,
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
manager.walletName,
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 2,
),
FutureBuilder(
future: manager.totalBalance,
builder: (builderContext, AsyncSnapshot<Decimal> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return Text(
"${Format.localizedStringAsFixed(
value: snapshot.data!,
locale: locale,
decimalPlaces: coin == Coin.monero
? Constants.satsPerCoinMonero
: coin == Coin.wownero
? Constants.satsPerCoinWownero
: Constants.satsPerCoin,
)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
);
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading balance",
"Loading balance.",
"Loading balance..",
"Loading balance..."
],
style: STextStyles.itemSubtitle(context),
);
}
},
),
],
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
manager.walletName,
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 2,
),
FutureBuilder(
future: manager.totalBalance,
builder: (builderContext, AsyncSnapshot<Decimal> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return Text(
"${Format.localizedStringAsFixed(
value: snapshot.data!,
locale: locale,
decimalPlaces: coin == Coin.monero
? Constants.decimalPlacesMonero
: coin == Coin.wownero
? Constants.decimalPlacesWownero
: Constants.decimalPlaces,
)} ${coin.ticker}",
style: STextStyles.itemSubtitle(context),
);
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading balance",
"Loading balance.",
"Loading balance..",
"Loading balance..."
],
style: STextStyles.itemSubtitle(context),
);
}
},
),
],
),
),
],
),

View file

@ -10,6 +10,7 @@ import 'package:stackwallet/models/exchange/change_now/exchange_transaction_stat
import 'package:stackwallet/models/paymint/transactions_model.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart';
import 'package:stackwallet/pages/exchange_view/send_from_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/providers/exchange/change_now_provider.dart';
@ -24,6 +25,7 @@ import 'package:stackwallet/utilities/format.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/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
@ -60,6 +62,15 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
String _note = "";
bool isStackCoin(String ticker) {
try {
coinFromTickerCaseInsensitive(ticker);
return true;
} on ArgumentError catch (_) {
return false;
}
}
@override
initState() {
tradeId = widget.tradeId;
@ -345,9 +356,48 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Send ${trade.fromCurrency.toUpperCase()} to this address",
style: STextStyles.itemSubtitle(context),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Send ${trade.fromCurrency.toUpperCase()} to this address",
style: STextStyles.itemSubtitle(context),
),
GestureDetector(
onTap: () async {
final address = trade.payinAddress;
await Clipboard.setData(
ClipboardData(
text: address,
),
);
unawaited(showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
context: context,
));
},
child: Row(
children: [
SvgPicture.asset(
Assets.svg.copy,
width: 12,
height: 12,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
const SizedBox(
width: 4,
),
Text(
"Copy",
style: STextStyles.link2(context),
),
],
),
),
],
),
const SizedBox(
height: 4,
@ -717,6 +767,32 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
const SizedBox(
height: 12,
),
if (isStackCoin(trade.fromCurrency) &&
trade.statusObject != null &&
(trade.statusObject!.status ==
ChangeNowTransactionStatus.New ||
trade.statusObject!.status ==
ChangeNowTransactionStatus.Waiting))
SecondaryButton(
label: "Send from Stack",
onPressed: () {
final amount = sendAmount;
final address = trade.payinAddress;
final coin =
coinFromTickerCaseInsensitive(trade.fromCurrency);
Navigator.of(context).pushNamed(
SendFromView.routeName,
arguments: Tuple4(
coin,
amount,
address,
trade,
),
);
},
),
],
),
),

View file

@ -83,8 +83,8 @@ class _WalletInitiatedExchangeViewState
child: Container(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark
.withOpacity(0.8),
.overlay
.withOpacity(0.6),
child: const CustomLoadingOverlay(
message: "Updating exchange rate",
eventBus: null,
@ -329,7 +329,7 @@ class _WalletInitiatedExchangeViewState
: fixedRateExchangeFormProvider.select(
(value) => value.toAmountString), (previous, String next) {
if (!_receiveFocusNode.hasFocus) {
_receiveController.text = next;
_receiveController.text = isEstimated ? "-" : next;
debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED");
if (_swapLock) {
_sendController.text = isEstimated
@ -349,7 +349,7 @@ class _WalletInitiatedExchangeViewState
debugPrint("SEND AMOUNT LISTENER ACTIVATED");
if (_swapLock) {
_receiveController.text = isEstimated
? ref.read(estimatedRateExchangeFormProvider).toAmountString
? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString
: ref.read(fixedRateExchangeFormProvider).toAmountString;
}
}
@ -808,7 +808,8 @@ class _WalletInitiatedExchangeViewState
.exchangeRateType ==
ExchangeRateType.estimated,
onTap: () {
if (_receiveController.text == "-") {
if (!isEstimated &&
_receiveController.text == "-") {
_receiveController.text = "";
}
},

View file

@ -209,6 +209,10 @@ abstract class SWB {
Logging.instance.log(
"...createStackWalletJSON DB.instance.mutex acquired",
level: LogLevel.Info);
Logging.instance.log(
"SWB backing up nodes",
level: LogLevel.Warning,
);
try {
var primaryNodes = nodeService.primaryNodes.map((e) async {
final map = e.toMap();
@ -231,6 +235,11 @@ abstract class SWB {
Logging.instance.log("$e $s", level: LogLevel.Error);
}
Logging.instance.log(
"SWB backing up prefs",
level: LogLevel.Warning,
);
Map<String, dynamic> prefs = {};
final _prefs = Prefs.instance;
await _prefs.init();
@ -251,11 +260,21 @@ abstract class SWB {
backupJson['prefs'] = prefs;
Logging.instance.log(
"SWB backing up addressbook",
level: LogLevel.Warning,
);
AddressBookService addressBookService = AddressBookService();
var addresses = await addressBookService.addressBookEntries;
backupJson['addressBookEntries'] =
addresses.map((e) => e.toMap()).toList();
Logging.instance.log(
"SWB backing up wallets",
level: LogLevel.Warning,
);
List<dynamic> backupWallets = [];
for (var manager in _wallets.managers) {
Map<String, dynamic> backupWallet = {};
@ -283,6 +302,11 @@ abstract class SWB {
}
backupJson['wallets'] = backupWallets;
Logging.instance.log(
"SWB backing up trades",
level: LogLevel.Warning,
);
// back up trade history
final tradesService = TradesService();
final trades =
@ -295,6 +319,11 @@ abstract class SWB {
tradeTxidLookupDataService.all.map((e) => e.toMap()).toList();
backupJson["tradeTxidLookupData"] = lookupData;
Logging.instance.log(
"SWB backing up trade notes",
level: LogLevel.Warning,
);
// back up trade notes
final tradeNotesService = TradeNotesService();
final tradeNotes = tradeNotesService.all;
@ -357,7 +386,7 @@ abstract class SWB {
final notes = walletbackup["notes"] as Map?;
if (notes != null) {
for (final note in notes.entries) {
notesService.editOrAddNote(
await notesService.editOrAddNote(
txid: note.key as String, note: note.value as String);
}
}
@ -432,11 +461,19 @@ abstract class SWB {
uiState?.preferences = StackRestoringStatus.restoring;
Logging.instance.log(
"SWB restoring prefs",
level: LogLevel.Warning,
);
await _restorePrefs(prefs);
uiState?.preferences = StackRestoringStatus.success;
uiState?.addressBook = StackRestoringStatus.restoring;
Logging.instance.log(
"SWB restoring addressbook",
level: LogLevel.Warning,
);
if (addressBookEntries != null) {
await _restoreAddressBook(addressBookEntries);
}
@ -444,6 +481,10 @@ abstract class SWB {
uiState?.addressBook = StackRestoringStatus.success;
uiState?.nodes = StackRestoringStatus.restoring;
Logging.instance.log(
"SWB restoring nodes",
level: LogLevel.Warning,
);
await _restoreNodes(nodes, primaryNodes);
uiState?.nodes = StackRestoringStatus.success;
@ -451,17 +492,29 @@ abstract class SWB {
// restore trade history
if (trades != null) {
Logging.instance.log(
"SWB restoring trades",
level: LogLevel.Warning,
);
await _restoreTrades(trades);
}
// restore trade history lookup data for trades send from stack wallet
if (tradeTxidLookupData != null) {
Logging.instance.log(
"SWB restoring trade look up data",
level: LogLevel.Warning,
);
await _restoreTradesLookUpData(tradeTxidLookupData, oldToNewWalletIdMap);
}
// restore trade notes
if (tradeNotes != null) {
Logging.instance.log(
"SWB restoring trade notes",
level: LogLevel.Warning,
);
await _restoreTradesNotes(tradeNotes);
}
@ -490,9 +543,17 @@ abstract class SWB {
String jsonBackup,
StackRestoringUIState? uiState,
) async {
if (!Platform.isLinux) Wakelock.enable();
if (!Platform.isLinux) await Wakelock.enable();
Logging.instance.log(
"SWB creating temp backup",
level: LogLevel.Warning,
);
final preRestoreJSON = await createStackWalletJSON();
Logging.instance.log(
"SWB temp backup created",
level: LogLevel.Warning,
);
List<String> _currentWalletIds = Map<String, dynamic>.from(DB.instance
.get<dynamic>(
@ -814,13 +875,13 @@ abstract class SWB {
}
await asyncRestore(epicCashWallets[i], uiState, walletsService);
}
if (!Platform.isLinux) Wakelock.disable();
if (!Platform.isLinux) await Wakelock.disable();
// check if cancel was requested and restore previous state
if (_checkShouldCancel(preRestoreState)) {
return false;
}
Logging.instance.log("done with SWB restore", level: LogLevel.Info);
Logging.instance.log("done with SWB restore", level: LogLevel.Warning);
return true;
}
@ -849,7 +910,7 @@ abstract class SWB {
// if no contacts were present before attempted restore then delete any that
// could have been added before the restore was cancelled
for (final String idToDelete in allContactIds) {
addressBookService.removeContact(idToDelete);
await addressBookService.removeContact(idToDelete);
}
} else {
final Map<String, dynamic> preContactMap = {};
@ -886,7 +947,7 @@ abstract class SWB {
);
} else {
// otherwise remove it as it was not there before attempting SWB restore
addressBookService.removeContact(id);
await addressBookService.removeContact(id);
}
}
}
@ -898,7 +959,7 @@ abstract class SWB {
// no pre nodes found so we delete all but defaults
for (final node in currentNodes) {
if (!node.isDefault) {
nodeService.delete(node.id, true);
await nodeService.delete(node.id, true);
}
}
} else {
@ -912,7 +973,7 @@ abstract class SWB {
if (nodeData != null) {
// node existed before restore attempt
// revert to pre restore node
nodeService.edit(
await nodeService.edit(
node.copyWith(
host: nodeData['host'] as String,
port: nodeData['port'] as int,
@ -927,7 +988,7 @@ abstract class SWB {
nodeData['password'] as String?,
true);
} else {
nodeService.delete(node.id, true);
await nodeService.delete(node.id, true);
}
}
}
@ -951,7 +1012,7 @@ abstract class SWB {
// no trade history found pre restore attempt so we delete anything that
// was added during the restore attempt
for (final tradeTx in currentTrades) {
tradesService.delete(trade: tradeTx, shouldNotifyListeners: true);
await tradesService.delete(trade: tradeTx, shouldNotifyListeners: true);
}
} else {
final Map<String, dynamic> preTradeMap = {};
@ -964,13 +1025,14 @@ abstract class SWB {
if (tradeData != null) {
// trade existed before attempted restore so we don't delete it, only
// revert data to pre restore state
tradesService.edit(
await tradesService.edit(
trade: ExchangeTransaction.fromJson(
tradeData as Map<String, dynamic>),
shouldNotifyListeners: true);
} else {
// trade did not exist before so we delete it
tradesService.delete(trade: tradeTx, shouldNotifyListeners: true);
await tradesService.delete(
trade: tradeTx, shouldNotifyListeners: true);
}
}
}
@ -982,7 +1044,7 @@ abstract class SWB {
if (tradeNotes == null) {
for (final noteEntry in currentNotes.entries) {
tradeNotesService.delete(tradeId: noteEntry.key);
await tradeNotesService.delete(tradeId: noteEntry.key);
}
} else {
// grab all trade IDs of (reverted to pre state) trades
@ -991,7 +1053,7 @@ abstract class SWB {
// delete all notes that don't correspond to an id that we have
for (final noteEntry in currentNotes.entries) {
if (!idsToKeep.contains(noteEntry.key)) {
tradeNotesService.delete(tradeId: noteEntry.key);
await tradeNotesService.delete(tradeId: noteEntry.key);
}
}
}
@ -1009,7 +1071,7 @@ abstract class SWB {
for (int i = 0; i < tradeTxidLookupData.length; i++) {
final json = Map<String, dynamic>.from(tradeTxidLookupData[i] as Map);
TradeWalletLookup lookup = TradeWalletLookup.fromJson(json);
tradeTxidLookupDataService.save(tradeWalletLookup: lookup);
await tradeTxidLookupDataService.save(tradeWalletLookup: lookup);
}
}
@ -1127,14 +1189,14 @@ abstract class SWB {
) async {
final tradesService = TradesService();
for (int i = 0; i < trades.length - 1; i++) {
tradesService.add(
await tradesService.add(
trade: ExchangeTransaction.fromJson(trades[i] as Map<String, dynamic>),
shouldNotifyListeners: false,
);
}
// only call notifyListeners on last one added
if (trades.isNotEmpty) {
tradesService.add(
await tradesService.add(
trade:
ExchangeTransaction.fromJson(trades.last as Map<String, dynamic>),
shouldNotifyListeners: true,
@ -1177,7 +1239,7 @@ abstract class SWB {
}
}
tradeTxidLookupDataService.save(tradeWalletLookup: lookup);
await tradeTxidLookupDataService.save(tradeWalletLookup: lookup);
}
}
@ -1186,7 +1248,8 @@ abstract class SWB {
) async {
final tradeNotesService = TradeNotesService();
for (final note in tradeNotes.entries) {
tradeNotesService.set(tradeId: note.key, note: note.value as String);
await tradeNotesService.set(
tradeId: note.key, note: note.value as String);
}
}
}

View file

@ -9,7 +9,12 @@ import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/trade_card.dart';
import 'package:stackwallet/widgets/transaction_card.dart';
import 'package:tuple/tuple.dart';
import '../../../providers/global/trades_service_provider.dart';
import '../../exchange_view/trade_details_view.dart';
class TransactionsList extends ConsumerStatefulWidget {
const TransactionsList({
@ -125,18 +130,67 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
radius = _borderRadiusFirst;
}
final tx = list[index];
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: TransactionCard(
// this may mess with combined firo transactions
key: Key(tx.toString()), //
transaction: tx,
walletId: widget.walletId,
),
);
final matchingTrades = ref
.read(tradesServiceProvider)
.trades
.where((e) =>
e.statusObject != null &&
(e.statusObject!.payinHash == tx.txid ||
e.statusObject!.payoutHash == tx.txid));
if (tx.txType == "Sent" && matchingTrades.isNotEmpty) {
final trade = matchingTrades.first;
return Container(
decoration: BoxDecoration(
color:
Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TransactionCard(
// this may mess with combined firo transactions
key: Key(tx.toString()), //
transaction: tx,
walletId: widget.walletId,
),
TradeCard(
// this may mess with combined firo transactions
key: Key(tx.toString() + trade.uuid), //
trade: trade,
onTap: () {
unawaited(
Navigator.of(context).pushNamed(
TradeDetailsView.routeName,
arguments: Tuple4(
trade.id,
tx,
widget.walletId,
ref.read(managerProvider).walletName,
),
),
);
},
)
],
),
);
} else {
return Container(
decoration: BoxDecoration(
color:
Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: TransactionCard(
// this may mess with combined firo transactions
key: Key(tx.toString()), //
transaction: tx,
walletId: widget.walletId,
),
);
}
},
),
);

View file

@ -1,7 +1,9 @@
import 'package:decimal/decimal.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/contact_address_entry.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/models/paymint/transactions_model.dart';
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
@ -20,12 +22,14 @@ import 'package:stackwallet/pages/address_book_views/subviews/address_book_filte
import 'package:stackwallet/pages/address_book_views/subviews/contact_details_view.dart';
import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_address_view.dart';
import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart';
import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart';
import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_loading_overlay.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_1_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_3_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.dart';
import 'package:stackwallet/pages/exchange_view/send_from_view.dart';
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
@ -879,6 +883,37 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case ChooseFromStackView.routeName:
if (args is Coin) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => ChooseFromStackView(
coin: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case SendFromView.routeName:
if (args is Tuple4<Coin, Decimal, String, ExchangeTransaction>) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => SendFromView(
coin: args.item1,
amount: args.item2,
trade: args.item4,
address: args.item3,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
// == Desktop specific routes ============================================
case CreatePasswordView.routeName:
return getRoute(

View file

@ -51,7 +51,7 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
const int MINIMUM_CONFIRMATIONS = 10;
const int MINIMUM_CONFIRMATIONS = 4;
//https://github.com/wownero-project/wownero/blob/8361d60aef6e17908658128284899e3a11d808d4/src/cryptonote_config.h#L162
const String GENESIS_HASH_MAINNET =

View file

@ -21,6 +21,8 @@ abstract class Constants {
static const int satsPerCoinWownero = 100000000000;
static const int satsPerCoin = 100000000;
static const int decimalPlaces = 8;
static const int decimalPlacesWownero = 11;
static const int decimalPlacesMonero = 12;
static const int notificationsMax = 0xFFFFFFFF;
static const Duration networkAliveTimerDuration = Duration(seconds: 10);