Merge pull request #120 from cypherstack/updatedesktop

add wownero and fix some building problems for linux
This commit is contained in:
Marco Salazar 2022-10-04 13:38:49 -06:00 committed by GitHub
commit 94c606f6c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 3095 additions and 449 deletions

View file

@ -54,12 +54,31 @@ jobs:
Write-Output "::set-output name=SECRET_FILE_FIRO::$secretFileFiro";
Write-Output "::set-output name=SECRET_FILE_FIRO_HASH::$($secretFileFiroHash.Hash)";
Write-Output "Secret file $secretFileFiro has hash $($secretFileFiroHash.Hash)";
$secretFileBitcoinCash = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart";
$encodedBytes = [System.Convert]::FromBase64String($env:BITCOINCASH_TEST);
Set-Content $secretFileBitcoinCash -Value $encodedBytes -AsByteStream;
$secretFileBitcoinCashHash = Get-FileHash $secretFileBitcoinCash;
Write-Output "::set-output name=SECRET_FILE_BITCOINCASH::$secretFileBitcoinCash";
Write-Output "::set-output name=SECRET_FILE_BITCOINCASH_HASH::$($secretFileBitcoinCashHash.Hash)";
Write-Output "Secret file $secretFileBitcoinCash has hash $($secretFileBitcoinCashHash.Hash)";
$secretFileNamecoin = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/namecoin/namecoin_wallet_test_parameters.dart";
$encodedBytes = [System.Convert]::FromBase64String($env:NAMECOIN_TEST);
Set-Content $secretFileNamecoin -Value $encodedBytes -AsByteStream;
$secretFileNamecoinHash = Get-FileHash $secretFileNamecoin;
Write-Output "::set-output name=SECRET_FILE_NAMECOIN::$secretFileNamecoin";
Write-Output "::set-output name=SECRET_FILE_NAMECOIN_HASH::$($secretFileNamecoinHash.Hash)";
Write-Output "Secret file $secretFileNamecoin has hash $($secretFileNamecoinHash.Hash)";
shell: pwsh
env:
CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
BITCOIN_TEST: ${{ secrets.BITCOIN_TEST }}
DOGECOIN_TEST: ${{ secrets.DOGECOIN_TEST }}
FIRO_TEST: ${{ secrets.FIRO_TEST }}
BITCOINCASH_TEST: ${{ secrets.BITCOINCASH_TEST }}
NAMECOIN_TEST: ${{ secrets.NAMECOIN_TEST }}
# - name: Analyze
# run: flutter analyze
- name: Test
@ -76,6 +95,8 @@ jobs:
Remove-Item -Path $env:BITCOIN_TEST;
Remove-Item -Path $env:DOGECOIN_TEST;
Remove-Item -Path $env:FIRO_TEST;
Remove-Item -Path $env:BITCOINCASH_TEST;
Remove-Item -Path $env:NAMECOIN_TEST;
shell: pwsh
if: always()
env:
@ -83,3 +104,5 @@ jobs:
BITCOIN_TEST: ${{ steps.secret-file1.outputs.SECRET_FILE_BITCOIN }}
DOGECOIN_TEST: ${{ steps.secret-file1.outputs.SECRET_FILE_DOGECOIN }}
FIRO_TEST: ${{ steps.secret-file1.outputs.SECRET_FILE_FIRO }}
BITCOINCASH_TEST: ${{ steps.secret-file1.outputs.SECRET_FILE_BITCOINCASH }}
NAMECOIN_TEST: ${{ steps.secret-file1.outputs.SECRET_FILE_NAMECOIN }}

View file

@ -32,7 +32,10 @@ cd stack_wallet
git submodule update --init --recursive
```
You will need to install all dependencies listed in each of the plugins in the crypto_plugins folder. (eg. [Monero](https://github.com/cypherstack/flutter_libmonero), [Epic Cash](https://github.com/cypherstack/flutter_libepiccash) ) as of Sep 8th 2022 that is:
Install all dependencies listed in each of the plugins in the crypto_plugins folder (eg. [flutter_libmonero](https://github.com/cypherstack/flutter_libmonero/blob/main/howto-build-android.md), [flutter_libepiccash](https://github.com/cypherstack/flutter_libepiccash) ) as of Oct 3rd 2022 that is:
```
sudo apt-get install unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm sudo apt-get install debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev
```
Install [Rust](https://www.rust-lang.org/tools/install)
```
@ -45,6 +48,26 @@ sudo apt install build-essential debhelper cmake libclang-dev libncurses5-dev cl
sudo apt install unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless
```
Run prebuild script
```
cd scripts
./prebuild.sh
// when finished go back to the root directory
cd ..
```
Remove pre-installed system libraries for the following packages built by cryptography plugins in the crypto_plugins folder: `boost iconv libjson-dev libsecret openssl sodium unbound zmq`. You can use
```
sudo apt list --installed | grep boost
```
for example to find which pre-installed packages you may need to remove with `sudo apt remove`. Be careful, as some packages (especially boost) are linked to GNOME (GUI) packages: when in doubt, remove `-dev` packages first like with
```
sudo apt-get remove '^libboost.*-dev.*'
```
<!-- TODO: configure compiler to prefer built over system libraries -->
Building plugins for Android
```
cd scripts/android/

View file

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 23.8456C18.5421 23.8456 23.8456 18.5421 23.8456 12C23.8456 5.45778 18.5421 0.154297 12 0.154297C5.45778 0.154297 0.154297 5.45778 0.154297 12C0.154297 18.5421 5.45778 23.8456 12 23.8456Z" fill="white"/>
<path d="M12 24C5.38264 24 0 18.6174 0 12C0 5.38264 5.38264 0 12 0C18.6174 0 24 5.38264 24 12C24 18.6174 18.6174 24 12 24ZM12 0.327974C5.55627 0.327974 0.327974 5.57556 0.327974 12C0.327974 18.4244 5.57556 23.672 12 23.672C18.4244 23.672 23.672 18.4244 23.672 12C23.672 5.57556 18.4437 0.327974 12 0.327974Z" fill="#D0509D"/>
<path d="M11.9995 0.154297C6.75195 0.154297 2.29536 3.58838 0.751953 8.31507H6.73266V15.7427L11.961 10.0707H12.0381L17.2664 15.762V8.31507H23.2471C21.7037 3.58838 17.2471 0.154297 11.9995 0.154297Z" fill="#FFCD05"/>
<path d="M6.56007 16.1672V8.48875H0.521484L0.598655 8.27653C2.19994 3.31833 6.79158 0 12.0006 0C17.2096 0 21.8012 3.31833 23.4025 8.27653L23.4797 8.48875H17.4411V16.1672L12.0006 10.283L6.56007 16.1672ZM11.9041 9.9164H12.1163L17.1131 15.3376V8.16077H23.036C21.3961 3.47267 16.9974 0.327974 12.0006 0.327974C7.0038 0.327974 2.60509 3.47267 0.965214 8.16077H6.88804V15.3376L11.9041 9.9164Z" fill="#D0509D"/>
<path d="M18.2473 18.2508L16.5495 16.418L11.9965 11.4791L7.4434 16.418L5.74565 18.2508V15.762V9.5498H1.59774C1.40482 10.3601 1.28906 11.209 1.28906 12.0964C1.28906 18 6.07363 22.8038 11.9965 22.8038C17.9193 22.8038 22.7039 18.0193 22.7039 12.0964C22.7039 11.2283 22.5881 10.3794 22.3952 9.5498H18.228V15.762V18.2508H18.2473Z" fill="#D0519D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -1 +1 @@
Subproject commit 4bffa40cb60ad3d98cf0ea5b5d819f3f4895dcd6
Subproject commit af5740e590cbd4d7a7c86592986288d9d568e23b

View file

@ -449,7 +449,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 51;
CURRENT_PROJECT_VERSION = 63;
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -503,7 +503,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
MARKETING_VERSION = 1.4.39;
MARKETING_VERSION = 1.4.48;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -633,7 +633,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 51;
CURRENT_PROJECT_VERSION = 63;
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -687,7 +687,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
MARKETING_VERSION = 1.4.39;
MARKETING_VERSION = 1.4.48;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -709,7 +709,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 51;
CURRENT_PROJECT_VERSION = 63;
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -763,7 +763,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
MARKETING_VERSION = 1.4.39;
MARKETING_VERSION = 1.4.48;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";

View file

@ -8,6 +8,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_libmonero/monero/monero.dart';
import 'package:flutter_libmonero/wownero/wownero.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:hive_flutter/hive_flutter.dart';
@ -83,8 +84,11 @@ void main() async {
if (Platform.isIOS) {
appDirectory = (await getLibraryDirectory());
}
if (Platform.isLinux || Logging.isArmLinux) {
appDirectory = Directory("${appDirectory.path}/.stackwallet");
await appDirectory.create();
}
// FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
await Hive.initFlutter(appDirectory.path);
if (!(Logging.isArmLinux || Logging.isTestEnv)) {
final isar = await Isar.open(
[LogSchema],
@ -128,11 +132,14 @@ void main() async {
Hive.registerAdapter(NodeAdapter());
Hive.registerAdapter(WalletInfoAdapter());
if (!Hive.isAdapterRegistered(WalletInfoAdapter().typeId)) {
Hive.registerAdapter(WalletInfoAdapter());
}
Hive.registerAdapter(WalletTypeAdapter());
Hive.registerAdapter(UnspentCoinsInfoAdapter());
await Hive.initFlutter(appDirectory.path);
await Hive.openBox<dynamic>(DB.boxNameDBInfo);
int dbVersion = DB.instance.get<dynamic>(
@ -143,6 +150,7 @@ void main() async {
}
monero.onStartup();
wownero.onStartup();
await Hive.openBox<dynamic>(DB.boxNameTheme);

View file

@ -59,7 +59,9 @@ class _NewWalletRecoveryPhraseWarningViewState
final _numberOfPhraseWords = coin == Coin.monero
? Constants.seedPhraseWordCountMonero
: Constants.seedPhraseWordCountBip39;
: coin == Coin.wownero
? 14
: Constants.seedPhraseWordCountBip39;
return MasterScaffold(
isDesktop: isDesktop,

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

@ -200,7 +200,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
// TODO: do actual check to make sure it is a valid mnemonic for monero
if (bip39.validateMnemonic(mnemonic) == false &&
!(widget.coin == Coin.monero)) {
!(widget.coin == Coin.monero || widget.coin == Coin.wownero)) {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
message: "Invalid seed phrase!",

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,
@ -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

@ -328,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(
@ -342,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 {
@ -260,7 +260,11 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
.read(fixedRateExchangeFormProvider)
.setFromAmountAndCalculateToAmount(Decimal.zero, true);
}
_receiveController.text = "";
_receiveController.text =
ref.read(prefsChangeNotifierProvider).exchangeRateType ==
ExchangeRateType.estimated
? "-"
: "";
}
}
});
@ -325,7 +329,7 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
: fixedRateExchangeFormProvider.select(
(value) => value.toAmountString), (previous, String next) {
if (!_receiveFocusNode.hasFocus) {
_receiveController.text = next;
_receiveController.text = isEstimated && next.isEmpty ? "-" : next;
debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED");
if (_swapLock) {
_sendController.text = isEstimated
@ -345,7 +349,12 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
debugPrint("SEND AMOUNT LISTENER ACTIVATED");
if (_swapLock) {
_receiveController.text = isEstimated
? ref.read(estimatedRateExchangeFormProvider).toAmountString
? ref
.read(estimatedRateExchangeFormProvider)
.toAmountString
.isEmpty
? "-"
: ref.read(estimatedRateExchangeFormProvider).toAmountString
: ref.read(fixedRateExchangeFormProvider).toAmountString;
}
}
@ -424,7 +433,7 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
.setFromAmountAndCalculateToAmount(
Decimal.zero, false);
}
_receiveController.text = "";
_receiveController.text = isEstimated ? "-" : "";
}
},
keyboardType: const TextInputType.numberWithOptions(
@ -737,7 +746,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 ? 12 : 8)} ${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,55 +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 ? 12 : 8,
)} ${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,
@ -271,7 +271,11 @@ class _WalletInitiatedExchangeViewState
.read(fixedRateExchangeFormProvider)
.setFromAmountAndCalculateToAmount(Decimal.zero, true);
}
_receiveController.text = "";
_receiveController.text =
ref.read(prefsChangeNotifierProvider).exchangeRateType ==
ExchangeRateType.estimated
? "-"
: "";
}
}
});
@ -329,7 +333,7 @@ class _WalletInitiatedExchangeViewState
: fixedRateExchangeFormProvider.select(
(value) => value.toAmountString), (previous, String next) {
if (!_receiveFocusNode.hasFocus) {
_receiveController.text = next;
_receiveController.text = isEstimated && next.isEmpty ? "-" : next;
debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED");
if (_swapLock) {
_sendController.text = isEstimated
@ -349,7 +353,12 @@ class _WalletInitiatedExchangeViewState
debugPrint("SEND AMOUNT LISTENER ACTIVATED");
if (_swapLock) {
_receiveController.text = isEstimated
? ref.read(estimatedRateExchangeFormProvider).toAmountString
? ref
.read(estimatedRateExchangeFormProvider)
.toAmountString
.isEmpty
? "-"
: ref.read(estimatedRateExchangeFormProvider).toAmountString
: ref.read(fixedRateExchangeFormProvider).toAmountString;
}
}
@ -469,7 +478,7 @@ class _WalletInitiatedExchangeViewState
.setFromAmountAndCalculateToAmount(
Decimal.zero, false);
}
_receiveController.text = "";
_receiveController.text = isEstimated ? "-" : "";
}
},
keyboardType: const TextInputType.numberWithOptions(
@ -808,7 +817,8 @@ class _WalletInitiatedExchangeViewState
.exchangeRateType ==
ExchangeRateType.estimated,
onTap: () {
if (_receiveController.text == "-") {
if (!isEstimated &&
_receiveController.text == "-") {
_receiveController.text = "";
}
},
@ -1280,23 +1290,23 @@ class _WalletInitiatedExchangeViewState
.exchangeRateType ==
ExchangeRateType.estimated;
final ft = isEstimated
? ref
.read(
estimatedRateExchangeFormProvider)
.from
?.ticker ??
""
: ref
.read(
fixedRateExchangeFormProvider)
.market
?.from ??
"";
final manager = ref
.read(walletsChangeNotifierProvider)
.getManager(walletId);
// final ft = isEstimated
// ? ref
// .read(
// estimatedRateExchangeFormProvider)
// .from
// ?.ticker ??
// ""
// : ref
// .read(
// fixedRateExchangeFormProvider)
// .market
// ?.from ??
// "";
//
// final manager = ref
// .read(walletsChangeNotifierProvider)
// .getManager(walletId);
final sendAmount = Decimal.parse(ref
.read(estimatedRateExchangeFormProvider)
.fromAmountString);

View file

@ -303,13 +303,17 @@ class _DebugViewState extends ConsumerState<DebugView> {
Logging.instance
.log("$e\n$s", level: LogLevel.Error);
}
final String? path =
await FilePicker.platform.getDirectoryPath(
dialogTitle: "Choose Backup location",
initialDirectory: dir.path,
lockParentWindow: true,
);
String? path;
if (Platform.isAndroid) {
path = dir.path;
} else {
path = await FilePicker.platform
.getDirectoryPath(
dialogTitle: "Choose Backup location",
initialDirectory: dir.path,
lockParentWindow: true,
);
}
if (path != null) {
final eventBus = EventBus();
@ -328,7 +332,7 @@ class _DebugViewState extends ConsumerState<DebugView> {
),
));
await ref
final filename = await ref
.read(debugServiceProvider)
.exportToFile(path, eventBus);
@ -336,10 +340,26 @@ class _DebugViewState extends ConsumerState<DebugView> {
if (mounted) {
Navigator.pop(context);
unawaited(showFloatingFlushBar(
type: FlushBarType.info,
context: context,
message: 'Logs file saved'));
if (Platform.isAndroid) {
unawaited(
showDialog(
context: context,
builder: (context) => StackOkDialog(
title: "Logs saved to",
message: "${path!}/$filename",
),
),
);
} else {
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
context: context,
message: 'Logs file saved',
),
);
}
}
}
},

View file

@ -90,6 +90,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
break;
case Coin.monero:
case Coin.wownero:
try {
final uri = Uri.parse(formData.host!);
if (uri.scheme.startsWith("http")) {
@ -384,6 +385,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
// strip unused path
String address = formData.host!;
if (coin == Coin.monero ||
coin == Coin.wownero ||
coin == Coin.epicCash) {
if (address.startsWith("http")) {
final uri = Uri.parse(address);
@ -539,6 +541,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
case Coin.epicCash:
case Coin.monero:
case Coin.wownero:
return true;
}
}
@ -699,7 +702,9 @@ class _NodeFormState extends ConsumerState<NodeForm> {
focusNode: _hostFocusNode,
style: STextStyles.field(context),
decoration: standardInputDecoration(
(widget.coin != Coin.monero && widget.coin != Coin.epicCash)
(widget.coin != Coin.monero &&
widget.coin != Coin.wownero &&
widget.coin != Coin.epicCash)
? "IP address"
: "Url",
_hostFocusNode,
@ -880,7 +885,9 @@ class _NodeFormState extends ConsumerState<NodeForm> {
const SizedBox(
height: 8,
),
if (widget.coin != Coin.monero && widget.coin != Coin.epicCash)
if (widget.coin != Coin.monero &&
widget.coin != Coin.wownero &&
widget.coin != Coin.epicCash)
Row(
children: [
GestureDetector(
@ -931,11 +938,15 @@ class _NodeFormState extends ConsumerState<NodeForm> {
),
],
),
if (widget.coin != Coin.monero && widget.coin != Coin.epicCash)
if (widget.coin != Coin.monero &&
widget.coin != Coin.wownero &&
widget.coin != Coin.epicCash)
const SizedBox(
height: 8,
),
if (widget.coin != Coin.monero && widget.coin != Coin.epicCash)
if (widget.coin != Coin.monero &&
widget.coin != Coin.wownero &&
widget.coin != Coin.epicCash)
Row(
children: [
GestureDetector(

View file

@ -81,6 +81,7 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
break;
case Coin.monero:
case Coin.wownero:
try {
final uri = Uri.parse(node!.host);
if (uri.scheme.startsWith("http")) {

View file

@ -82,6 +82,17 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
passwordFocusNode = FocusNode();
passwordRepeatFocusNode = FocusNode();
if (Platform.isAndroid) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final dir = await stackFileSystem.prepareStorage();
if (mounted) {
setState(() {
fileLocationController.text = dir.path;
});
}
});
}
super.initState();
}
@ -133,64 +144,70 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
const SizedBox(
height: 10,
),
TextField(
onTap: () async {
try {
await stackFileSystem.prepareStorage();
if (!Platform.isAndroid)
TextField(
onTap: Platform.isAndroid
? null
: () async {
try {
await stackFileSystem.prepareStorage();
if (mounted) {
await stackFileSystem.pickDir(context);
}
if (mounted) {
await stackFileSystem.pickDir(context);
}
if (mounted) {
setState(() {
fileLocationController.text =
stackFileSystem.dirPath ?? "";
});
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Error);
}
},
controller: fileLocationController,
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Save to...",
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
Assets.svg.folder,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
if (mounted) {
setState(() {
fileLocationController.text =
stackFileSystem.dirPath ?? "";
});
}
} catch (e, s) {
Logging.instance
.log("$e\n$s", level: LogLevel.Error);
}
},
controller: fileLocationController,
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Save to...",
hintStyle: STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
Assets.svg.folder,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
),
),
),
key: const Key(
"createBackupSaveToFileLocationTextFieldKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
),
onChanged: (newValue) {},
),
key: const Key(
"createBackupSaveToFileLocationTextFieldKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
if (!Platform.isAndroid)
const SizedBox(
height: 10,
),
onChanged: (newValue) {},
),
const SizedBox(
height: 10,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
@ -593,8 +610,15 @@ class _EnableAutoBackupViewState extends ConsumerState<CreateAutoBackupView> {
await showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => const StackOkDialog(
title: "Stack Auto Backup enabled!"),
builder: (_) => Platform.isAndroid
? StackOkDialog(
title:
"Stack Auto Backup enabled and saved to:",
message: fileToSave,
)
: const StackOkDialog(
title:
"Stack Auto Backup enabled!"),
);
if (mounted) {
passwordController.text = "";

View file

@ -64,6 +64,17 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
passwordFocusNode = FocusNode();
passwordRepeatFocusNode = FocusNode();
if (Platform.isAndroid) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final dir = await stackFileSystem.prepareStorage();
if (mounted) {
setState(() {
fileLocationController.text = dir.path;
});
}
});
}
super.initState();
}
@ -113,88 +124,78 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Consumer(builder: (context, ref, __) {
return Container(
color: Colors.transparent,
child: TextField(
onTap: () async {
try {
await stackFileSystem.prepareStorage();
// ref
// .read(
// shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = false;
if (mounted) {
await stackFileSystem.pickDir(context);
}
if (!Platform.isAndroid)
Consumer(builder: (context, ref, __) {
return Container(
color: Colors.transparent,
child: TextField(
onTap: Platform.isAndroid
? null
: () async {
try {
await stackFileSystem.prepareStorage();
// Future<void>.delayed(
// const Duration(seconds: 2),
// () => ref
// .read(
// shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = true,
// );
if (mounted) {
await stackFileSystem
.pickDir(context);
}
setState(() {
fileLocationController.text =
stackFileSystem.dirPath ?? "";
});
} catch (e, s) {
// ref
// .read(
// shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = true;
Logging.instance
.log("$e\n$s", level: LogLevel.Error);
}
},
controller: fileLocationController,
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Save to...",
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
Assets.svg.folder,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
if (mounted) {
setState(() {
fileLocationController.text =
stackFileSystem.dirPath ?? "";
});
}
} catch (e, s) {
Logging.instance.log("$e\n$s",
level: LogLevel.Error);
}
},
controller: fileLocationController,
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Save to...",
hintStyle: STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
Assets.svg.folder,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
),
),
),
key: const Key(
"createBackupSaveToFileLocationTextFieldKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
),
onChanged: (newValue) {
// ref.read(addressEntryDataProvider(widget.id)).address = newValue;
},
),
key: const Key(
"createBackupSaveToFileLocationTextFieldKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
),
onChanged: (newValue) {
// ref.read(addressEntryDataProvider(widget.id)).address = newValue;
},
),
);
}),
const SizedBox(
height: 8,
),
);
}),
if (!Platform.isAndroid)
const SizedBox(
height: 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
@ -315,8 +316,12 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
.extension<StackColors>()!
.accentColorRed
: passwordStrength < 1
? Theme.of(context).extension<StackColors>()!.accentColorYellow
: Theme.of(context).extension<StackColors>()!.accentColorGreen,
? Theme.of(context)
.extension<StackColors>()!
.accentColorYellow
: Theme.of(context)
.extension<StackColors>()!
.accentColorGreen,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.buttonBackSecondary,
@ -389,8 +394,12 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
const Spacer(),
TextButton(
style: shouldEnableCreate
? Theme.of(context) .extension<StackColors>()!.getPrimaryEnabledButtonColor(context)
: Theme.of(context) .extension<StackColors>()!.getPrimaryDisabledButtonColor(context),
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonColor(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonColor(context),
onPressed: !shouldEnableCreate
? null
: () async {
@ -468,8 +477,14 @@ class _RestoreFromFileViewState extends State<CreateBackupView> {
await showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => const StackOkDialog(
title: "Backup creation succeeded"),
builder: (_) => Platform.isAndroid
? StackOkDialog(
title: "Backup saved to:",
message: fileToSave,
)
: const StackOkDialog(
title:
"Backup creation succeeded"),
);
passwordController.text = "";
passwordRepeatController.text = "";

View file

@ -84,6 +84,17 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
passwordFocusNode = FocusNode();
passwordRepeatFocusNode = FocusNode();
if (Platform.isAndroid) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final dir = await stackFileSystem.prepareStorage();
if (mounted) {
setState(() {
fileLocationController.text = dir.path;
});
}
});
}
super.initState();
}
@ -135,64 +146,70 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
const SizedBox(
height: 10,
),
TextField(
onTap: () async {
try {
await stackFileSystem.prepareStorage();
if (!Platform.isAndroid)
TextField(
onTap: Platform.isAndroid
? null
: () async {
try {
await stackFileSystem.prepareStorage();
if (mounted) {
await stackFileSystem.pickDir(context);
}
if (mounted) {
await stackFileSystem.pickDir(context);
}
if (mounted) {
setState(() {
fileLocationController.text =
stackFileSystem.dirPath ?? "";
});
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Error);
}
},
controller: fileLocationController,
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Save to...",
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
Assets.svg.folder,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
if (mounted) {
setState(() {
fileLocationController.text =
stackFileSystem.dirPath ?? "";
});
}
} catch (e, s) {
Logging.instance
.log("$e\n$s", level: LogLevel.Error);
}
},
controller: fileLocationController,
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Save to...",
hintStyle: STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
Assets.svg.folder,
color: Theme.of(context)
.extension<StackColors>()!
.textDark3,
width: 16,
height: 16,
),
const SizedBox(
width: 12,
),
],
),
),
),
key: const Key(
"createBackupSaveToFileLocationTextFieldKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
),
onChanged: (newValue) {},
),
key: const Key(
"createBackupSaveToFileLocationTextFieldKey"),
readOnly: true,
toolbarOptions: const ToolbarOptions(
copy: true,
cut: false,
paste: false,
selectAll: false,
if (!Platform.isAndroid)
const SizedBox(
height: 10,
),
onChanged: (newValue) {},
),
const SizedBox(
height: 10,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
@ -594,8 +611,14 @@ class _EditAutoBackupViewState extends ConsumerState<EditAutoBackupView> {
await showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => const StackOkDialog(
title: "Stack Auto Backup saved"),
builder: (_) => Platform.isAndroid
? StackOkDialog(
title:
"Stack Auto Backup saved to:",
message: fileToSave,
)
: const StackOkDialog(
title: "Stack Auto Backup saved"),
);
if (mounted) {
passwordController.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

@ -13,7 +13,7 @@ class StackFileSystem {
final bool isDesktop = !(Platform.isAndroid || Platform.isIOS);
Future<void> prepareStorage() async {
Future<Directory> prepareStorage() async {
rootPath = (await getApplicationDocumentsDirectory());
debugPrint(rootPath!.absolute.toString());
if (Platform.isAndroid) {
@ -47,6 +47,7 @@ class StackFileSystem {
debugPrint("$e $s");
}
startPath = sampleFolder;
return sampleFolder;
}
Future<void> pickDir(BuildContext context) async {

View file

@ -99,29 +99,17 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
onTap: () async {
try {
await stackFileSystem.prepareStorage();
// ref
// .read(shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = false;
await stackFileSystem.openFile(context);
if (mounted) {
await stackFileSystem.openFile(context);
}
// Future<void>.delayed(
// const Duration(seconds: 2),
// () => ref
// .read(
// shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = true,
// );
fileLocationController.text =
stackFileSystem.filePath ?? "";
setState(() {});
if (mounted) {
setState(() {
fileLocationController.text =
stackFileSystem.filePath ?? "";
});
}
} catch (e, s) {
// ref
// .read(shouldShowLockscreenOnResumeStateProvider
// .state)
// .state = true;
Logging.instance
.log("$e\n$s", level: LogLevel.Error);
}
@ -130,6 +118,7 @@ class _RestoreFromFileViewState extends ConsumerState<RestoreFromFileView> {
style: STextStyles.field(context),
decoration: InputDecoration(
hintText: "Choose file...",
hintStyle: STextStyles.fieldLabel(context),
suffixIcon: UnconstrainedBox(
child: Row(
children: [

View file

@ -12,6 +12,7 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_net
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
import 'package:stackwallet/services/coins/monero/monero_wallet.dart';
import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart';
import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
@ -205,7 +206,7 @@ class _WalletNetworkSettingsViewState
.getManager(widget.walletId)
.coin;
if (coin == Coin.monero || coin == Coin.epicCash) {
if (coin == Coin.monero || coin == Coin.wownero || coin == Coin.epicCash) {
_blocksRemainingSubscription = eventBus.on<BlocksRemainingEvent>().listen(
(event) async {
if (event.walletId == widget.walletId) {
@ -271,6 +272,15 @@ class _WalletNetworkSettingsViewState
if (_percent < highestPercent) {
_percent = highestPercent.clamp(0.0, 1.0);
}
} else if (coin == Coin.wownero) {
double highestPercent = (ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.wallet as WowneroWallet)
.highestPercentCached;
if (_percent < highestPercent) {
_percent = highestPercent.clamp(0.0, 1.0);
}
} else if (coin == Coin.epicCash) {
double highestPercent = (ref
.read(walletsChangeNotifierProvider)
@ -545,6 +555,7 @@ class _WalletNetworkSettingsViewState
),
),
if (coin == Coin.monero ||
coin == Coin.wownero ||
coin == Coin.epicCash)
Text(
" (Blocks to go: ${_blocksRemaining == -1 ? "?" : _blocksRemaining})",

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

@ -241,7 +241,9 @@ class _TransactionDetailsViewState
"$amountPrefix${Format.localizedStringAsFixed(
value: coin == Coin.monero
? (amount / 10000.toDecimal()).toDecimal()
: amount,
: coin == Coin.wownero
? (amount / 1000.toDecimal()).toDecimal()
: amount,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
@ -254,7 +256,7 @@ class _TransactionDetailsViewState
height: 2,
),
SelectableText(
"${Format.localizedStringAsFixed(value: (coin == Coin.monero ? (amount / 10000.toDecimal()).toDecimal() : amount) * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getPrice(coin).item1)), locale: ref.watch(
"${Format.localizedStringAsFixed(value: (coin == Coin.monero ? (amount / 10000.toDecimal()).toDecimal() : coin == Coin.wownero ? (amount / 1000.toDecimal()).toDecimal() : amount) * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getPrice(coin).item1)), locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
), decimalPlaces: 2)} ${ref.watch(
@ -298,14 +300,14 @@ class _TransactionDetailsViewState
],
),
),
if (!(coin == Coin.monero &&
if (!((coin == Coin.monero || coin == Coin.wownero) &&
_transaction.txType.toLowerCase() == "sent") &&
!((coin == Coin.firo || coin == Coin.firoTestNet) &&
_transaction.subType == "mint"))
const SizedBox(
height: 12,
),
if (!(coin == Coin.monero &&
if (!((coin == Coin.monero || coin == Coin.wownero) &&
_transaction.txType.toLowerCase() == "sent") &&
!((coin == Coin.firo || coin == Coin.firoTestNet) &&
_transaction.subType == "mint"))
@ -464,7 +466,10 @@ class _TransactionDetailsViewState
? Format.localizedStringAsFixed(
value: coin == Coin.monero
? (fee / 10000.toDecimal()).toDecimal()
: fee,
: coin == Coin.wownero
? (fee / 1000.toDecimal())
.toDecimal()
: fee,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale)),
@ -473,7 +478,9 @@ class _TransactionDetailsViewState
: Format.localizedStringAsFixed(
value: coin == Coin.monero
? (fee / 10000.toDecimal()).toDecimal()
: fee,
: coin == Coin.wownero
? (fee / 1000.toDecimal()).toDecimal()
: fee,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale)),

View file

@ -755,6 +755,11 @@ class _TransactionSearchViewState
.floor()
.toBigInt()
.toInt();
} else if (widget.coin == Coin.wownero) {
amount = (amountDecimal * Decimal.fromInt(Constants.satsPerCoinWownero))
.floor()
.toBigInt()
.toInt();
} else {
amount = (amountDecimal * Decimal.fromInt(Constants.satsPerCoin))
.floor()

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

@ -6,6 +6,7 @@ import 'dart:typed_data';
import 'package:bech32/bech32.dart';
import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
import 'package:bitbox/bitbox.dart' as Bitbox;
import 'package:bitcoindart/bitcoindart.dart';
import 'package:bs58check/bs58check.dart' as bs58check;
import 'package:crypto/crypto.dart';
@ -41,7 +42,6 @@ import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:tuple/tuple.dart';
import 'package:uuid/uuid.dart';
import 'package:bitbox/bitbox.dart' as Bitbox;
const int MINIMUM_CONFIRMATIONS = 3;
const int DUST_LIMIT = 546;

View file

@ -4,11 +4,12 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart';
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/services/coins/monero/monero_wallet.dart';
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart';
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -145,6 +146,14 @@ abstract class CoinServiceAPI {
// tracker: tracker,
);
case Coin.wownero:
return WowneroWallet(
walletId: walletId,
walletName: walletName,
coin: coin,
// tracker: tracker,
);
case Coin.namecoin:
return NamecoinWallet(
walletId: walletId,

View file

@ -259,6 +259,9 @@ Future<String> deleteEpicWallet({
if (Platform.isIOS) {
appDir = (await getLibraryDirectory());
}
if (Platform.isLinux) {
appDir = Directory("${appDir.path}/.stackwallet");
}
final path = "${appDir.path}/epiccash";
final String name = walletId;
@ -1232,6 +1235,9 @@ class EpicCashWallet extends CoinServiceAPI {
if (Platform.isIOS) {
appDir = (await getLibraryDirectory());
}
if (Platform.isLinux) {
appDir = Directory("${appDir.path}/.stackwallet");
}
final path = "${appDir.path}/epiccash";
final String name = _walletId.trim();
return '$path/$name';

View file

@ -910,6 +910,10 @@ class MoneroWallet extends CoinServiceAPI {
if (Platform.isIOS) {
root = (await getLibraryDirectory());
}
//
if (Platform.isLinux) {
root = Directory("${root.path}/.stackwallet");
}
final prefix = walletTypeToString(type).toLowerCase();
final walletsDir = Directory('${root.path}/wallets');
final walletDire = Directory('${walletsDir.path}/$prefix/$name');

File diff suppressed because it is too large Load diff

View file

@ -75,7 +75,8 @@ class DebugService extends ChangeNotifier {
level: LogLevel.Info);
}
Future<void> exportToFile(String directory, EventBus eventBus) async {
/// returns the filename of the saved logs file
Future<String> exportToFile(String directory, EventBus eventBus) async {
final now = DateTime.now();
final filename =
"Stack_Wallet_logs_${now.year}_${now.month}_${now.day}_${now.hour}_${now.minute}_${now.second}.txt";
@ -99,5 +100,6 @@ class DebugService extends ChangeNotifier {
await sink.close();
eventBus.fire(1.0);
return filename;
}
}

View file

@ -79,7 +79,7 @@ class PriceAPI {
Map<Coin, Tuple2<Decimal, double>> result = {};
try {
final uri = Uri.parse(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false");
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false");
// final uri = Uri.parse(
// "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero%2Cbitcoin%2Cepic-cash%2Czcoin%2Cdogecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false");

View file

@ -223,7 +223,7 @@ class Wallets extends ChangeNotifier {
final shouldSetAutoSync = shouldAutoSyncAll ||
walletIdsToEnableAutoSync.contains(manager.walletId);
if (manager.coin == Coin.monero) {
if (manager.coin == Coin.monero || manager.coin == Coin.wownero) {
walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync));
} else {
walletInitFutures.add(manager.initializeExisting().then((value) {
@ -312,7 +312,7 @@ class Wallets extends ChangeNotifier {
final shouldSetAutoSync = shouldAutoSyncAll ||
walletIdsToEnableAutoSync.contains(manager.walletId);
if (manager.coin == Coin.monero) {
if (manager.coin == Coin.monero || manager.coin == Coin.wownero) {
walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync));
} else {
walletInitFutures.add(manager.initializeExisting().then((value) {

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_libmonero/monero/monero.dart';
import 'package:flutter_libmonero/wownero/wownero.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:stackwallet/hive/db.dart';
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
@ -367,8 +368,13 @@ class WalletsService extends ChangeNotifier {
await DB.instance.delete<dynamic>(
boxName: DB.boxNameAllWalletsData,
key: "${walletId}_mnemonicHasBeenVerified");
if (coinFromPrettyName(shell['coin'] as String) == Coin.monero) {
if (coinFromPrettyName(shell['coin'] as String) == Coin.wownero) {
final wowService =
wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox);
await wowService.remove(walletId);
Logging.instance
.log("monero wallet: $walletId deleted", level: LogLevel.Info);
} else if (coinFromPrettyName(shell['coin'] as String) == Coin.monero) {
final xmrService =
monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox);
await xmrService.remove(walletId);

View file

@ -53,6 +53,9 @@ class AddressUtils {
case Coin.monero:
return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) ||
RegExp("[a-zA-Z0-9]{106}").hasMatch(address);
case Coin.wownero:
return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) ||
RegExp("[a-zA-Z0-9]{106}").hasMatch(address);
case Coin.namecoin:
return Address.validateAddress(address, namecoin, namecoin.bech32!);
case Coin.bitcoinTestNet:

View file

@ -130,6 +130,7 @@ class _SVG {
String get epicCash => "assets/svg/coin_icons/EpicCash.svg";
String get firo => "assets/svg/coin_icons/Firo.svg";
String get monero => "assets/svg/coin_icons/Monero.svg";
String get wownero => "assets/svg/coin_icons/Wownero.svg";
String get namecoin => "assets/svg/coin_icons/Namecoin.svg";
String get chevronRight => "assets/svg/chevron-right.svg";
@ -158,6 +159,8 @@ class _SVG {
return firo;
case Coin.monero:
return monero;
case Coin.wownero:
return wownero;
case Coin.namecoin:
return namecoin;
case Coin.bitcoinTestNet:
@ -179,6 +182,7 @@ class _PNG {
String get splash => "assets/images/splash.png";
String get monero => "assets/images/monero.png";
String get wownero => "assets/images/wownero.png";
String get firo => "assets/images/firo.png";
String get dogecoin => "assets/images/doge.png";
String get bitcoin => "assets/images/bitcoin.png";
@ -205,6 +209,8 @@ class _PNG {
return firo;
case Coin.monero:
return monero;
case Coin.wownero:
return wownero;
case Coin.namecoin:
return namecoin;
}

View file

@ -18,6 +18,8 @@ Uri getBlockExplorerTransactionUrlFor({
throw UnimplementedError("missing block explorer for epic cash");
case Coin.monero:
return Uri.parse("https://xmrchain.net/tx/$txid");
case Coin.wownero:
return Uri.parse("https://explore.wownero.com/search?value=$txid");
case Coin.firo:
return Uri.parse("https://explorer.firo.org/tx/$txid");
case Coin.firoTestNet:

View file

@ -18,8 +18,11 @@ abstract class Constants {
//TODO: correct for monero?
static const int satsPerCoinMonero = 1000000000000;
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);
@ -40,6 +43,7 @@ abstract class Constants {
switch (coin) {
case Coin.bitcoin:
case Coin.bitcoincash:
case Coin.bitcoincashTestnet:
case Coin.dogecoin:
case Coin.firo:
case Coin.bitcoinTestNet:
@ -53,6 +57,9 @@ abstract class Constants {
case Coin.monero:
values.addAll([25]);
break;
case Coin.wownero:
values.addAll([14]);
break;
}
return values;
}
@ -82,6 +89,9 @@ abstract class Constants {
case Coin.monero:
return 120;
case Coin.wownero:
return 120;
case Coin.namecoin:
return 600;
}

View file

@ -15,7 +15,9 @@ abstract class DefaultNodes {
epicCash,
bitcoincash,
namecoin,
wownero,
bitcoinTestnet,
bitcoincashTestnet,
dogecoinTestnet,
firoTestnet,
];
@ -82,6 +84,20 @@ abstract class DefaultNodes {
isDown: false,
);
// TODO: eventually enable ssl and set scheme to https
// currently get certificate failure
static NodeModel get wownero => NodeModel(
host: "http://eu-west-2.wow.xmr.pm",
port: 34568,
name: defaultName,
id: _nodeId(Coin.wownero),
useSSL: false,
enabled: true,
coinName: Coin.wownero.name,
isFailover: true,
isDown: false,
);
static NodeModel get epicCash => NodeModel(
host: "http://epiccash.stackwallet.com",
port: 3413,
@ -174,6 +190,9 @@ abstract class DefaultNodes {
case Coin.monero:
return monero;
case Coin.wownero:
return wownero;
case Coin.namecoin:
return namecoin;

View file

@ -9,6 +9,7 @@ import 'package:stackwallet/services/coins/firo/firo_wallet.dart' as firo;
import 'package:stackwallet/services/coins/monero/monero_wallet.dart' as xmr;
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'
as nmc;
import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow;
enum Coin {
bitcoin,
@ -17,6 +18,7 @@ enum Coin {
epicCash,
firo,
monero,
wownero,
namecoin,
///
@ -47,6 +49,8 @@ extension CoinExt on Coin {
return "Firo";
case Coin.monero:
return "Monero";
case Coin.wownero:
return "Wownero";
case Coin.namecoin:
return "Namecoin";
case Coin.bitcoinTestNet:
@ -74,6 +78,8 @@ extension CoinExt on Coin {
return "FIRO";
case Coin.monero:
return "XMR";
case Coin.wownero:
return "WOW";
case Coin.namecoin:
return "NMC";
case Coin.bitcoinTestNet:
@ -102,6 +108,8 @@ extension CoinExt on Coin {
return "firo";
case Coin.monero:
return "monero";
case Coin.wownero:
return "wownero";
case Coin.namecoin:
return "namecoin";
case Coin.bitcoinTestNet:
@ -130,6 +138,7 @@ extension CoinExt on Coin {
case Coin.epicCash:
case Coin.monero:
case Coin.wownero:
return false;
}
}
@ -157,6 +166,10 @@ extension CoinExt on Coin {
case Coin.monero:
return xmr.MINIMUM_CONFIRMATIONS;
case Coin.wownero:
return wow.MINIMUM_CONFIRMATIONS;
case Coin.namecoin:
return nmc.MINIMUM_CONFIRMATIONS;
}
@ -204,6 +217,10 @@ Coin coinFromPrettyName(String name) {
case "tDogecoin":
case "dogecoinTestNet":
return Coin.dogecoinTestNet;
case "Wownero":
case "tWownero":
case "wownero":
return Coin.wownero;
default:
throw ArgumentError.value(
name, "name", "No Coin enum value with that prettyName");
@ -234,6 +251,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) {
return Coin.firoTestNet;
case "tdoge":
return Coin.dogecoinTestNet;
case "wow":
return Coin.wownero;
default:
throw ArgumentError.value(
ticker, "name", "No Coin enum value with that ticker");

View file

@ -209,8 +209,8 @@ class CoinThemeColor {
return monero;
case Coin.namecoin:
return namecoin;
// case Coin.wownero:
// return wownero;
case Coin.wownero:
return wownero;
}
}
}

View file

@ -1422,8 +1422,8 @@ class StackColors extends ThemeExtension<StackColors> {
return _coin.monero;
case Coin.namecoin:
return _coin.namecoin;
// case Coin.wownero:
// return wownero;
case Coin.wownero:
return _coin.wownero;
}
}

View file

@ -85,6 +85,7 @@ class NodeOptionsSheet extends ConsumerWidget {
break;
case Coin.monero:
case Coin.wownero:
try {
final uri = Uri.parse(node.host);
if (uri.scheme.startsWith("http")) {

View file

@ -172,7 +172,9 @@ class _TransactionCardState extends ConsumerState<TransactionCard> {
builder: (_) {
final amount = coin == Coin.monero
? (_transaction.amount ~/ 10000)
: _transaction.amount;
: coin == Coin.wownero
? (_transaction.amount ~/ 1000)
: _transaction.amount;
return Text(
"${Format.satoshiAmountToPrettyString(amount, locale)} ${coin.ticker}",
style:
@ -212,6 +214,8 @@ class _TransactionCardState extends ConsumerState<TransactionCard> {
int value = _transaction.amount;
if (coin == Coin.monero) {
value = (value ~/ 10000);
} else if (coin == Coin.wownero) {
value = (value ~/ 1000);
}
return Text(

View file

@ -137,6 +137,9 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libmonero/scripts/linux/build/libcw_monero.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libmonero/scripts/linux/build/libcw_wownero.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libepiccash/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libepic_cash_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)

View file

@ -317,6 +317,13 @@ packages:
relative: true
source: path
version: "0.0.1"
cw_wownero:
dependency: "direct main"
description:
path: "crypto_plugins/flutter_libmonero/cw_wownero"
relative: true
source: path
version: "0.0.1"
dart_numerics:
dependency: "direct main"
description:

View file

@ -11,7 +11,7 @@ description: Stack Wallet
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.4.45+60
version: 1.4.53+69
environment:
sdk: ">=2.17.0 <3.0.0"
@ -31,6 +31,9 @@ dependencies:
cw_monero:
path: ./crypto_plugins/flutter_libmonero/cw_monero
cw_wownero:
path: ./crypto_plugins/flutter_libmonero/cw_wownero
cw_core:
path: ./crypto_plugins/flutter_libmonero/cw_core
@ -189,6 +192,7 @@ flutter:
- assets/svg/clipboard.svg
- assets/images/stack.png
- assets/images/monero.png
- assets/images/wownero.png
- assets/images/firo.png
- assets/images/doge.png
- assets/images/bitcoin.png
@ -293,6 +297,7 @@ flutter:
- assets/svg/coin_icons/EpicCash.svg
- assets/svg/coin_icons/Firo.svg
- assets/svg/coin_icons/Monero.svg
- assets/svg/coin_icons/Wownero.svg
- assets/svg/coin_icons/Namecoin.svg
# lottie animations
- assets/lottie/test.json

0
scripts/ios/build_all.sh Normal file → Executable file
View file

6
scripts/prebuild.sh Normal file
View file

@ -0,0 +1,6 @@
# Create template lib/external_api_keys.dart file if it doesn't already exist
KEYS=../lib/external_api_keys.dart
if ! test -f "$KEYS"; then
echo 'prebuild.sh: creating template lib/external_api_keys.dart file'
echo 'const kChangeNowApiKey = "";' > $KEYS
fi

View file

@ -1,7 +1,152 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:stackwallet/models/models.dart';
import '../services/coins/firo/sample_data/transaction_data_samples.dart';
void main() {
group("TransactionData", () {
test("TransactionData from Json", () {
final txChunk = TransactionChunk.fromJson({
"timestamp": 993260735,
"transactions": [
{
"txid": "txid",
"confirmed_status": true,
"timestamp": 1876352482,
"txType": "txType",
"amount": 10,
"worthNow": "1",
"worthAtBlockTimestamp": "1",
"fees": 1,
"inputSize": 1,
"outputSize": 1,
"inputs": [],
"outputs": [],
"address": "address",
"height": 1,
"confirmations": 1,
"aliens": [],
"subType": "mint",
"isCancelled": false,
"slateId": "slateId",
"otherData": "otherData",
}
]
});
final txdata =
TransactionData.fromJson({"dateTimeChunks": [], "txChunks": []});
txdata.findTransaction("txid");
txdata.getAllTransactions();
});
});
group("Timestamp", () {
test("Timestamp is now", () {
final date = extractDateFromTimestamp(0);
});
test("Timestamp is null", () {
final date = extractDateFromTimestamp(null);
});
test("Timestamp is a random date", () {
final date = extractDateFromTimestamp(1876352482);
});
});
group("Transaction", () {
test("Transaction from Json", () {
final tx = Transaction.fromJson({
"txid": "txid",
"confirmed_status": true,
"timestamp": 1876352482,
"txType": "txType",
"amount": 10,
"worthNow": "1",
"worthAtBlockTimestamp": "1",
"fees": 1,
"inputSize": 1,
"outputSize": 1,
"inputs": [],
"outputs": [],
"address": "address",
"height": 1,
"confirmations": 1,
"aliens": [],
"subType": "mint",
"isCancelled": false,
"slateId": "slateId",
"otherData": "otherData",
});
});
test("Transaction from Lelantus Json", () {
final tx = Transaction.fromLelantusJson({
"txid": "txid",
"confirmed_status": true,
"timestamp": 1876352482,
"txType": "txType",
"amount": 10,
"worthNow": "1",
"worthAtBlockTimestamp": "1",
"fees": 1,
"inputSize": 1,
"outputSize": 1,
"inputs": [],
"outputs": [],
"address": "address",
"height": 1,
"confirmations": 1,
"aliens": [],
"subType": "mint",
"isCancelled": false,
"slateId": "slateId",
"otherData": "otherData",
});
});
test("TransactionChunk", () {
final transactionchunk = TransactionChunk.fromJson({
"timestamp": 45920,
"transactions": [],
});
expect(
transactionchunk.toString(), "timestamp: 45920 transactions: [\n]");
});
test("TransactionChunk with a transaction", () {
final txChunk = TransactionChunk.fromJson({
"timestamp": 993260735,
"transactions": [
{
"txid": "txid",
"confirmed_status": true,
"timestamp": 1876352482,
"txType": "txType",
"amount": 10,
"worthNow": "1",
"worthAtBlockTimestamp": "1",
"fees": 1,
"inputSize": 1,
"outputSize": 1,
"inputs": [],
"outputs": [],
"address": "address",
"height": 1,
"confirmations": 1,
"aliens": [],
"subType": "mint",
"isCancelled": false,
"slateId": "slateId",
"otherData": "otherData",
}
]
});
expect(txChunk.toString(),
"timestamp: 993260735 transactions: [\n {txid: txid, type: txType, subType: mint, value: 10, fee: 1, height: 1, confirm: true, confirmations: 1, address: address, timestamp: 1876352482, worthNow: 1, inputs: [], slateid: slateId } \n]");
});
});
group("Transaction isMinting", () {
test("Transaction isMinting unconfirmed mint", () {
final tx = Transaction(
@ -94,4 +239,57 @@ void main() {
expect(tx1 == tx2, false);
expect(tx2.toString(), tx1.toString());
});
group("Input", () {
test("Input.toString", () {
final input = Input(
txid: "txid",
vout: 1,
prevout: null,
scriptsig: "scriptsig",
scriptsigAsm: "scriptsigAsm",
witness: [],
isCoinbase: false,
sequence: 1,
innerRedeemscriptAsm: "innerRedeemscriptAsm",
); //Input
expect(input.toString(), "{txid: txid}");
});
test("Input.toString", () {
final input = Input.fromJson({
"txid": "txid",
"vout": 1,
"prevout": null,
"scriptSig": {"hex": "somehexString", "asm": "someasmthing"},
"scriptsigAsm": "scriptsigAsm",
"witness": [],
"isCoinbase": false,
"sequence": 1,
"innerRedeemscriptAsm": "innerRedeemscriptAsm",
}); //Input
expect(input.toString(), "{txid: txid}");
});
});
group("Output", () {
test("Output.toString", () {
final output = Output.fromJson({
"scriptPubKey": {
"hex": "somehexSting",
"asm": "someasmthing",
"type": "sometype",
"addresses": "someaddresses",
},
"scriptpubkeyAsm": "scriptpubkeyAsm",
"scriptpubkeyType": "scriptpubkeyType",
"scriptpubkeyAddress": "address",
"value": 2,
}); //Input
expect(output.toString(), "Instance of \'Output\'");
});
});
}

View file

@ -23,7 +23,7 @@ void main() {
when(client.get(
Uri.parse(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
headers: {
'Content-Type': 'application/json'
})).thenAnswer((_) async => Response(
@ -36,10 +36,10 @@ void main() {
final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc");
expect(price.toString(),
'{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524], Coin.firo: [0.0001096, -0.89304], Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
'{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524], Coin.firo: [0.0001096, -0.89304], Coin.monero: [0.00717236, -0.77656], Coin.wownero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
verify(client.get(
Uri.parse(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
headers: {'Content-Type': 'application/json'})).called(1);
verifyNoMoreInteractions(client);
@ -50,7 +50,7 @@ void main() {
when(client.get(
Uri.parse(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
headers: {
'Content-Type': 'application/json'
})).thenAnswer((_) async => Response(
@ -68,12 +68,12 @@ void main() {
await priceAPI.getPricesAnd24hChange(baseCurrency: "btc");
expect(cachedPrice.toString(),
'{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524], Coin.firo: [0.0001096, -0.89304], Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
'{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524], Coin.firo: [0.0001096, -0.89304], Coin.monero: [0.00717236, -0.77656], Coin.wownero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
// verify only called once during filling of cache
verify(client.get(
Uri.parse(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
headers: {'Content-Type': 'application/json'})).called(1);
verifyNoMoreInteractions(client);
@ -84,7 +84,7 @@ void main() {
when(client.get(
Uri.parse(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
headers: {
'Content-Type': 'application/json'
})).thenAnswer((_) async => Response(
@ -97,7 +97,7 @@ void main() {
final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc");
expect(price.toString(),
'{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.firo: [0, 0.0], Coin.monero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
'{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.firo: [0, 0.0], Coin.monero: [0, 0.0], Coin.wownero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
});
test("no internet available", () async {
@ -105,7 +105,7 @@ void main() {
when(client.get(
Uri.parse(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero&order=market_cap_desc&per_page=10&page=1&sparkline=false"),
headers: {
'Content-Type': 'application/json'
})).thenThrow(const SocketException(
@ -117,7 +117,7 @@ void main() {
final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc");
expect(price.toString(),
'{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.firo: [0, 0.0], Coin.monero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
'{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.firo: [0, 0.0], Coin.monero: [0, 0.0], Coin.wownero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
});
tearDown(() async {

View file

@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:bitcoindart/bitcoindart.dart';
import 'package:decimal/decimal.dart';
import 'package:flutter_test/flutter_test.dart';

View file

@ -1,6 +1,3 @@
import 'dart:convert';
import 'package:bitcoindart/bitcoindart.dart';
import 'package:decimal/decimal.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hive/hive.dart';