mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-03-21 22:58:49 +00:00
commit
bd4ce2c2ac
26 changed files with 32835 additions and 516 deletions
15
.github/workflows/test.yaml
vendored
15
.github/workflows/test.yaml
vendored
|
@ -1,5 +1,6 @@
|
||||||
|
#should deny
|
||||||
name: Test
|
name: Test
|
||||||
on: [push, pull_request]
|
on: [pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
@ -13,6 +14,10 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
- name: Checkout submodules
|
- name: Checkout submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
|
- name: Build Lelantus
|
||||||
|
run: |
|
||||||
|
cd crypto_plugins/flutter_liblelantus/scripts/linux/
|
||||||
|
./build_all.sh
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
- name: Create temp files
|
- name: Create temp files
|
||||||
|
@ -58,7 +63,13 @@ jobs:
|
||||||
# - name: Analyze
|
# - name: Analyze
|
||||||
# run: flutter analyze
|
# run: flutter analyze
|
||||||
- name: Test
|
- name: Test
|
||||||
run: flutter test
|
run: flutter test --coverage
|
||||||
|
- name: Upload to code coverage
|
||||||
|
uses: codecov/codecov-action@v1.2.2
|
||||||
|
if: success() || failure()
|
||||||
|
with:
|
||||||
|
token: ${{secrets.CODECOV_TOKEN}}
|
||||||
|
file: coverage/lcov.info
|
||||||
- name: Delete temp files
|
- name: Delete temp files
|
||||||
run: |
|
run: |
|
||||||
Remove-Item -Path $env:CHANGE_NOW;
|
Remove-Item -Path $env:CHANGE_NOW;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
[](https://codecov.io/gh/cypherstack/stack_wallet)
|
||||||
|
|
||||||
# Stack Wallet
|
# Stack Wallet
|
||||||
put details here
|
put details here
|
||||||
|
|
||||||
|
@ -8,6 +10,7 @@ put features here
|
||||||
|
|
||||||
## Build and run
|
## Build and run
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
- Flutter 3.0.5
|
||||||
- Flutter SDK Requirement (>=2.12.0, up until <3.0.0)
|
- Flutter SDK Requirement (>=2.12.0, up until <3.0.0)
|
||||||
- Android/iOS dev setup (Android Studio, xCode and subsequent dependencies)
|
- Android/iOS dev setup (Android Studio, xCode and subsequent dependencies)
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ linter:
|
||||||
no_leading_underscores_for_local_identifiers: false
|
no_leading_underscores_for_local_identifiers: false
|
||||||
no_leading_underscores_for_library_prefixes: false
|
no_leading_underscores_for_library_prefixes: false
|
||||||
avoid_print: true
|
avoid_print: true
|
||||||
unawaited_futures: false
|
unawaited_futures: true
|
||||||
avoid_double_and_int_checks: false
|
avoid_double_and_int_checks: false
|
||||||
constant_identifier_names: false
|
constant_identifier_names: false
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
|
|
@ -53,6 +53,15 @@ android {
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "x86_64","armeabi-v7a", "arm64-v8a"
|
abiFilters "x86_64","armeabi-v7a", "arm64-v8a"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments "-DANDROID_STL=c++_shared", '-DBUILD_TESTING=OFF', "-DANDROID_TOOLCHAIN=clang -v"
|
||||||
|
cppFlags "-frtti -fexceptions -v -DANDROID -std=c++17"
|
||||||
|
// cppFlags "-std=c++11"
|
||||||
|
version "3.10.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 78533fa427ffc582b83cb67a766c8d38fac2abd8
|
Subproject commit 6242046217abf47b61d9397ae447632b06f853fa
|
|
@ -1 +1 @@
|
||||||
Subproject commit 771175e87c629ca1d8db7a4417191bd15012d52f
|
Subproject commit cd6f9cf62afcb6c1e55b16a76374a8577d85352f
|
|
@ -449,7 +449,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 37;
|
CURRENT_PROJECT_VERSION = 42;
|
||||||
DEVELOPMENT_TEAM = 4DQKUWSG6C;
|
DEVELOPMENT_TEAM = 4DQKUWSG6C;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
@ -503,7 +503,7 @@
|
||||||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
|
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
|
||||||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
|
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.30;
|
MARKETING_VERSION = 1.4.34;
|
||||||
ONLY_ACTIVE_ARCH = NO;
|
ONLY_ACTIVE_ARCH = NO;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -633,7 +633,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 37;
|
CURRENT_PROJECT_VERSION = 42;
|
||||||
DEVELOPMENT_TEAM = 4DQKUWSG6C;
|
DEVELOPMENT_TEAM = 4DQKUWSG6C;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
@ -687,7 +687,7 @@
|
||||||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
|
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
|
||||||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
|
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.30;
|
MARKETING_VERSION = 1.4.34;
|
||||||
ONLY_ACTIVE_ARCH = NO;
|
ONLY_ACTIVE_ARCH = NO;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -709,7 +709,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 37;
|
CURRENT_PROJECT_VERSION = 42;
|
||||||
DEVELOPMENT_TEAM = 4DQKUWSG6C;
|
DEVELOPMENT_TEAM = 4DQKUWSG6C;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
@ -763,7 +763,7 @@
|
||||||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
|
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
|
||||||
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
|
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.30;
|
MARKETING_VERSION = 1.4.34;
|
||||||
ONLY_ACTIVE_ARCH = NO;
|
ONLY_ACTIVE_ARCH = NO;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
|
@ -176,7 +176,14 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
||||||
|
|
||||||
late final Completer<void> loadingCompleter;
|
late final Completer<void> loadingCompleter;
|
||||||
|
|
||||||
|
bool didLoad = false;
|
||||||
|
|
||||||
Future<void> load() async {
|
Future<void> load() async {
|
||||||
|
if (didLoad) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
didLoad = true;
|
||||||
|
|
||||||
await DB.instance.init();
|
await DB.instance.init();
|
||||||
|
|
||||||
_notificationsService = ref.read(notificationsProvider);
|
_notificationsService = ref.read(notificationsProvider);
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart';
|
import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart';
|
||||||
|
@ -15,15 +13,10 @@ class AddWalletView extends StatelessWidget {
|
||||||
|
|
||||||
static const routeName = "/addWallet";
|
static const routeName = "/addWallet";
|
||||||
|
|
||||||
final _coins = Coin.values;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
List<Coin> coins = _coins;
|
List<Coin> coins = [...Coin.values];
|
||||||
if (Platform.isIOS) {
|
coins.remove(Coin.firoTestNet);
|
||||||
coins = _coins;
|
|
||||||
}
|
|
||||||
debugPrint("BUILD: $runtimeType");
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AppBarBackButton(
|
leading: AppBarBackButton(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:math';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:bip39/src/wordlists/english.dart' as bip39wordlist;
|
import 'package:bip39/src/wordlists/english.dart' as bip39wordlist;
|
||||||
|
@ -26,6 +26,7 @@ import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
|
||||||
import 'package:stackwallet/utilities/cfcolors.dart';
|
import 'package:stackwallet/utilities/cfcolors.dart';
|
||||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
|
import 'package:stackwallet/utilities/custom_text_selection_controls.dart';
|
||||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
|
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
|
||||||
|
@ -70,25 +71,61 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
||||||
final ScrollController controller = ScrollController();
|
final ScrollController controller = ScrollController();
|
||||||
|
|
||||||
final List<TextEditingController> _controllers = [];
|
final List<TextEditingController> _controllers = [];
|
||||||
// late final TextEditingController _heightController;
|
|
||||||
final List<FormInputStatus> _inputStatuses = [];
|
final List<FormInputStatus> _inputStatuses = [];
|
||||||
|
|
||||||
// late final FocusNode _heightFocusNode;
|
|
||||||
|
|
||||||
late final BarcodeScannerInterface scanner;
|
late final BarcodeScannerInterface scanner;
|
||||||
|
|
||||||
|
late final TextSelectionControls textSelectionControls;
|
||||||
|
|
||||||
|
Future<void> onControlsPaste(TextSelectionDelegate delegate) async {
|
||||||
|
final data = await widget.clipboard.getData(Clipboard.kTextPlain);
|
||||||
|
if (data?.text == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final text = data!.text!.trim();
|
||||||
|
if (text.isEmpty || _controllers.isEmpty) {
|
||||||
|
delegate.pasteText(SelectionChangedCause.toolbar);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final words = text.split(" ");
|
||||||
|
if (words.isEmpty) {
|
||||||
|
delegate.pasteText(SelectionChangedCause.toolbar);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (words.length == 1) {
|
||||||
|
_controllers.first.text = words.first;
|
||||||
|
if (_isValidMnemonicWord(words.first.toLowerCase())) {
|
||||||
|
setState(() {
|
||||||
|
_inputStatuses.first = FormInputStatus.valid;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_inputStatuses.first = FormInputStatus.invalid;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearAndPopulateMnemonic(words);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_seedWordCount = widget.seedWordsLength;
|
_seedWordCount = widget.seedWordsLength;
|
||||||
|
|
||||||
// _heightFocusNode = FocusNode();
|
textSelectionControls = Platform.isIOS
|
||||||
|
? CustomCupertinoTextSelectionControls(onPaste: onControlsPaste)
|
||||||
|
: CustomMaterialTextSelectionControls(onPaste: onControlsPaste);
|
||||||
|
|
||||||
scanner = widget.barcodeScanner;
|
scanner = widget.barcodeScanner;
|
||||||
for (int i = 0; i < _seedWordCount; i++) {
|
for (int i = 0; i < _seedWordCount; i++) {
|
||||||
_controllers.add(TextEditingController());
|
_controllers.add(TextEditingController());
|
||||||
_inputStatuses.add(FormInputStatus.empty);
|
_inputStatuses.add(FormInputStatus.empty);
|
||||||
}
|
}
|
||||||
// _heightController = TextEditingController();
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +134,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
||||||
for (var element in _controllers) {
|
for (var element in _controllers) {
|
||||||
element.dispose();
|
element.dispose();
|
||||||
}
|
}
|
||||||
// _heightController.dispose();
|
|
||||||
// _heightFocusNode.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,6 +440,9 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
||||||
_inputStatuses[i] = FormInputStatus.empty;
|
_inputStatuses[i] = FormInputStatus.empty;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controller.animateTo(controller.position.maxScrollExtent,
|
||||||
|
duration: const Duration(milliseconds: 300), curve: Curves.decelerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -442,18 +481,8 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
// ref
|
|
||||||
// .read(shouldShowLockscreenOnResumeStateProvider.state)
|
|
||||||
// .state = false;
|
|
||||||
final qrResult = await scanner.scan();
|
final qrResult = await scanner.scan();
|
||||||
|
|
||||||
// Future<void>.delayed(
|
|
||||||
// const Duration(seconds: 2),
|
|
||||||
// () => ref
|
|
||||||
// .read(shouldShowLockscreenOnResumeStateProvider.state)
|
|
||||||
// .state = true,
|
|
||||||
// );
|
|
||||||
|
|
||||||
final results =
|
final results =
|
||||||
AddressUtils.decodeQRSeedData(qrResult.rawContent);
|
AddressUtils.decodeQRSeedData(qrResult.rawContent);
|
||||||
|
|
||||||
|
@ -474,9 +503,6 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
// ref
|
|
||||||
// .read(shouldShowLockscreenOnResumeStateProvider.state)
|
|
||||||
// .state = true;
|
|
||||||
// likely failed to get camera permissions
|
// likely failed to get camera permissions
|
||||||
Logging.instance.log("Restore wallet qr scan failed: $e",
|
Logging.instance.log("Restore wallet qr scan failed: $e",
|
||||||
level: LogLevel.Warning);
|
level: LogLevel.Warning);
|
||||||
|
@ -512,9 +538,6 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
||||||
final content = data.text!.trim();
|
final content = data.text!.trim();
|
||||||
final list = content.split(" ");
|
final list = content.split(" ");
|
||||||
_clearAndPopulateMnemonic(list);
|
_clearAndPopulateMnemonic(list);
|
||||||
controller.animateTo(controller.position.maxScrollExtent,
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
curve: Curves.decelerate);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -572,6 +595,8 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
||||||
_inputStatuses[i - 1], "$i"),
|
_inputStatuses[i - 1], "$i"),
|
||||||
autovalidateMode:
|
autovalidateMode:
|
||||||
AutovalidateMode.onUserInteraction,
|
AutovalidateMode.onUserInteraction,
|
||||||
|
selectionControls:
|
||||||
|
i == 1 ? textSelectionControls : null,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value.isEmpty) {
|
if (value.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
@ -48,7 +48,9 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
|
||||||
ref.refresh(addressBookFilterProvider);
|
ref.refresh(addressBookFilterProvider);
|
||||||
|
|
||||||
if (widget.coin == null) {
|
if (widget.coin == null) {
|
||||||
final coins = Coin.values.where((e) => !(e == Coin.epicCash)).toList();
|
List<Coin> coins =
|
||||||
|
Coin.values.where((e) => !(e == Coin.epicCash)).toList();
|
||||||
|
coins.remove(Coin.firoTestNet);
|
||||||
|
|
||||||
bool showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins;
|
bool showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ class _AddressBookFilterViewState extends ConsumerState<AddressBookFilterView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final coins = Coin.values;
|
List<Coin> coins = [...Coin.values];
|
||||||
|
coins.remove(Coin.firoTestNet);
|
||||||
|
|
||||||
bool showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins;
|
bool showTestNet = ref.read(prefsChangeNotifierProvider).showTestNetCoins;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ class CoinSelectSheet extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final maxHeight = MediaQuery.of(context).size.height * 0.60;
|
final maxHeight = MediaQuery.of(context).size.height * 0.60;
|
||||||
|
var coins_ = [...Coin.values];
|
||||||
|
coins_.remove(Coin.firoTestNet);
|
||||||
return Container(
|
return Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: CFColors.white,
|
color: CFColors.white,
|
||||||
|
@ -68,10 +70,10 @@ class CoinSelectSheet extends StatelessWidget {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: showTestNet
|
itemCount: showTestNet
|
||||||
? Coin.values.length
|
? coins_.length
|
||||||
: Coin.values.length - kTestNetCoinCount,
|
: coins_.length - kTestNetCoinCount,
|
||||||
itemBuilder: (builderContext, index) {
|
itemBuilder: (builderContext, index) {
|
||||||
final coin = Coin.values[index];
|
final coin = coins_[index];
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
child: RawMaterialButton(
|
child: RawMaterialButton(
|
||||||
|
|
|
@ -23,11 +23,12 @@ class ManageNodesView extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ManageNodesViewState extends ConsumerState<ManageNodesView> {
|
class _ManageNodesViewState extends ConsumerState<ManageNodesView> {
|
||||||
List<Coin> _coins = Coin.values;
|
List<Coin> _coins = [...Coin.values];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_coins = _coins.toList();
|
_coins = _coins.toList();
|
||||||
|
_coins.remove(Coin.firoTestNet);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,15 @@ abstract class ChangeNow {
|
||||||
static const String authority = "api.changenow.io";
|
static const String authority = "api.changenow.io";
|
||||||
static const String apiVersion = "/v1";
|
static const String apiVersion = "/v1";
|
||||||
|
|
||||||
|
/// set this to override using standard http client. Useful for testing
|
||||||
|
static http.Client? client;
|
||||||
|
|
||||||
static Uri _buildUri(String path, Map<String, dynamic>? params) {
|
static Uri _buildUri(String path, Map<String, dynamic>? params) {
|
||||||
return Uri.https(authority, apiVersion + path, params);
|
return Uri.https(authority, apiVersion + path, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<dynamic> _makeGetRequest(Uri uri) async {
|
static Future<dynamic> _makeGetRequest(Uri uri) async {
|
||||||
final client = http.Client();
|
final client = ChangeNow.client ?? http.Client();
|
||||||
try {
|
try {
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
uri,
|
uri,
|
||||||
|
@ -44,7 +47,7 @@ abstract class ChangeNow {
|
||||||
Uri uri,
|
Uri uri,
|
||||||
Map<String, String> body,
|
Map<String, String> body,
|
||||||
) async {
|
) async {
|
||||||
final client = http.Client();
|
final client = ChangeNow.client ?? http.Client();
|
||||||
try {
|
try {
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
uri,
|
uri,
|
||||||
|
@ -205,8 +208,9 @@ abstract class ChangeNow {
|
||||||
static Future<ChangeNowResponse<Decimal>> getMinimalExchangeAmount({
|
static Future<ChangeNowResponse<Decimal>> getMinimalExchangeAmount({
|
||||||
required String fromTicker,
|
required String fromTicker,
|
||||||
required String toTicker,
|
required String toTicker,
|
||||||
|
String? apiKey,
|
||||||
}) async {
|
}) async {
|
||||||
Map<String, dynamic>? params = {"api_key": kChangeNowApiKey};
|
Map<String, dynamic>? params = {"api_key": apiKey ?? kChangeNowApiKey};
|
||||||
|
|
||||||
final uri = _buildUri("/min-amount/${fromTicker}_$toTicker", params);
|
final uri = _buildUri("/min-amount/${fromTicker}_$toTicker", params);
|
||||||
|
|
||||||
|
@ -244,8 +248,9 @@ abstract class ChangeNow {
|
||||||
required String fromTicker,
|
required String fromTicker,
|
||||||
required String toTicker,
|
required String toTicker,
|
||||||
required Decimal fromAmount,
|
required Decimal fromAmount,
|
||||||
|
String? apiKey,
|
||||||
}) async {
|
}) async {
|
||||||
Map<String, dynamic> params = {"api_key": kChangeNowApiKey};
|
Map<String, dynamic> params = {"api_key": apiKey ?? kChangeNowApiKey};
|
||||||
|
|
||||||
final uri = _buildUri(
|
final uri = _buildUri(
|
||||||
"/exchange-amount/${fromAmount.toString()}/${fromTicker}_$toTicker",
|
"/exchange-amount/${fromAmount.toString()}/${fromTicker}_$toTicker",
|
||||||
|
@ -292,9 +297,10 @@ abstract class ChangeNow {
|
||||||
// to freeze estimated amount that you got in this method. Current estimated
|
// to freeze estimated amount that you got in this method. Current estimated
|
||||||
// amount would be valid until time in field "validUntil"
|
// amount would be valid until time in field "validUntil"
|
||||||
bool useRateId = true,
|
bool useRateId = true,
|
||||||
|
String? apiKey,
|
||||||
}) async {
|
}) async {
|
||||||
Map<String, dynamic> params = {
|
Map<String, dynamic> params = {
|
||||||
"api_key": kChangeNowApiKey,
|
"api_key": apiKey ?? kChangeNowApiKey,
|
||||||
"useRateId": useRateId.toString(),
|
"useRateId": useRateId.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -337,8 +343,11 @@ abstract class ChangeNow {
|
||||||
/// time and the market info gets updates, so make sure to refresh the list
|
/// time and the market info gets updates, so make sure to refresh the list
|
||||||
/// occasionally. One time per minute is sufficient.
|
/// occasionally. One time per minute is sufficient.
|
||||||
static Future<ChangeNowResponse<List<FixedRateMarket>>>
|
static Future<ChangeNowResponse<List<FixedRateMarket>>>
|
||||||
getAvailableFixedRateMarkets() async {
|
getAvailableFixedRateMarkets({
|
||||||
final uri = _buildUri("/market-info/fixed-rate/$kChangeNowApiKey", null);
|
String? apiKey,
|
||||||
|
}) async {
|
||||||
|
final uri = _buildUri(
|
||||||
|
"/market-info/fixed-rate/${apiKey ?? kChangeNowApiKey}", null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// json array is expected here
|
// json array is expected here
|
||||||
|
@ -406,6 +415,7 @@ abstract class ChangeNow {
|
||||||
String contactEmail = "",
|
String contactEmail = "",
|
||||||
String refundAddress = "",
|
String refundAddress = "",
|
||||||
String refundExtraId = "",
|
String refundExtraId = "",
|
||||||
|
String? apiKey,
|
||||||
}) async {
|
}) async {
|
||||||
final Map<String, String> map = {
|
final Map<String, String> map = {
|
||||||
"from": fromTicker,
|
"from": fromTicker,
|
||||||
|
@ -420,14 +430,14 @@ abstract class ChangeNow {
|
||||||
"refundExtraId": refundExtraId,
|
"refundExtraId": refundExtraId,
|
||||||
};
|
};
|
||||||
|
|
||||||
final uri = _buildUri("/transactions/$kChangeNowApiKey", null);
|
final uri = _buildUri("/transactions/${apiKey ?? kChangeNowApiKey}", null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// simple json object is expected here
|
// simple json object is expected here
|
||||||
final json = await _makePostRequest(uri, map);
|
final json = await _makePostRequest(uri, map);
|
||||||
|
|
||||||
// pass in date to prevent using default 1970 date
|
// pass in date to prevent using default 1970 date
|
||||||
json["date"] = DateTime.now();
|
json["date"] = DateTime.now().toString();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final value = ExchangeTransaction.fromJson(
|
final value = ExchangeTransaction.fromJson(
|
||||||
|
@ -468,6 +478,7 @@ abstract class ChangeNow {
|
||||||
String contactEmail = "",
|
String contactEmail = "",
|
||||||
String refundAddress = "",
|
String refundAddress = "",
|
||||||
String refundExtraId = "",
|
String refundExtraId = "",
|
||||||
|
String? apiKey,
|
||||||
}) async {
|
}) async {
|
||||||
final Map<String, String> map = {
|
final Map<String, String> map = {
|
||||||
"from": fromTicker,
|
"from": fromTicker,
|
||||||
|
@ -483,14 +494,15 @@ abstract class ChangeNow {
|
||||||
"rateId": rateId,
|
"rateId": rateId,
|
||||||
};
|
};
|
||||||
|
|
||||||
final uri = _buildUri("/transactions/fixed-rate/$kChangeNowApiKey", null);
|
final uri = _buildUri(
|
||||||
|
"/transactions/fixed-rate/${apiKey ?? kChangeNowApiKey}", null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// simple json object is expected here
|
// simple json object is expected here
|
||||||
final json = await _makePostRequest(uri, map);
|
final json = await _makePostRequest(uri, map);
|
||||||
|
|
||||||
// pass in date to prevent using default 1970 date
|
// pass in date to prevent using default 1970 date
|
||||||
json["date"] = DateTime.now();
|
json["date"] = DateTime.now().toString();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final value = ExchangeTransaction.fromJson(
|
final value = ExchangeTransaction.fromJson(
|
||||||
|
@ -520,8 +532,10 @@ abstract class ChangeNow {
|
||||||
static Future<ChangeNowResponse<ExchangeTransactionStatus>>
|
static Future<ChangeNowResponse<ExchangeTransactionStatus>>
|
||||||
getTransactionStatus({
|
getTransactionStatus({
|
||||||
required String id,
|
required String id,
|
||||||
|
String? apiKey,
|
||||||
}) async {
|
}) async {
|
||||||
final uri = _buildUri("/transactions/$id/$kChangeNowApiKey", null);
|
final uri =
|
||||||
|
_buildUri("/transactions/$id/${apiKey ?? kChangeNowApiKey}", null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// simple json object is expected here
|
// simple json object is expected here
|
||||||
|
|
|
@ -726,7 +726,7 @@ Future<String> _getMintScriptWrapper(
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _setTestnetWrapper(bool isTestnet) async {
|
Future<void> _setTestnetWrapper(bool isTestnet) async {
|
||||||
setTestnet(isTestnet);
|
// setTestnet(isTestnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a single instance of a firo wallet
|
/// Handles a single instance of a firo wallet
|
||||||
|
@ -2893,6 +2893,7 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
if (Platform.environment["FLUTTER_TEST"] == "true" || integrationTestFlag) {
|
if (Platform.environment["FLUTTER_TEST"] == "true" || integrationTestFlag) {
|
||||||
perBatch = 10;
|
perBatch = 10;
|
||||||
|
numberOfThreads = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
final receiveDerivationsString =
|
final receiveDerivationsString =
|
||||||
|
@ -3818,7 +3819,7 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
|
|
||||||
var sendIndex = 1;
|
var sendIndex = 1;
|
||||||
if (tx["vout"][0]["value"] != null &&
|
if (tx["vout"][0]["value"] != null &&
|
||||||
tx["vout"][0]["value"] as int > 0) {
|
Decimal.parse(tx["vout"][0]["value"].toString()) > Decimal.zero) {
|
||||||
sendIndex = 0;
|
sendIndex = 0;
|
||||||
}
|
}
|
||||||
tx["amount"] = tx["vout"][sendIndex]["value"];
|
tx["amount"] = tx["vout"][sendIndex]["value"];
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cw_core/monero_amount_format.dart';
|
||||||
import 'package:cw_core/monero_transaction_priority.dart';
|
import 'package:cw_core/monero_transaction_priority.dart';
|
||||||
import 'package:cw_core/node.dart';
|
import 'package:cw_core/node.dart';
|
||||||
import 'package:cw_core/pending_transaction.dart';
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
|
import 'package:cw_core/sync_status.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_credentials.dart';
|
import 'package:cw_core/wallet_credentials.dart';
|
||||||
|
@ -24,6 +25,7 @@ import 'package:flutter_libmonero/monero/monero.dart';
|
||||||
import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output;
|
import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output;
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:stackwallet/hive/db.dart';
|
import 'package:stackwallet/hive/db.dart';
|
||||||
|
@ -121,10 +123,10 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
||||||
|
|
||||||
// TODO: is this sync call needed? Do we need to notify ui here?
|
// TODO: is this sync call needed? Do we need to notify ui here?
|
||||||
walletBase?.startSync();
|
await walletBase?.startSync();
|
||||||
|
|
||||||
if (shouldRefresh) {
|
if (shouldRefresh) {
|
||||||
refresh();
|
await refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,13 +144,37 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
Future<List<String>> get mnemonic => _getMnemonicList();
|
Future<List<String>> get mnemonic => _getMnemonicList();
|
||||||
|
|
||||||
Future<int> get currentNodeHeight async {
|
Future<int> get currentNodeHeight async {
|
||||||
int currentHeight = await getNodeHeight();
|
try {
|
||||||
|
if (walletBase!.syncStatus! is SyncedSyncStatus &&
|
||||||
|
walletBase!.syncStatus!.progress() == 1.0) {
|
||||||
|
return await walletBase!.getNodeHeight();
|
||||||
|
}
|
||||||
|
} catch (e, s) {}
|
||||||
|
int _height = -1;
|
||||||
|
try {
|
||||||
|
_height = (walletBase!.syncStatus as SyncingSyncStatus).height;
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log("$e $s", level: LogLevel.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
int blocksRemaining = -1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
blocksRemaining =
|
||||||
|
(walletBase!.syncStatus as SyncingSyncStatus).blocksLeft;
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log("$e $s", level: LogLevel.Warning);
|
||||||
|
}
|
||||||
|
int currentHeight = _height + blocksRemaining;
|
||||||
|
if (_height == -1 || blocksRemaining == -1) {
|
||||||
|
currentHeight = int64MaxValue;
|
||||||
|
}
|
||||||
final cachedHeight = DB.instance
|
final cachedHeight = DB.instance
|
||||||
.get<dynamic>(boxName: walletId, key: "storedNodeHeight") as int? ??
|
.get<dynamic>(boxName: walletId, key: "storedNodeHeight") as int? ??
|
||||||
0;
|
0;
|
||||||
|
|
||||||
if (currentHeight > cachedHeight) {
|
if (currentHeight > cachedHeight && currentHeight != int64MaxValue) {
|
||||||
DB.instance.put<dynamic>(
|
await DB.instance.put<dynamic>(
|
||||||
boxName: walletId, key: "storedNodeHeight", value: currentHeight);
|
boxName: walletId, key: "storedNodeHeight", value: currentHeight);
|
||||||
return currentHeight;
|
return currentHeight;
|
||||||
} else {
|
} else {
|
||||||
|
@ -156,29 +182,34 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int get currentSyncingHeight {
|
Future<int> get currentSyncingHeight async {
|
||||||
//TODO return the tip of the monero blockchain
|
//TODO return the tip of the monero blockchain
|
||||||
final syncingHeight = getSyncingHeight();
|
try {
|
||||||
|
if (walletBase!.syncStatus! is SyncedSyncStatus &&
|
||||||
|
walletBase!.syncStatus!.progress() == 1.0) {
|
||||||
|
Logging.instance
|
||||||
|
.log("currentSyncingHeight lol", level: LogLevel.Warning);
|
||||||
|
return getSyncingHeight();
|
||||||
|
}
|
||||||
|
} catch (e, s) {}
|
||||||
|
int syncingHeight = -1;
|
||||||
|
try {
|
||||||
|
syncingHeight = (walletBase!.syncStatus as SyncingSyncStatus).height;
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log("$e $s", level: LogLevel.Warning);
|
||||||
|
}
|
||||||
final cachedHeight =
|
final cachedHeight =
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: "storedSyncingHeight")
|
DB.instance.get<dynamic>(boxName: walletId, key: "storedSyncingHeight")
|
||||||
as int? ??
|
as int? ??
|
||||||
0;
|
0;
|
||||||
|
|
||||||
if (syncingHeight > cachedHeight) {
|
if (syncingHeight > cachedHeight) {
|
||||||
DB.instance.put<dynamic>(
|
await DB.instance.put<dynamic>(
|
||||||
boxName: walletId, key: "storedSyncingHeight", value: syncingHeight);
|
boxName: walletId, key: "storedSyncingHeight", value: syncingHeight);
|
||||||
return syncingHeight;
|
return syncingHeight;
|
||||||
} else {
|
} else {
|
||||||
return cachedHeight;
|
return cachedHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try {
|
|
||||||
// final result = await _electrumXClient.getBlockHeadTip();
|
|
||||||
// return result["height"];
|
|
||||||
// } catch (e, s) {
|
|
||||||
// Logging.instance.log("Exception caught in chainHeight: $e\n$s");
|
|
||||||
// return -1;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateStoredChainHeight({required int newHeight}) async {
|
Future<void> updateStoredChainHeight({required int newHeight}) async {
|
||||||
|
@ -270,63 +301,78 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
|
|
||||||
Timer? syncPercentTimer;
|
Timer? syncPercentTimer;
|
||||||
|
|
||||||
void stopSyncPercentTimer() {
|
Mutex syncHeightMutex = Mutex();
|
||||||
|
Future<void> stopSyncPercentTimer() async {
|
||||||
syncPercentTimer?.cancel();
|
syncPercentTimer?.cancel();
|
||||||
syncPercentTimer = null;
|
syncPercentTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void startSyncPercentTimer() {
|
Future<void> startSyncPercentTimer() async {
|
||||||
|
if (syncPercentTimer != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
syncPercentTimer?.cancel();
|
syncPercentTimer?.cancel();
|
||||||
GlobalEventBus.instance
|
GlobalEventBus.instance
|
||||||
.fire(RefreshPercentChangedEvent(highestPercentCached, walletId));
|
.fire(RefreshPercentChangedEvent(highestPercentCached, walletId));
|
||||||
syncPercentTimer = Timer.periodic(const Duration(seconds: 3), (_) async {
|
syncPercentTimer = Timer.periodic(const Duration(seconds: 30), (_) async {
|
||||||
// int restoreheight = walletBase!.walletInfo.restoreHeight ?? 0;
|
if (syncHeightMutex.isLocked) {
|
||||||
int _height = currentSyncingHeight;
|
|
||||||
int _currentHeight = await currentNodeHeight;
|
|
||||||
|
|
||||||
final int blocksRemaining = _currentHeight - _height;
|
|
||||||
|
|
||||||
GlobalEventBus.instance
|
|
||||||
.fire(BlocksRemainingEvent(blocksRemaining, walletId));
|
|
||||||
|
|
||||||
if (blocksRemaining <= 1 && _currentHeight > 0 && _height > 0) {
|
|
||||||
stopSyncPercentTimer();
|
|
||||||
GlobalEventBus.instance.fire(
|
|
||||||
WalletSyncStatusChangedEvent(
|
|
||||||
WalletSyncStatus.synced,
|
|
||||||
walletId,
|
|
||||||
coin,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await syncHeightMutex.protect(() async {
|
||||||
|
// int restoreheight = walletBase!.walletInfo.restoreHeight ?? 0;
|
||||||
|
int _height = await currentSyncingHeight;
|
||||||
|
int _currentHeight = await currentNodeHeight;
|
||||||
|
double progress = 0;
|
||||||
|
try {
|
||||||
|
progress = walletBase!.syncStatus!.progress();
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log("$e $s", level: LogLevel.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
// for some reason this can be 0 which screws up the percent calculation
|
final int blocksRemaining = _currentHeight - _height;
|
||||||
// int64MaxValue is NOT the best value to use here
|
|
||||||
if (_currentHeight < 1) {
|
|
||||||
_currentHeight = int64MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_height < 1) {
|
GlobalEventBus.instance
|
||||||
_height = 1;
|
.fire(BlocksRemainingEvent(blocksRemaining, walletId));
|
||||||
}
|
|
||||||
|
|
||||||
double restorePercent = (_height / _currentHeight).clamp(0.0, 1.0);
|
if (progress == 1 && _currentHeight > 0 && _height > 0) {
|
||||||
double highestPercent = highestPercentCached;
|
await stopSyncPercentTimer();
|
||||||
|
GlobalEventBus.instance.fire(
|
||||||
|
WalletSyncStatusChangedEvent(
|
||||||
|
WalletSyncStatus.synced,
|
||||||
|
walletId,
|
||||||
|
coin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Logging.instance.log(
|
// for some reason this can be 0 which screws up the percent calculation
|
||||||
"currentSyncingHeight: $_height, nodeHeight: $_currentHeight, restorePercent: $restorePercent, highestPercentCached: $highestPercentCached",
|
// int64MaxValue is NOT the best value to use here
|
||||||
level: LogLevel.Info);
|
if (_currentHeight < 1) {
|
||||||
|
_currentHeight = int64MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (restorePercent > 0 && restorePercent <= 1) {
|
if (_height < 1) {
|
||||||
if (restorePercent > highestPercent) {
|
_height = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
double restorePercent = progress;
|
||||||
|
double highestPercent = highestPercentCached;
|
||||||
|
|
||||||
|
Logging.instance.log(
|
||||||
|
"currentSyncingHeight: $_height, nodeHeight: $_currentHeight, restorePercent: $restorePercent, highestPercentCached: $highestPercentCached",
|
||||||
|
level: LogLevel.Info);
|
||||||
|
|
||||||
|
if (restorePercent > 0 && restorePercent <= 1) {
|
||||||
|
// if (restorePercent > highestPercent) {
|
||||||
highestPercent = restorePercent;
|
highestPercent = restorePercent;
|
||||||
highestPercentCached = restorePercent;
|
highestPercentCached = restorePercent;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
GlobalEventBus.instance
|
GlobalEventBus.instance
|
||||||
.fire(RefreshPercentChangedEvent(highestPercent, walletId));
|
.fire(RefreshPercentChangedEvent(highestPercent, walletId));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +402,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startSyncPercentTimer();
|
await startSyncPercentTimer();
|
||||||
GlobalEventBus.instance.fire(
|
GlobalEventBus.instance.fire(
|
||||||
WalletSyncStatusChangedEvent(
|
WalletSyncStatusChangedEvent(
|
||||||
WalletSyncStatus.syncing,
|
WalletSyncStatus.syncing,
|
||||||
|
@ -365,26 +411,30 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final int _currentSyncingHeight = currentSyncingHeight;
|
final int _currentSyncingHeight = await currentSyncingHeight;
|
||||||
final int storedHeight = storedChainHeight;
|
final int storedHeight = storedChainHeight;
|
||||||
int _currentNodeHeight = await walletBase?.getNodeHeight() ?? 0;
|
int _currentNodeHeight = await currentNodeHeight;
|
||||||
if (_currentNodeHeight < 1) {
|
|
||||||
_currentNodeHeight = int64MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_fetchTransactionData();
|
double progress = 0;
|
||||||
|
try {
|
||||||
|
progress = (walletBase!.syncStatus!).progress();
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log("$e $s", level: LogLevel.Warning);
|
||||||
|
}
|
||||||
|
await _fetchTransactionData();
|
||||||
|
|
||||||
bool stillSyncing = false;
|
bool stillSyncing = false;
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"storedHeight: $storedHeight, _currentSyncingHeight: $_currentSyncingHeight, _currentNodeHeight: $_currentNodeHeight",
|
"storedHeight: $storedHeight, _currentSyncingHeight: $_currentSyncingHeight, _currentNodeHeight: $_currentNodeHeight, progress: $progress, issynced: ${await walletBase!.isConnected()}",
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
if (storedHeight + 10 < _currentNodeHeight) {
|
|
||||||
|
if (progress < 1.0) {
|
||||||
stillSyncing = true;
|
stillSyncing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentSyncingHeight != storedHeight) {
|
if (_currentSyncingHeight > storedHeight) {
|
||||||
// 0 is returned from monero as I assume an error?????
|
// 0 is returned from monero as I assume an error?????
|
||||||
if (_currentSyncingHeight != 0) {
|
if (_currentSyncingHeight > 0) {
|
||||||
// 0 failed to fetch current height???
|
// 0 failed to fetch current height???
|
||||||
await updateStoredChainHeight(newHeight: _currentSyncingHeight);
|
await updateStoredChainHeight(newHeight: _currentSyncingHeight);
|
||||||
}
|
}
|
||||||
|
@ -403,19 +453,11 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
"Failed to call _generateAddressForChain(0, $curIndex): $e\n$s",
|
"Failed to call _generateAddressForChain(0, $curIndex): $e\n$s",
|
||||||
level: LogLevel.Error);
|
level: LogLevel.Error);
|
||||||
}
|
}
|
||||||
//
|
final newTxData = await _fetchTransactionData();
|
||||||
final newTxData = _fetchTransactionData();
|
|
||||||
// final feeObj = _getFees();
|
|
||||||
//
|
|
||||||
_transactionData = Future(() => newTxData);
|
_transactionData = Future(() => newTxData);
|
||||||
//
|
|
||||||
// this._feeObject = Future(() => feeObj);
|
|
||||||
// this._utxoData = Future(() => newUtxoData);
|
|
||||||
//
|
|
||||||
// await getAllTxsToWatch(await newTxData);
|
|
||||||
|
|
||||||
if (isActive || shouldAutoSync) {
|
if (isActive || shouldAutoSync) {
|
||||||
timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
|
timer ??= Timer.periodic(const Duration(seconds: 60), (timer) async {
|
||||||
debugPrint("run timer");
|
debugPrint("run timer");
|
||||||
//TODO: check for new data and refresh if needed. if monero even needs this
|
//TODO: check for new data and refresh if needed. if monero even needs this
|
||||||
// chain height check currently broken
|
// chain height check currently broken
|
||||||
|
@ -450,7 +492,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
refreshMutex = false;
|
refreshMutex = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopSyncPercentTimer();
|
await stopSyncPercentTimer();
|
||||||
GlobalEventBus.instance.fire(
|
GlobalEventBus.instance.fire(
|
||||||
WalletSyncStatusChangedEvent(
|
WalletSyncStatusChangedEvent(
|
||||||
WalletSyncStatus.synced,
|
WalletSyncStatus.synced,
|
||||||
|
@ -461,7 +503,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
refreshMutex = false;
|
refreshMutex = false;
|
||||||
} catch (error, strace) {
|
} catch (error, strace) {
|
||||||
refreshMutex = false;
|
refreshMutex = false;
|
||||||
stopSyncPercentTimer();
|
await stopSyncPercentTimer();
|
||||||
GlobalEventBus.instance.fire(
|
GlobalEventBus.instance.fire(
|
||||||
NodeConnectionStatusChangedEvent(
|
NodeConnectionStatusChangedEvent(
|
||||||
NodeConnectionStatus.disconnected,
|
NodeConnectionStatus.disconnected,
|
||||||
|
@ -500,7 +542,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> exit() async {
|
Future<void> exit() async {
|
||||||
stopSyncPercentTimer();
|
await stopSyncPercentTimer();
|
||||||
_hasCalledExit = true;
|
_hasCalledExit = true;
|
||||||
isActive = false;
|
isActive = false;
|
||||||
await walletBase?.save(prioritySave: true);
|
await walletBase?.save(prioritySave: true);
|
||||||
|
@ -552,6 +594,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _generateAddressForChain(int chain, int index) async {
|
Future<String> _generateAddressForChain(int chain, int index) async {
|
||||||
|
//
|
||||||
String address = walletBase!.getTransactionAddress(chain, index);
|
String address = walletBase!.getTransactionAddress(chain, index);
|
||||||
|
|
||||||
return address;
|
return address;
|
||||||
|
@ -688,7 +731,6 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
await walletBase?.connectToNode(
|
await walletBase?.connectToNode(
|
||||||
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
||||||
await walletBase?.startSync();
|
await walletBase?.startSync();
|
||||||
walletBase?.getNodeHeight();
|
|
||||||
await DB.instance
|
await DB.instance
|
||||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
||||||
|
|
||||||
|
@ -806,7 +848,6 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
String? password;
|
String? password;
|
||||||
try {
|
try {
|
||||||
password = await keysStorage?.getWalletPassword(walletName: _walletId);
|
password = await keysStorage?.getWalletPassword(walletName: _walletId);
|
||||||
debugPrint("password $password");
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
debugPrint("Exception was thrown $e $s");
|
debugPrint("Exception was thrown $e $s");
|
||||||
throw Exception("Password not found $e, $s");
|
throw Exception("Password not found $e, $s");
|
||||||
|
@ -999,7 +1040,6 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
await walletBase?.connectToNode(
|
await walletBase?.connectToNode(
|
||||||
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
||||||
await walletBase?.rescan(height: credentials.height);
|
await walletBase?.rescan(height: credentials.height);
|
||||||
await walletBase?.getNodeHeight();
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"Exception rethrown from recoverFromMnemonic(): $e\n$s",
|
"Exception rethrown from recoverFromMnemonic(): $e\n$s",
|
||||||
|
@ -1113,13 +1153,12 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
moneroAutosaveTimer = null;
|
moneroAutosaveTimer = null;
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
timer = null;
|
timer = null;
|
||||||
stopSyncPercentTimer();
|
await stopSyncPercentTimer();
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
String? password;
|
String? password;
|
||||||
try {
|
try {
|
||||||
password =
|
password =
|
||||||
await keysStorage?.getWalletPassword(walletName: _walletId);
|
await keysStorage?.getWalletPassword(walletName: _walletId);
|
||||||
debugPrint("password $password");
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
debugPrint("Exception was thrown $e $s");
|
debugPrint("Exception was thrown $e $s");
|
||||||
throw Exception("Password not found $e, $s");
|
throw Exception("Password not found $e, $s");
|
||||||
|
@ -1131,9 +1170,9 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
final host = Uri.parse(node.host).host;
|
final host = Uri.parse(node.host).host;
|
||||||
await walletBase?.connectToNode(
|
await walletBase?.connectToNode(
|
||||||
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
||||||
walletBase?.startSync();
|
await walletBase?.startSync();
|
||||||
}
|
}
|
||||||
refresh();
|
await refresh();
|
||||||
}
|
}
|
||||||
this.isActive = isActive;
|
this.isActive = isActive;
|
||||||
};
|
};
|
||||||
|
|
|
@ -167,7 +167,7 @@ class NodeService extends ChangeNotifier {
|
||||||
final json = jsonDecode(response.body) as Map;
|
final json = jsonDecode(response.body) as Map;
|
||||||
final result = jsonDecode(json['result'] as String);
|
final result = jsonDecode(json['result'] as String);
|
||||||
final map = jsonDecode(result as String);
|
final map = jsonDecode(result as String);
|
||||||
Logging.instance.log("Rylee: $map", level: LogLevel.Info);
|
Logging.instance.log(map, level: LogLevel.Info);
|
||||||
|
|
||||||
for (final coin in Coin.values) {
|
for (final coin in Coin.values) {
|
||||||
final nodeList = List<Map<String, dynamic>>.from(
|
final nodeList = List<Map<String, dynamic>>.from(
|
||||||
|
|
22
lib/utilities/custom_text_selection_controls.dart
Normal file
22
lib/utilities/custom_text_selection_controls.dart
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CustomMaterialTextSelectionControls
|
||||||
|
extends MaterialTextSelectionControls {
|
||||||
|
CustomMaterialTextSelectionControls({required this.onPaste});
|
||||||
|
ValueChanged<TextSelectionDelegate> onPaste;
|
||||||
|
@override
|
||||||
|
Future<void> handlePaste(final TextSelectionDelegate delegate) async {
|
||||||
|
return onPaste(delegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomCupertinoTextSelectionControls
|
||||||
|
extends CupertinoTextSelectionControls {
|
||||||
|
CustomCupertinoTextSelectionControls({required this.onPaste});
|
||||||
|
ValueChanged<TextSelectionDelegate> onPaste;
|
||||||
|
@override
|
||||||
|
Future<void> handlePaste(final TextSelectionDelegate delegate) async {
|
||||||
|
return onPaste(delegate);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,8 @@ enum Coin {
|
||||||
firoTestNet,
|
firoTestNet,
|
||||||
}
|
}
|
||||||
|
|
||||||
const int kTestNetCoinCount = 3;
|
// remove firotestnet for now
|
||||||
|
const int kTestNetCoinCount = 2;
|
||||||
|
|
||||||
extension CoinExt on Coin {
|
extension CoinExt on Coin {
|
||||||
String get prettyName {
|
String get prettyName {
|
||||||
|
|
|
@ -11,7 +11,7 @@ description: Stack Wallet
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.4.31+38
|
version: 1.4.35+43
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.0 <3.0.0"
|
sdk: ">=2.17.0 <3.0.0"
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
// import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:hive_test/hive_test.dart';
|
// import 'package:hive_test/hive_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
setUp(() async {
|
// setUp(() async {
|
||||||
await setUpTestHive();
|
// await setUpTestHive();
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
// no migration to test yet
|
// // no migration to test yet
|
||||||
|
//
|
||||||
tearDown(() async {
|
// tearDown(() async {
|
||||||
await tearDownTestHive();
|
// await tearDownTestHive();
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
31326
test/services/change_now/change_now_sample_data.dart
Normal file
31326
test/services/change_now/change_now_sample_data.dart
Normal file
File diff suppressed because it is too large
Load diff
482
test/services/change_now/change_now_test.dart
Normal file
482
test/services/change_now/change_now_test.dart
Normal file
|
@ -0,0 +1,482 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:decimal/decimal.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:mockito/annotations.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:stackwallet/models/exchange/change_now/change_now_response.dart';
|
||||||
|
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
|
||||||
|
import 'package:stackwallet/services/change_now/change_now.dart';
|
||||||
|
|
||||||
|
import 'change_now_sample_data.dart';
|
||||||
|
import 'change_now_test.mocks.dart';
|
||||||
|
|
||||||
|
@GenerateMocks([Client])
|
||||||
|
void main() {
|
||||||
|
group("getAvailableCurrencies", () {
|
||||||
|
test("getAvailableCurrencies succeeds without options", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/currencies"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response(jsonEncode(availableCurrenciesJSON), 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getAvailableCurrencies();
|
||||||
|
|
||||||
|
expect(result.exception, null);
|
||||||
|
expect(result.value == null, false);
|
||||||
|
expect(result.value!.length, 538);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getAvailableCurrencies succeeds with active option", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/currencies?active=true"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response(jsonEncode(availableCurrenciesJSONActive), 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getAvailableCurrencies(active: true);
|
||||||
|
|
||||||
|
expect(result.exception, null);
|
||||||
|
expect(result.value == null, false);
|
||||||
|
expect(result.value!.length, 531);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getAvailableCurrencies succeeds with fixedRate option", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/currencies?fixedRate=true"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response(jsonEncode(availableCurrenciesJSONFixedRate), 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getAvailableCurrencies(fixedRate: true);
|
||||||
|
|
||||||
|
expect(result.exception, null);
|
||||||
|
expect(result.value == null, false);
|
||||||
|
expect(result.value!.length, 410);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getAvailableCurrencies succeeds with fixedRate and active options",
|
||||||
|
() async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/currencies?fixedRate=true&active=true"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response(jsonEncode(availableCurrenciesJSONActiveFixedRate), 200));
|
||||||
|
|
||||||
|
final result =
|
||||||
|
await ChangeNow.getAvailableCurrencies(active: true, fixedRate: true);
|
||||||
|
|
||||||
|
expect(result.exception, null);
|
||||||
|
expect(result.value == null, false);
|
||||||
|
expect(result.value!.length, 410);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"getAvailableCurrencies fails with ChangeNowExceptionType.serializeResponseError",
|
||||||
|
() async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/currencies"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response('{"some unexpected": "but valid json data"}', 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getAvailableCurrencies();
|
||||||
|
|
||||||
|
expect(result.exception!.type,
|
||||||
|
ChangeNowExceptionType.serializeResponseError);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getAvailableCurrencies fails for any other reason", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/currencies"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async => Response("", 400));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getAvailableCurrencies();
|
||||||
|
|
||||||
|
expect(result.exception!.type, ChangeNowExceptionType.generic);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group("getPairedCurrencies", () {
|
||||||
|
test("getPairedCurrencies succeeds without fixedRate option", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/currencies-to/XMR"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response(jsonEncode(getPairedCurrenciesJSON), 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getPairedCurrencies(ticker: "XMR");
|
||||||
|
|
||||||
|
expect(result.exception, null);
|
||||||
|
expect(result.value == null, false);
|
||||||
|
expect(result.value!.length, 537);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getPairedCurrencies succeeds with fixedRate option", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/currencies-to/XMR?fixedRate=true"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response(jsonEncode(getPairedCurrenciesJSONFixedRate), 200));
|
||||||
|
|
||||||
|
final result =
|
||||||
|
await ChangeNow.getPairedCurrencies(ticker: "XMR", fixedRate: true);
|
||||||
|
|
||||||
|
expect(result.exception, null);
|
||||||
|
expect(result.value == null, false);
|
||||||
|
expect(result.value!.length, 410);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"getPairedCurrencies fails with ChangeNowExceptionType.serializeResponseError",
|
||||||
|
() async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/currencies-to/XMR"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response('[{"some unexpected": "but valid json data"}]', 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getPairedCurrencies(ticker: "XMR");
|
||||||
|
|
||||||
|
expect(result.exception!.type,
|
||||||
|
ChangeNowExceptionType.serializeResponseError);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getPairedCurrencies fails for any other reason", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/currencies"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async => Response("", 400));
|
||||||
|
|
||||||
|
final result =
|
||||||
|
await ChangeNow.getPairedCurrencies(ticker: "XMR", fixedRate: true);
|
||||||
|
|
||||||
|
expect(result.exception!.type, ChangeNowExceptionType.generic);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group("getMinimalExchangeAmount", () {
|
||||||
|
test("getMinimalExchangeAmount succeeds", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer(
|
||||||
|
(realInvocation) async => Response('{"minAmount": 42}', 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getMinimalExchangeAmount(
|
||||||
|
fromTicker: "xmr",
|
||||||
|
toTicker: "btc",
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception, null);
|
||||||
|
expect(result.value == null, false);
|
||||||
|
expect(result.value, Decimal.fromInt(42));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"getMinimalExchangeAmount fails with ChangeNowExceptionType.serializeResponseError",
|
||||||
|
() async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async => Response('{"error": 42}', 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getMinimalExchangeAmount(
|
||||||
|
fromTicker: "xmr",
|
||||||
|
toTicker: "btc",
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception!.type,
|
||||||
|
ChangeNowExceptionType.serializeResponseError);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getMinimalExchangeAmount fails for any other reason", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async => Response('', 400));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getMinimalExchangeAmount(
|
||||||
|
fromTicker: "xmr",
|
||||||
|
toTicker: "btc",
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception!.type, ChangeNowExceptionType.generic);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group("getEstimatedFixedRateExchangeAmount", () {
|
||||||
|
test("getEstimatedFixedRateExchangeAmount succeeds", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response(jsonEncode(estFixedRateExchangeAmountJSON), 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getEstimatedFixedRateExchangeAmount(
|
||||||
|
fromTicker: "xmr",
|
||||||
|
toTicker: "btc",
|
||||||
|
fromAmount: Decimal.fromInt(10),
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception, null);
|
||||||
|
expect(result.value == null, false);
|
||||||
|
expect(result.value.toString(),
|
||||||
|
'EstimatedExchangeAmount: {estimatedAmount: 0.07271053, transactionSpeedForecast: 10-60, warningMessage: null, rateId: 1t2W5KBPqhycSJVYpaNZzYWLfMr0kSFe, networkFee: 0.00002408}');
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"getEstimatedFixedRateExchangeAmount fails with ChangeNowExceptionType.serializeResponseError",
|
||||||
|
() async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async => Response('{"error": 42}', 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getEstimatedFixedRateExchangeAmount(
|
||||||
|
fromTicker: "xmr",
|
||||||
|
toTicker: "btc",
|
||||||
|
fromAmount: Decimal.fromInt(10),
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception!.type,
|
||||||
|
ChangeNowExceptionType.serializeResponseError);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getEstimatedFixedRateExchangeAmount fails for any other reason",
|
||||||
|
() async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async => Response('', 400));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getEstimatedFixedRateExchangeAmount(
|
||||||
|
fromTicker: "xmr",
|
||||||
|
toTicker: "btc",
|
||||||
|
fromAmount: Decimal.fromInt(10),
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception!.type, ChangeNowExceptionType.generic);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group("getAvailableFixedRateMarkets", () {
|
||||||
|
test("getAvailableFixedRateMarkets succeeds", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/market-info/fixed-rate/testAPIKEY"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response(jsonEncode(fixedRateMarketsJSON), 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getAvailableFixedRateMarkets(
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception, null);
|
||||||
|
expect(result.value == null, false);
|
||||||
|
expect(result.value!.length, 237);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"getAvailableFixedRateMarkets fails with ChangeNowExceptionType.serializeResponseError",
|
||||||
|
() async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/market-info/fixed-rate/testAPIKEY"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async => Response('{"error": 42}', 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getAvailableFixedRateMarkets(
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception!.type,
|
||||||
|
ChangeNowExceptionType.serializeResponseError);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getAvailableFixedRateMarkets fails for any other reason", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://api.changenow.io/v1/market-info/fixed-rate/testAPIKEY"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
)).thenAnswer((realInvocation) async => Response('', 400));
|
||||||
|
|
||||||
|
final result = await ChangeNow.getAvailableFixedRateMarkets(
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception!.type, ChangeNowExceptionType.generic);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group("createStandardExchangeTransaction", () {
|
||||||
|
test("createStandardExchangeTransaction succeeds", () async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.post(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/transactions/testAPIKEY"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body:
|
||||||
|
'{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}',
|
||||||
|
encoding: null,
|
||||||
|
)).thenAnswer((realInvocation) async =>
|
||||||
|
Response(jsonEncode(createStandardTransactionResponse), 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.createStandardExchangeTransaction(
|
||||||
|
fromTicker: "xmr",
|
||||||
|
toTicker: "btc",
|
||||||
|
receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5",
|
||||||
|
amount: Decimal.parse("0.3"),
|
||||||
|
refundAddress:
|
||||||
|
"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H",
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception, null);
|
||||||
|
expect(result.value == null, false);
|
||||||
|
expect(result.value, isA<ExchangeTransaction>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"createStandardExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError",
|
||||||
|
() async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.post(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/transactions/testAPIKEY"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body:
|
||||||
|
'{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}',
|
||||||
|
encoding: null,
|
||||||
|
)).thenAnswer((realInvocation) async => Response('{"error": 42}', 200));
|
||||||
|
|
||||||
|
final result = await ChangeNow.createStandardExchangeTransaction(
|
||||||
|
fromTicker: "xmr",
|
||||||
|
toTicker: "btc",
|
||||||
|
receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5",
|
||||||
|
amount: Decimal.parse("0.3"),
|
||||||
|
refundAddress:
|
||||||
|
"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H",
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception!.type,
|
||||||
|
ChangeNowExceptionType.serializeResponseError);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("createStandardExchangeTransaction fails for any other reason",
|
||||||
|
() async {
|
||||||
|
final client = MockClient();
|
||||||
|
ChangeNow.client = client;
|
||||||
|
|
||||||
|
when(client.post(
|
||||||
|
Uri.parse("https://api.changenow.io/v1/transactions/testAPIKEY"),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body:
|
||||||
|
'{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}',
|
||||||
|
encoding: null,
|
||||||
|
)).thenAnswer((realInvocation) async => Response('', 400));
|
||||||
|
|
||||||
|
final result = await ChangeNow.createStandardExchangeTransaction(
|
||||||
|
fromTicker: "xmr",
|
||||||
|
toTicker: "btc",
|
||||||
|
receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5",
|
||||||
|
amount: Decimal.parse("0.3"),
|
||||||
|
refundAddress:
|
||||||
|
"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H",
|
||||||
|
apiKey: "testAPIKEY",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.exception!.type, ChangeNowExceptionType.generic);
|
||||||
|
expect(result.value == null, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
108
test/services/change_now/change_now_test.mocks.dart
Normal file
108
test/services/change_now/change_now_test.mocks.dart
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// Mocks generated by Mockito 5.2.0 from annotations
|
||||||
|
// in stackwallet/test/services/change_now_test.dart.
|
||||||
|
// Do not manually edit this file.
|
||||||
|
|
||||||
|
import 'dart:async' as _i5;
|
||||||
|
import 'dart:convert' as _i6;
|
||||||
|
import 'dart:typed_data' as _i7;
|
||||||
|
|
||||||
|
import 'package:http/src/base_request.dart' as _i8;
|
||||||
|
import 'package:http/src/client.dart' as _i4;
|
||||||
|
import 'package:http/src/response.dart' as _i2;
|
||||||
|
import 'package:http/src/streamed_response.dart' as _i3;
|
||||||
|
import 'package:mockito/mockito.dart' as _i1;
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: avoid_redundant_argument_values
|
||||||
|
// ignore_for_file: avoid_setters_without_getters
|
||||||
|
// ignore_for_file: comment_references
|
||||||
|
// ignore_for_file: implementation_imports
|
||||||
|
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
// ignore_for_file: unnecessary_parenthesis
|
||||||
|
// ignore_for_file: camel_case_types
|
||||||
|
|
||||||
|
class _FakeResponse_0 extends _i1.Fake implements _i2.Response {}
|
||||||
|
|
||||||
|
class _FakeStreamedResponse_1 extends _i1.Fake implements _i3.StreamedResponse {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A class which mocks [Client].
|
||||||
|
///
|
||||||
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
|
class MockClient extends _i1.Mock implements _i4.Client {
|
||||||
|
MockClient() {
|
||||||
|
_i1.throwOnMissingStub(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> head(Uri? url, {Map<String, String>? headers}) =>
|
||||||
|
(super.noSuchMethod(Invocation.method(#head, [url], {#headers: headers}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse_0()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> get(Uri? url, {Map<String, String>? headers}) =>
|
||||||
|
(super.noSuchMethod(Invocation.method(#get, [url], {#headers: headers}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse_0()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> post(Uri? url,
|
||||||
|
{Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
_i6.Encoding? encoding}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(#post, [url],
|
||||||
|
{#headers: headers, #body: body, #encoding: encoding}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse_0()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> put(Uri? url,
|
||||||
|
{Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
_i6.Encoding? encoding}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(#put, [url],
|
||||||
|
{#headers: headers, #body: body, #encoding: encoding}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse_0()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> patch(Uri? url,
|
||||||
|
{Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
_i6.Encoding? encoding}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(#patch, [url],
|
||||||
|
{#headers: headers, #body: body, #encoding: encoding}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse_0()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> delete(Uri? url,
|
||||||
|
{Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
_i6.Encoding? encoding}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(#delete, [url],
|
||||||
|
{#headers: headers, #body: body, #encoding: encoding}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse_0()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<String> read(Uri? url, {Map<String, String>? headers}) =>
|
||||||
|
(super.noSuchMethod(Invocation.method(#read, [url], {#headers: headers}),
|
||||||
|
returnValue: Future<String>.value('')) as _i5.Future<String>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i7.Uint8List> readBytes(Uri? url,
|
||||||
|
{Map<String, String>? headers}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(#readBytes, [url], {#headers: headers}),
|
||||||
|
returnValue: Future<_i7.Uint8List>.value(_i7.Uint8List(0)))
|
||||||
|
as _i5.Future<_i7.Uint8List>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i3.StreamedResponse> send(_i8.BaseRequest? request) =>
|
||||||
|
(super.noSuchMethod(Invocation.method(#send, [request]),
|
||||||
|
returnValue:
|
||||||
|
Future<_i3.StreamedResponse>.value(_FakeStreamedResponse_1()))
|
||||||
|
as _i5.Future<_i3.StreamedResponse>);
|
||||||
|
@override
|
||||||
|
void close() => super.noSuchMethod(Invocation.method(#close, []),
|
||||||
|
returnValueForMissingStub: null);
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue