mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 09:50:15 +01:00
Merge branch 'master' into limpbrains-cc
This commit is contained in:
commit
d065bd586f
@ -784,6 +784,7 @@ export const BlueListItem = React.memo(props => {
|
||||
topDivider={props.topDivider !== undefined ? props.topDivider : false}
|
||||
testID={props.testID}
|
||||
onPress={props.onPress}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
{props.leftAvatar && <Avatar>{props.leftAvatar}</Avatar>}
|
||||
{props.leftIcon && <Avatar icon={props.leftIcon} />}
|
||||
|
@ -5,6 +5,7 @@ import Share from 'react-native-share';
|
||||
import loc from '../loc';
|
||||
import { getSystemName } from 'react-native-device-info';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
|
||||
|
||||
const isDesktop = getSystemName() === 'Mac OS X';
|
||||
|
||||
@ -83,7 +84,14 @@ const showFilePickerAndReadFile = async function () {
|
||||
const res = await DocumentPicker.pick({
|
||||
type:
|
||||
Platform.OS === 'ios'
|
||||
? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn', 'io.bluewallet.backup', DocumentPicker.types.plainText, 'public.json']
|
||||
? [
|
||||
'io.bluewallet.psbt',
|
||||
'io.bluewallet.psbt.txn',
|
||||
'io.bluewallet.backup',
|
||||
DocumentPicker.types.plainText,
|
||||
'public.json',
|
||||
DocumentPicker.types.images,
|
||||
]
|
||||
: [DocumentPicker.types.allFiles],
|
||||
});
|
||||
|
||||
@ -94,11 +102,24 @@ const showFilePickerAndReadFile = async function () {
|
||||
if (res.uri.toLowerCase().endsWith('.psbt')) {
|
||||
// this is either binary file from ElectrumDesktop OR string file with base64 string in there
|
||||
file = await _readPsbtFileIntoBase64(uri);
|
||||
return { data: file, uri: decodeURI(res.uri) };
|
||||
} else {
|
||||
file = await RNFS.readFile(uri);
|
||||
if (res.type === DocumentPicker.types.images || res.type.startsWith('image/')) {
|
||||
return new Promise(resolve => {
|
||||
const uri = Platform.OS === 'ios' ? res.uri.toString().replace('file://', '') : res.path.toString();
|
||||
LocalQRCode.decode(decodeURI(uri), (error, result) => {
|
||||
if (!error) {
|
||||
resolve({ data: result, uri: decodeURI(res.uri) });
|
||||
} else {
|
||||
resolve({ data: false, uri: false });
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
file = await RNFS.readFile(uri);
|
||||
return { data: file, uri: decodeURI(res.uri) };
|
||||
}
|
||||
}
|
||||
|
||||
return { data: file, uri: decodeURI(res.uri) };
|
||||
} catch (err) {
|
||||
return { data: false, uri: false };
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import {
|
||||
HDSegwitElectrumSeedP2WPKHWallet,
|
||||
MultisigHDWallet,
|
||||
} from './';
|
||||
import { AbstractHDElectrumWallet } from './wallets/abstract-hd-electrum-wallet';
|
||||
import { Platform } from 'react-native';
|
||||
const encryption = require('../blue_modules/encryption');
|
||||
const Realm = require('realm');
|
||||
@ -59,7 +58,7 @@ export class AppStorage {
|
||||
} else {
|
||||
return AsyncStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is
|
||||
@ -68,13 +67,13 @@ export class AppStorage {
|
||||
* @param key
|
||||
* @returns {Promise<any>|*}
|
||||
*/
|
||||
getItem = (key) => {
|
||||
getItem = key => {
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
return RNSecureKeyStore.get(key);
|
||||
} else {
|
||||
return AsyncStorage.getItem(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setResetOnAppUninstallTo = async value => {
|
||||
if (Platform.OS === 'ios') {
|
||||
@ -396,7 +395,7 @@ export class AppStorage {
|
||||
const id = wallet.getID();
|
||||
const walletToSave = wallet._hdWalletInstance ?? wallet;
|
||||
|
||||
if (walletToSave instanceof AbstractHDElectrumWallet) {
|
||||
if (walletToSave._txs_by_external_index) {
|
||||
realm.write(() => {
|
||||
const j1 = JSON.stringify(walletToSave._txs_by_external_index);
|
||||
const j2 = JSON.stringify(walletToSave._txs_by_internal_index);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { LegacyWallet } from './legacy-wallet';
|
||||
import Frisbee from 'frisbee';
|
||||
const bip39 = require('bip39');
|
||||
const BlueElectrum = require('../../blue_modules/BlueElectrum');
|
||||
|
||||
@ -180,99 +179,7 @@ export class AbstractHDWallet extends LegacyWallet {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async fetchTransactions() {
|
||||
try {
|
||||
const api = new Frisbee({ baseURI: 'https://blockchain.info' });
|
||||
this.transactions = [];
|
||||
let offset = 0;
|
||||
|
||||
while (1) {
|
||||
const response = await api.get('/multiaddr?active=' + this.getXpub() + '&n=100&offset=' + offset);
|
||||
|
||||
if (response && response.body) {
|
||||
if (response.body.txs && response.body.txs.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
let latestBlock = false;
|
||||
if (response.body.info && response.body.info.latest_block) {
|
||||
latestBlock = response.body.info.latest_block.height;
|
||||
}
|
||||
|
||||
this._lastTxFetch = +new Date();
|
||||
|
||||
// processing TXs and adding to internal memory
|
||||
if (response.body.txs) {
|
||||
for (const tx of response.body.txs) {
|
||||
let value = 0;
|
||||
|
||||
for (const input of tx.inputs) {
|
||||
// ----- INPUTS
|
||||
if (input.prev_out.xpub) {
|
||||
// sent FROM US
|
||||
value -= input.prev_out.value;
|
||||
|
||||
// setting internal caches to help ourselves in future...
|
||||
const path = input.prev_out.xpub.path.split('/');
|
||||
if (path[path.length - 2] === '1') {
|
||||
// change address
|
||||
this.next_free_change_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_change_address_index);
|
||||
// setting to point to last maximum known change address + 1
|
||||
}
|
||||
if (path[path.length - 2] === '0') {
|
||||
// main (aka external) address
|
||||
this.next_free_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_address_index);
|
||||
// setting to point to last maximum known main address + 1
|
||||
}
|
||||
// done with cache
|
||||
}
|
||||
}
|
||||
|
||||
for (const output of tx.out) {
|
||||
// ----- OUTPUTS
|
||||
if (output.xpub) {
|
||||
// sent TO US (change)
|
||||
value += output.value;
|
||||
|
||||
// setting internal caches to help ourselves in future...
|
||||
const path = output.xpub.path.split('/');
|
||||
if (path[path.length - 2] === '1') {
|
||||
// change address
|
||||
this.next_free_change_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_change_address_index);
|
||||
// setting to point to last maximum known change address + 1
|
||||
}
|
||||
if (path[path.length - 2] === '0') {
|
||||
// main (aka external) address
|
||||
this.next_free_address_index = Math.max(path[path.length - 1] * 1 + 1, this.next_free_address_index);
|
||||
// setting to point to last maximum known main address + 1
|
||||
}
|
||||
// done with cache
|
||||
}
|
||||
}
|
||||
|
||||
tx.value = value; // new BigNumber(value).div(100000000).toString() * 1;
|
||||
if (!tx.confirmations && latestBlock) {
|
||||
tx.confirmations = latestBlock - tx.block_height + 1;
|
||||
}
|
||||
|
||||
this.transactions.push(tx);
|
||||
}
|
||||
|
||||
if (response.body.txs.length < 100) {
|
||||
// this fetch yilded less than page size, thus requesting next batch makes no sense
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break; // error ?
|
||||
}
|
||||
} else {
|
||||
throw new Error('Could not fetch transactions from API: ' + response.err); // breaks here
|
||||
}
|
||||
|
||||
offset += 100;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,6 @@ export class AbstractWallet {
|
||||
this.secret = ''; // private key or recovery phrase
|
||||
this.balance = 0;
|
||||
this.unconfirmed_balance = 0;
|
||||
this.transactions = [];
|
||||
this._address = false; // cache
|
||||
this.utxo = [];
|
||||
this._lastTxFetch = 0;
|
||||
@ -41,7 +40,7 @@ export class AbstractWallet {
|
||||
}
|
||||
|
||||
getTransactions() {
|
||||
return this.transactions;
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
getUserHasSavedExport() {
|
||||
|
@ -26,6 +26,21 @@
|
||||
6D641F2425525054003792DF /* WalletInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F2225525053003792DF /* WalletInformationView.swift */; };
|
||||
6D641F3525526311003792DF /* SendReceiveButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F3425526311003792DF /* SendReceiveButtons.swift */; };
|
||||
6D641F3625526311003792DF /* SendReceiveButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F3425526311003792DF /* SendReceiveButtons.swift */; };
|
||||
6D6CA4B9255872E3009312A5 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; };
|
||||
6D6CA4BA255872E3009312A5 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; };
|
||||
6D6CA4BD255872E3009312A5 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */; };
|
||||
6D6CA4C3255872E7009312A5 /* PriceWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D6CA4B8255872E3009312A5 /* PriceWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
6D6CA4D725587397009312A5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D9A2E08254BA348007B5B82 /* Assets.xcassets */; };
|
||||
6D6CA4E0255873BC009312A5 /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; };
|
||||
6D6CA5152558EBA4009312A5 /* WidgetAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */; };
|
||||
6D6CA5162558EBA4009312A5 /* WidgetAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */; };
|
||||
6D6CA5282558EC52009312A5 /* PriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5272558EC52009312A5 /* PriceView.swift */; };
|
||||
6D6CA5292558EC52009312A5 /* PriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5272558EC52009312A5 /* PriceView.swift */; };
|
||||
6D6CA5322558ED4D009312A5 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; };
|
||||
6D6CA5332558ED54009312A5 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; };
|
||||
6D6CA53C2558F316009312A5 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; };
|
||||
6D6CA5452558F365009312A5 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; };
|
||||
6D6CA54E2558F497009312A5 /* WidgetAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */; };
|
||||
6D99465F2555A660000E52E8 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; };
|
||||
6D9946602555A660000E52E8 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; };
|
||||
6D9946632555A660000E52E8 /* MarketWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9946622555A660000E52E8 /* MarketWidget.swift */; };
|
||||
@ -95,6 +110,13 @@
|
||||
remoteGlobalIDString = 3271B0A8236E2E0700DA766F;
|
||||
remoteInfo = TodayExtension;
|
||||
};
|
||||
6D6CA4C1255872E7009312A5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 6D6CA4B7255872E3009312A5;
|
||||
remoteInfo = PriceWidgetExtension;
|
||||
};
|
||||
6D9946442555A583000E52E8 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
@ -147,6 +169,7 @@
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
6D9946692555A661000E52E8 /* MarketWidgetExtension.appex in Embed App Extensions */,
|
||||
6D6CA4C3255872E7009312A5 /* PriceWidgetExtension.appex in Embed App Extensions */,
|
||||
6D9A2E0D254BA348007B5B82 /* WalletInformationAndMarketWidgetExtension.appex in Embed App Extensions */,
|
||||
6DEB4AB8254FB59E00E9F9AA /* WalletInformationWidgetExtension.appex in Embed App Extensions */,
|
||||
3271B0B5236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex in Embed App Extensions */,
|
||||
@ -272,6 +295,12 @@
|
||||
6D641F17255226DA003792DF /* MarketView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketView.swift; sourceTree = "<group>"; };
|
||||
6D641F2225525053003792DF /* WalletInformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationView.swift; sourceTree = "<group>"; };
|
||||
6D641F3425526311003792DF /* SendReceiveButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendReceiveButtons.swift; sourceTree = "<group>"; };
|
||||
6D6CA4B8255872E3009312A5 /* PriceWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PriceWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6D6CA4BC255872E3009312A5 /* PriceWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceWidget.swift; sourceTree = "<group>"; };
|
||||
6D6CA4C0255872E7009312A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WidgetAPI+Electrum.swift"; sourceTree = "<group>"; };
|
||||
6D6CA5272558EC52009312A5 /* PriceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceView.swift; sourceTree = "<group>"; };
|
||||
6D6CA6192558F6AB009312A5 /* PriceWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PriceWidgetExtension.entitlements; sourceTree = "<group>"; };
|
||||
6D99465E2555A660000E52E8 /* MarketWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MarketWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6D9946622555A660000E52E8 /* MarketWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketWidget.swift; sourceTree = "<group>"; };
|
||||
6D9946662555A661000E52E8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@ -383,6 +412,15 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6D6CA4B5255872E3009312A5 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6D6CA4BA255872E3009312A5 /* SwiftUI.framework in Frameworks */,
|
||||
6D6CA4B9255872E3009312A5 /* WidgetKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6D99465B2555A660000E52E8 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -518,6 +556,15 @@
|
||||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6D6CA4BB255872E3009312A5 /* PriceWidget */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6D6CA4BC255872E3009312A5 /* PriceWidget.swift */,
|
||||
6D6CA4C0255872E7009312A5 /* Info.plist */,
|
||||
);
|
||||
path = PriceWidget;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6D9946612555A660000E52E8 /* MarketWidget */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -551,6 +598,7 @@
|
||||
children = (
|
||||
6DEB4BC1254FB98300E9F9AA /* Shared */,
|
||||
6D9946612555A660000E52E8 /* MarketWidget */,
|
||||
6D6CA4BB255872E3009312A5 /* PriceWidget */,
|
||||
6D9A2E05254BA347007B5B82 /* WalletInformationAndMarketWidget */,
|
||||
6DEB4AB0254FB59C00E9F9AA /* WalletInformationWidget */,
|
||||
);
|
||||
@ -563,6 +611,7 @@
|
||||
children = (
|
||||
6DEB4DD82552260200E9F9AA /* Views */,
|
||||
6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */,
|
||||
6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */,
|
||||
6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */,
|
||||
6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */,
|
||||
6D9A2E08254BA348007B5B82 /* Assets.xcassets */,
|
||||
@ -578,6 +627,7 @@
|
||||
6D641F17255226DA003792DF /* MarketView.swift */,
|
||||
6D641F2225525053003792DF /* WalletInformationView.swift */,
|
||||
6D641F3425526311003792DF /* SendReceiveButtons.swift */,
|
||||
6D6CA5272558EC52009312A5 /* PriceView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@ -585,6 +635,7 @@
|
||||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6D6CA6192558F6AB009312A5 /* PriceWidgetExtension.entitlements */,
|
||||
6D9947152555AB9E000E52E8 /* MarketWidgetExtension.entitlements */,
|
||||
6DEB4ACE254FB5D800E9F9AA /* WalletInformationWidgetExtension.entitlements */,
|
||||
13B07FAE1A68108700A75B9A /* BlueWallet */,
|
||||
@ -614,6 +665,7 @@
|
||||
6D9A2E02254BA347007B5B82 /* WalletInformationAndMarketWidgetExtension.appex */,
|
||||
6DEB4AAD254FB59B00E9F9AA /* WalletInformationWidgetExtension.appex */,
|
||||
6D99465E2555A660000E52E8 /* MarketWidgetExtension.appex */,
|
||||
6D6CA4B8255872E3009312A5 /* PriceWidgetExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -736,6 +788,7 @@
|
||||
6DEB4AB7254FB59E00E9F9AA /* PBXTargetDependency */,
|
||||
6D9946452555A583000E52E8 /* PBXTargetDependency */,
|
||||
6D9946682555A661000E52E8 /* PBXTargetDependency */,
|
||||
6D6CA4C2255872E7009312A5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = BlueWallet;
|
||||
productName = "Hello World";
|
||||
@ -759,6 +812,23 @@
|
||||
productReference = 3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
6D6CA4B7255872E3009312A5 /* PriceWidgetExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 6D6CA4C6255872E7009312A5 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */;
|
||||
buildPhases = (
|
||||
6D6CA4B4255872E3009312A5 /* Sources */,
|
||||
6D6CA4B5255872E3009312A5 /* Frameworks */,
|
||||
6D6CA4B6255872E3009312A5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = PriceWidgetExtension;
|
||||
productName = PriceWidgetExtension;
|
||||
productReference = 6D6CA4B8255872E3009312A5 /* PriceWidgetExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
6D99465D2555A660000E52E8 /* MarketWidgetExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 6D99466A2555A661000E52E8 /* Build configuration list for PBXNativeTarget "MarketWidgetExtension" */;
|
||||
@ -875,6 +945,9 @@
|
||||
CreatedOnToolsVersion = 11.2;
|
||||
LastSwiftMigration = 1130;
|
||||
};
|
||||
6D6CA4B7255872E3009312A5 = {
|
||||
CreatedOnToolsVersion = 12.1;
|
||||
};
|
||||
6D99465D2555A660000E52E8 = {
|
||||
CreatedOnToolsVersion = 12.1;
|
||||
};
|
||||
@ -948,6 +1021,7 @@
|
||||
6D99465D2555A660000E52E8 /* MarketWidgetExtension */,
|
||||
6DEB4AAC254FB59B00E9F9AA /* WalletInformationWidgetExtension */,
|
||||
6D9A2E01254BA347007B5B82 /* WalletInformationAndMarketWidgetExtension */,
|
||||
6D6CA4B7255872E3009312A5 /* PriceWidgetExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@ -970,6 +1044,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6D6CA4B6255872E3009312A5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6D6CA4D725587397009312A5 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6D99465C2555A660000E52E8 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -1170,6 +1252,20 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6D6CA4B4255872E3009312A5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6D6CA5452558F365009312A5 /* WidgetDataStore.swift in Sources */,
|
||||
6D6CA4E0255873BC009312A5 /* UserDefaultsGroup.swift in Sources */,
|
||||
6D6CA5322558ED4D009312A5 /* Colors.swift in Sources */,
|
||||
6D6CA4BD255872E3009312A5 /* PriceWidget.swift in Sources */,
|
||||
6D6CA5292558EC52009312A5 /* PriceView.swift in Sources */,
|
||||
6D6CA53C2558F316009312A5 /* WidgetAPI.swift in Sources */,
|
||||
6D6CA5332558ED54009312A5 /* Models.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6D99465A2555A660000E52E8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -1183,6 +1279,7 @@
|
||||
6D99467B2555A68A000E52E8 /* MarketView.swift in Sources */,
|
||||
6D9946872555A695000E52E8 /* UserDefaultsGroup.swift in Sources */,
|
||||
6D9946632555A660000E52E8 /* MarketWidget.swift in Sources */,
|
||||
6D6CA5152558EBA4009312A5 /* WidgetAPI+Electrum.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1193,12 +1290,14 @@
|
||||
6D641F2325525054003792DF /* WalletInformationView.swift in Sources */,
|
||||
6D9A2E6D254BAB1B007B5B82 /* WidgetAPI.swift in Sources */,
|
||||
6DEB4C3B254FBF4800E9F9AA /* Colors.swift in Sources */,
|
||||
6D6CA5282558EC52009312A5 /* PriceView.swift in Sources */,
|
||||
6D9A2E6F254BAB1B007B5B82 /* WidgetDataStore.swift in Sources */,
|
||||
6DA7047E254E24D5005FE5E2 /* UserDefaultsGroup.swift in Sources */,
|
||||
6D9A2E07254BA347007B5B82 /* WalletInformationAndMarketWidget.swift in Sources */,
|
||||
6D641F3525526311003792DF /* SendReceiveButtons.swift in Sources */,
|
||||
6DEB4BFB254FBA0E00E9F9AA /* Models.swift in Sources */,
|
||||
6D641F18255226DA003792DF /* MarketView.swift in Sources */,
|
||||
6D6CA5162558EBA4009312A5 /* WidgetAPI+Electrum.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1215,6 +1314,7 @@
|
||||
6D641F3625526311003792DF /* SendReceiveButtons.swift in Sources */,
|
||||
6DEB4BFC254FBA0E00E9F9AA /* Models.swift in Sources */,
|
||||
6D641F19255226DA003792DF /* MarketView.swift in Sources */,
|
||||
6D6CA54E2558F497009312A5 /* WidgetAPI+Electrum.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1249,6 +1349,11 @@
|
||||
target = 3271B0A8236E2E0700DA766F /* TodayExtension */;
|
||||
targetProxy = 3271B0B3236E2E0700DA766F /* PBXContainerItemProxy */;
|
||||
};
|
||||
6D6CA4C2255872E7009312A5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 6D6CA4B7255872E3009312A5 /* PriceWidgetExtension */;
|
||||
targetProxy = 6D6CA4C1255872E7009312A5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
6D9946452555A583000E52E8 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 6D99465D2555A660000E52E8 /* MarketWidgetExtension */;
|
||||
@ -1506,6 +1611,85 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
6D6CA4C4255872E7009312A5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 290;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = A7W54YZ4WU;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/WalletInformationWidget/Widgets/PriceWidget/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.6.5;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = PriceWidget;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
6D6CA4C5255872E7009312A5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 290;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = A7W54YZ4WU;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/WalletInformationWidget/Widgets/PriceWidget/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.6.5;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = PriceWidget;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
6D99466B2555A661000E52E8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 367FA8CEB35BC9431019D98A /* Pods-MarketWidgetExtension.debug.xcconfig */;
|
||||
@ -2025,6 +2209,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
6D6CA4C6255872E7009312A5 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
6D6CA4C4255872E7009312A5 /* Debug */,
|
||||
6D6CA4C5255872E7009312A5 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
6D99466A2555A661000E52E8 /* Build configuration list for PBXNativeTarget "MarketWidgetExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
@ -390,7 +390,7 @@ PODS:
|
||||
- React
|
||||
- RNVectorIcons (6.6.0):
|
||||
- React
|
||||
- RNWatch (1.0.2):
|
||||
- RNWatch (1.0.3):
|
||||
- React
|
||||
- Sentry (5.2.2):
|
||||
- Sentry/Core (= 5.2.2)
|
||||
@ -739,7 +739,7 @@ SPEC CHECKSUMS:
|
||||
RNShare: 7a7277f3c313652422d9de072ac50714dff5e8a4
|
||||
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
|
||||
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
|
||||
RNWatch: d56d00be49131ee454bb5a4a574f18506c8949e4
|
||||
RNWatch: e4c5d19506c94506860032fb68aedd5991beb985
|
||||
Sentry: 8fa58a051237554f22507fb483b9a1de0171a2dc
|
||||
SwiftSocket: c8d482e867ae4d3eb4c769e9382e123c1f1f833b
|
||||
ToolTipMenu: 4d89d95ddffd7539230bdbe02ee51bbde362e37e
|
||||
|
10
ios/PriceWidgetExtension.entitlements
Normal file
10
ios/PriceWidgetExtension.entitlements
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.io.bluewallet.bluewallet</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
29
ios/WalletInformationWidget/Widgets/PriceWidget/Info.plist
Normal file
29
ios/WalletInformationWidget/Widgets/PriceWidget/Info.plist
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>PriceWidget</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,97 @@
|
||||
//
|
||||
// PriceWidget.swift
|
||||
// PriceWidget
|
||||
//
|
||||
// Created by Marcos Rodriguez on 11/8/20.
|
||||
// Copyright © 2020 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
var marketData: [MarketDataTimeline: MarketData?] = [ .Current: nil, .Previous: nil]
|
||||
struct Provider: TimelineProvider {
|
||||
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
SimpleEntry(date: Date(), currentMarketData: nil)
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
||||
let entry: SimpleEntry
|
||||
if (context.isPreview) {
|
||||
entry = SimpleEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"))
|
||||
} else {
|
||||
entry = SimpleEntry(date: Date(), currentMarketData: emptyMarketData)
|
||||
}
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
var entries: [SimpleEntry] = []
|
||||
|
||||
if WidgetAPI.getUserPreferredCurrency() != WidgetAPI.getLastSelectedCurrency() {
|
||||
marketData[.Current] = nil
|
||||
marketData[.Previous] = nil
|
||||
WidgetAPI.saveNewSelectedCurrency()
|
||||
}
|
||||
|
||||
var entryMarketData = marketData[.Current] ?? emptyMarketData
|
||||
WidgetAPI.fetchPrice(currency: WidgetAPI.getUserPreferredCurrency()) { (data, error) in
|
||||
if let data = data, let formattedRate = data.formattedRate {
|
||||
let currentMarketData = MarketData(nextBlock: "", sats: "", price: formattedRate, rate: data.rateDouble, dateString: data.lastUpdate)
|
||||
if let cachedMarketData = marketData[.Current], currentMarketData.dateString != cachedMarketData?.dateString {
|
||||
marketData[.Previous] = marketData[.Current]
|
||||
marketData[.Current] = currentMarketData
|
||||
entryMarketData = currentMarketData
|
||||
entries.append(SimpleEntry(date:Date(), currentMarketData: entryMarketData))
|
||||
} else {
|
||||
entries.append(SimpleEntry(date:Date(), currentMarketData: currentMarketData))
|
||||
}
|
||||
}
|
||||
|
||||
let timeline = Timeline(entries: entries, policy: .atEnd)
|
||||
completion(timeline)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let currentMarketData: MarketData?
|
||||
var previousMarketData: MarketData? {
|
||||
return marketData[.Previous] as? MarketData
|
||||
}
|
||||
}
|
||||
|
||||
struct PriceWidgetEntryView : View {
|
||||
var entry: Provider.Entry
|
||||
var priceView: some View {
|
||||
PriceView(currentMarketData: entry.currentMarketData, previousMarketData: marketData[.Previous] ?? emptyMarketData).padding()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
priceView.background(Color.widgetBackground)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct PriceWidget: Widget {
|
||||
let kind: String = "PriceWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
||||
PriceWidgetEntryView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName("Price")
|
||||
.description("View the current price of Bitcoin.").supportedFamilies([.systemSmall])
|
||||
}
|
||||
}
|
||||
|
||||
struct PriceWidget_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PriceWidgetEntryView(entry: SimpleEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00")))
|
||||
.previewContext(WidgetPreviewContext(family: .systemSmall))
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct MarketData {
|
||||
struct MarketData:Codable {
|
||||
var nextBlock: String
|
||||
var sats: String
|
||||
var price: String
|
||||
@ -16,6 +16,19 @@ struct MarketData {
|
||||
var formattedNextBlock: String {
|
||||
return nextBlock == "..." ? "..." : #"\#(nextBlock) sat/b"#
|
||||
}
|
||||
var dateString: String = ""
|
||||
var formattedDate: String? {
|
||||
let isoDateFormatter = ISO8601DateFormatter()
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.locale = Locale.current
|
||||
dateFormatter.timeStyle = .short
|
||||
|
||||
if let date = isoDateFormatter.date(from: dateString) {
|
||||
return dateFormatter.string(from: date)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct WalletData {
|
||||
@ -30,3 +43,8 @@ struct WalletData {
|
||||
|
||||
let emptyMarketData = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0)
|
||||
let emptyWalletData = WalletData(balance: 0, latestTransactionTime: Int(Date().timeIntervalSince1970))
|
||||
|
||||
enum MarketDataTimeline: String {
|
||||
case Previous = "previous"
|
||||
case Current = "current"
|
||||
}
|
||||
|
@ -32,13 +32,16 @@ class UserDefaultsGroup {
|
||||
static private let suite = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue)
|
||||
|
||||
static func getElectrumSettings() -> UserDefaultsElectrumSettings {
|
||||
guard let electrumSettingsHost = suite?.string(forKey: UserDefaultsGroupKey.ElectrumSettingsHost.rawValue), let electrumSettingsTCPPort = suite?.string(forKey: UserDefaultsGroupKey.ElectrumSettingsTCPPort.rawValue), let electrumSettingsSSLPort = suite?.string(forKey: UserDefaultsGroupKey.ElectrumSettingsSSLPort.rawValue) else {
|
||||
guard let electrumSettingsHost = suite?.string(forKey: UserDefaultsGroupKey.ElectrumSettingsHost.rawValue) else {
|
||||
return UserDefaultsElectrumSettings(host: "electrum1.bluewallet.io", port: 50001, sslPort: 443)
|
||||
}
|
||||
|
||||
let electrumSettingsTCPPort = suite?.string(forKey: UserDefaultsGroupKey.ElectrumSettingsTCPPort.rawValue) ?? "50001"
|
||||
let electrumSettingsSSLPort = suite?.string(forKey: UserDefaultsGroupKey.ElectrumSettingsSSLPort.rawValue) ?? "443"
|
||||
|
||||
let host = electrumSettingsHost
|
||||
let sslPort = Int32(electrumSettingsSSLPort) ?? 443
|
||||
let port = Int32(electrumSettingsTCPPort) ?? 50001
|
||||
let sslPort = Int32(electrumSettingsSSLPort)
|
||||
let port = Int32(electrumSettingsTCPPort)
|
||||
|
||||
return UserDefaultsElectrumSettings(host: host, port: port, sslPort: sslPort)
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
//
|
||||
// PriceView.swift
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 11/8/20.
|
||||
// Copyright © 2020 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
struct PriceView: View {
|
||||
|
||||
var currentMarketData: MarketData? = emptyMarketData
|
||||
var previousMarketData: MarketData? = emptyMarketData
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .trailing, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: {
|
||||
Text("Last Updated").font(Font.system(size: 11, weight: .regular, design: .default)).foregroundColor(.textColorLightGray)
|
||||
HStack(alignment: .lastTextBaseline, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: {
|
||||
Text(currentMarketData?.formattedDate ?? "").lineLimit(1).foregroundColor(.textColor).font(Font.system(size:13, weight: .regular, design: .default)).minimumScaleFactor(0.01).transition(.opacity)
|
||||
})
|
||||
Spacer()
|
||||
VStack(alignment: .trailing, spacing: 16, content: {
|
||||
HStack(alignment: .lastTextBaseline, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: {
|
||||
Text(currentMarketData?.price ?? "").lineLimit(1).foregroundColor(.textColor).font(Font.system(size:28, weight: .bold, design: .default)).minimumScaleFactor(0.01).transition(.opacity)
|
||||
})
|
||||
if let previousMarketDataPrice = previousMarketData?.price, let currentMarketDataRate = currentMarketData?.rate, let previousMarketDataRate = previousMarketData?.rate, previousMarketDataRate > 0, currentMarketDataRate != previousMarketDataRate {
|
||||
HStack(alignment: .lastTextBaseline, spacing: /*@START_MENU_TOKEN@*/nil/*@END_MENU_TOKEN@*/, content: {
|
||||
Image(systemName: currentMarketDataRate > previousMarketDataRate ? "arrow.up" : "arrow.down")
|
||||
Text("from").lineLimit(1).foregroundColor(.textColor).font(Font.system(size:13, weight: .regular, design: .default)).minimumScaleFactor(0.01)
|
||||
Text(previousMarketDataPrice).lineLimit(1).foregroundColor(.textColor).font(Font.system(size:13, weight: .regular, design: .default)).minimumScaleFactor(0.01)
|
||||
}).transition(.slide)
|
||||
}
|
||||
})
|
||||
}).frame(minWidth: 0,
|
||||
maxWidth: .infinity,
|
||||
minHeight: 0,
|
||||
maxHeight: .infinity,
|
||||
alignment: .trailing)
|
||||
}
|
||||
}
|
||||
|
||||
struct PriceView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PriceView().previewContext(WidgetPreviewContext(family: .systemSmall))
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
//
|
||||
// WidgetAPI+Electrum.swift
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 11/8/20.
|
||||
// Copyright © 2020 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
import SwiftSocket
|
||||
|
||||
struct APIError: LocalizedError {
|
||||
var errorDescription: String = "Failed to fetch Electrum data..."
|
||||
}
|
||||
|
||||
extension WidgetAPI {
|
||||
|
||||
static func fetchNextBlockFee(completion: @escaping ((MarketData?, Error?) -> Void), userElectrumSettings: UserDefaultsElectrumSettings = UserDefaultsGroup.getElectrumSettings()) {
|
||||
guard let host = userElectrumSettings.host, let _ = userElectrumSettings.sslPort, let port = userElectrumSettings.port else {
|
||||
print("No valid UserDefaultsElectrumSettings found");
|
||||
return
|
||||
}
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
let client = TCPClient(address: host, port: port)
|
||||
let send = "{\"id\": 1, \"method\": \"blockchain.estimatefee\", \"params\": [1]}\n"
|
||||
switch client.connect(timeout: 1) {
|
||||
case .success:
|
||||
switch client.send(string: send) {
|
||||
case .success:
|
||||
guard let data = client.read(1024*10, timeout: 1) else {
|
||||
client.close()
|
||||
completion(nil, APIError())
|
||||
return
|
||||
}
|
||||
let characterSet = Set("0123456789.")
|
||||
if let response = String(bytes: data, encoding: .utf8), let nextBlockResponse = response.components(separatedBy: #"result":"#).last?.components(separatedBy: ",").first, let nextBlockResponseDouble = Double(nextBlockResponse.filter({characterSet.contains($0)}).trimmingCharacters(in: .whitespacesAndNewlines)) {
|
||||
print("Successfully obtained response from Electrum sever")
|
||||
print(userElectrumSettings)
|
||||
let marketData = MarketData(nextBlock: String(format: "%.0f", (nextBlockResponseDouble / 1024) * 100000000), sats: "0", price: "0", rate: 0)
|
||||
client.close()
|
||||
completion(MarketData(nextBlock: String(format: "%.0f", (nextBlockResponseDouble / 1024) * 100000000), sats: "0", price: "0", rate: 0), nil)
|
||||
completion(marketData, nil)
|
||||
} else {
|
||||
client.close()
|
||||
completion(nil, APIError())
|
||||
}
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
client.close()
|
||||
completion(nil, APIError())
|
||||
}
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
client.close()
|
||||
if userElectrumSettings.host == DefaultElectrumPeers.last?.host {
|
||||
completion(nil, APIError())
|
||||
} else if let currentIndex = DefaultElectrumPeers.firstIndex(where: {$0.host == userElectrumSettings.host}) {
|
||||
fetchNextBlockFee(completion: completion, userElectrumSettings: DefaultElectrumPeers[DefaultElectrumPeers.index(after: currentIndex)])
|
||||
} else {
|
||||
if let first = DefaultElectrumPeers.first {
|
||||
fetchNextBlockFee(completion: completion, userElectrumSettings: first)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func fetchMarketData(currency: String, completion: @escaping ((MarketData?, Error?) -> Void)) {
|
||||
var marketDataEntry = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0)
|
||||
WidgetAPI.fetchPrice(currency: currency, completion: { (result, error) in
|
||||
if let result = result {
|
||||
marketDataEntry.rate = result.rateDouble
|
||||
marketDataEntry.price = result.formattedRate ?? "!"
|
||||
}
|
||||
WidgetAPI.fetchNextBlockFee { (marketData, error) in
|
||||
if let nextBlock = marketData?.nextBlock {
|
||||
marketDataEntry.nextBlock = nextBlock
|
||||
} else {
|
||||
marketDataEntry.nextBlock = "!"
|
||||
}
|
||||
if let rateDouble = result?.rateDouble {
|
||||
marketDataEntry.sats = numberFormatter.string(from: NSNumber(value: Double(10 / rateDouble) * 10000000)) ?? "!"
|
||||
}
|
||||
completion(marketDataEntry, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -7,11 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftSocket
|
||||
|
||||
struct APIError: LocalizedError {
|
||||
var errorDescription: String = "Failed to fetch Electrum data..."
|
||||
}
|
||||
|
||||
var numberFormatter: NumberFormatter {
|
||||
let formatter = NumberFormatter()
|
||||
@ -24,71 +20,6 @@ var numberFormatter: NumberFormatter {
|
||||
|
||||
class WidgetAPI {
|
||||
|
||||
static func fetchNextBlockFee(completion: @escaping ((MarketData?, Error?) -> Void), userElectrumSettings: UserDefaultsElectrumSettings = UserDefaultsGroup.getElectrumSettings()) {
|
||||
guard let host = userElectrumSettings.host, let _ = userElectrumSettings.sslPort, let port = userElectrumSettings.port else {
|
||||
print("No valid UserDefaultsElectrumSettings found");
|
||||
return
|
||||
}
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
let client = TCPClient(address: host, port: port)
|
||||
let send = "{\"id\": 1, \"method\": \"blockchain.estimatefee\", \"params\": [1]}\n"
|
||||
switch client.connect(timeout: 1) {
|
||||
case .success:
|
||||
switch client.send(string: send) {
|
||||
case .success:
|
||||
guard let data = client.read(1024*10, timeout: 1) else {
|
||||
client.close()
|
||||
completion(nil, APIError())
|
||||
return
|
||||
}
|
||||
if let response = String(bytes: data, encoding: .utf8), let nextBlockResponse = response.components(separatedBy: #"result":"#).last?.components(separatedBy: ",").first, let nextBlockResponseDouble = Double(nextBlockResponse.trimmingCharacters(in: .whitespacesAndNewlines)) {
|
||||
print("Successfully obtained response from Electrum sever")
|
||||
print(userElectrumSettings)
|
||||
client.close()
|
||||
completion(MarketData(nextBlock: String(format: "%.0f", (nextBlockResponseDouble / 1024) * 100000000), sats: "0", price: "0", rate: 0), nil)
|
||||
}
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
client.close()
|
||||
completion(nil, APIError())
|
||||
}
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
client.close()
|
||||
if userElectrumSettings.host == DefaultElectrumPeers.last?.host {
|
||||
completion(nil, APIError())
|
||||
} else if let currentIndex = DefaultElectrumPeers.firstIndex(where: {$0.host == userElectrumSettings.host}) {
|
||||
fetchNextBlockFee(completion: completion, userElectrumSettings: DefaultElectrumPeers[DefaultElectrumPeers.index(after: currentIndex)])
|
||||
} else {
|
||||
if let first = DefaultElectrumPeers.first {
|
||||
fetchNextBlockFee(completion: completion, userElectrumSettings: first)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func fetchMarketData(currency: String, completion: @escaping ((MarketData?, Error?) -> Void)) {
|
||||
var marketDataEntry = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0)
|
||||
WidgetAPI.fetchPrice(currency: currency, completion: { (result, error) in
|
||||
if let result = result {
|
||||
marketDataEntry.rate = result.rateDouble
|
||||
marketDataEntry.price = result.formattedRate ?? "!"
|
||||
}
|
||||
WidgetAPI.fetchNextBlockFee { (marketData, error) in
|
||||
if let nextBlock = marketData?.nextBlock {
|
||||
marketDataEntry.nextBlock = nextBlock
|
||||
} else {
|
||||
marketDataEntry.nextBlock = "!"
|
||||
}
|
||||
if let rateDouble = result?.rateDouble {
|
||||
marketDataEntry.sats = numberFormatter.string(from: NSNumber(value: Double(10 / rateDouble) * 10000000)) ?? "!"
|
||||
}
|
||||
completion(marketDataEntry, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static func fetchPrice(currency: String, completion: @escaping ((WidgetDataStore?, Error?) -> Void)) {
|
||||
guard let url = URL(string: "https://api.coindesk.com/v1/bpi/currentPrice/\(currency).json") else {return}
|
||||
|
||||
@ -100,11 +31,12 @@ class WidgetAPI {
|
||||
completion(nil, error)
|
||||
return }
|
||||
|
||||
guard let bpi = json?["bpi"] as? Dictionary<String, Any>, let preferredCurrency = bpi[currency] as? Dictionary<String, Any>, let rateString = preferredCurrency["rate"] as? String, let rateDouble = preferredCurrency["rate_float"] as? Double else {
|
||||
guard let bpi = json?["bpi"] as? Dictionary<String, Any>, let preferredCurrency = bpi[currency] as? Dictionary<String, Any>, let rateString = preferredCurrency["rate"] as? String, let rateDouble = preferredCurrency["rate_float"] as? Double, let time = json?["time"] as? Dictionary<String, Any>, let lastUpdatedString = time["updatedISO"] as? String
|
||||
else {
|
||||
print(error?.localizedDescription ?? "Response Error")
|
||||
completion(nil, error)
|
||||
return }
|
||||
let latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: "", rateDouble: rateDouble)
|
||||
let latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
|
||||
completion(latestRateDataStore, nil)
|
||||
}.resume()
|
||||
}
|
||||
|
27
package-lock.json
generated
27
package-lock.json
generated
@ -6957,9 +6957,9 @@
|
||||
}
|
||||
},
|
||||
"amplitude-js": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-7.2.2.tgz",
|
||||
"integrity": "sha512-Y1/kw/NaxMdqwBnkbjPywpjPbSmuVuszFLQ9tw56P6YraljvbMC93afHQvLC/3zG5SImDnykbg/8HxrWFDhsLg==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-7.3.0.tgz",
|
||||
"integrity": "sha512-FI3ziFNfV4kqpLYHLo6t+E7cbZZ1n8VRCNt214Z1CuDbEzPcc/TenmkCwPoYJA5FQibamqZL9qiKtdMTZhSsUg==",
|
||||
"requires": {
|
||||
"@amplitude/ua-parser-js": "0.7.24",
|
||||
"blueimp-md5": "^2.10.0",
|
||||
@ -8523,13 +8523,15 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
|
||||
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
|
||||
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-extglob": "^1.0.0"
|
||||
}
|
||||
@ -11427,6 +11429,7 @@
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
|
||||
"integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-glob": "^2.0.0"
|
||||
},
|
||||
@ -11435,13 +11438,15 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
|
||||
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
|
||||
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-extglob": "^1.0.0"
|
||||
}
|
||||
@ -17866,7 +17871,8 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
|
||||
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "2.0.1",
|
||||
@ -19215,9 +19221,9 @@
|
||||
}
|
||||
},
|
||||
"react-native-watch-connectivity": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-watch-connectivity/-/react-native-watch-connectivity-1.0.2.tgz",
|
||||
"integrity": "sha512-uNx6Zhx++LpRRyQddukBcMyMtWnleP97OME9HEkW91rEuwTviBK12+1sn/YGBgGLrmdEULAoPO91w619w2Efbw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-watch-connectivity/-/react-native-watch-connectivity-1.0.3.tgz",
|
||||
"integrity": "sha512-YITROIsVJw2mw5bm2Xr0waob16EdWJKpIO1B7AJWEpoWwSRu6bS/x1Z0QOhIFDWhRJFNvTQwnnflCq6PjflXNQ==",
|
||||
"requires": {
|
||||
"lodash.sortby": "^4.7.0"
|
||||
}
|
||||
@ -19390,7 +19396,8 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
|
@ -77,7 +77,7 @@
|
||||
"@react-navigation/stack": "5.9.3",
|
||||
"@remobile/react-native-qrcode-local-image": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git",
|
||||
"@sentry/react-native": "1.9.0",
|
||||
"amplitude-js": "7.2.2",
|
||||
"amplitude-js": "7.3.0",
|
||||
"assert": "1.5.0",
|
||||
"base-x": "3.0.8",
|
||||
"bc-bech32": "file:blue_modules/bc-bech32",
|
||||
@ -156,7 +156,7 @@
|
||||
"react-native-tcp-socket": "3.7.1",
|
||||
"react-native-tooltip": "git+https://github.com/BlueWallet/react-native-tooltip.git#d369e7ece09e4dec73873f1cfeac83e9d35294a6",
|
||||
"react-native-vector-icons": "6.6.0",
|
||||
"react-native-watch-connectivity": "1.0.2",
|
||||
"react-native-watch-connectivity": "1.0.3",
|
||||
"react-native-webview": "10.9.2",
|
||||
"react-native-widget-center": "git+https://github.com/BlueWallet/react-native-widget-center.git#e2e9a9038b76d096bf929a87105a97a0a7095001",
|
||||
"react-test-render": "1.1.2",
|
||||
|
@ -14,8 +14,9 @@ import {
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
Platform,
|
||||
ScrollView,
|
||||
Text,
|
||||
LayoutAnimation,
|
||||
FlatList,
|
||||
} from 'react-native';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
@ -65,7 +66,6 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: BlueCurrentTheme.colors.elevated,
|
||||
},
|
||||
scrollViewContent: {
|
||||
flexWrap: 'wrap',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
modalContent: {
|
||||
@ -214,6 +214,7 @@ const styles = StyleSheet.create({
|
||||
export default class SendDetails extends Component {
|
||||
static contextType = BlueStorageContext;
|
||||
state = { isLoading: true };
|
||||
scrollView = React.createRef();
|
||||
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
@ -255,8 +256,8 @@ export default class SendDetails extends Component {
|
||||
},
|
||||
feeUnit: fromWallet.getPreferredBalanceUnit(),
|
||||
amountUnit: fromWallet.preferredBalanceUnit, // default for whole screen
|
||||
renderWalletSelectionOrCoinsSelectedHidden: false,
|
||||
width: Dimensions.get('window').width - 320,
|
||||
renderWalletSelectionButtonHidden: false,
|
||||
width: Dimensions.get('window').width,
|
||||
utxo: null,
|
||||
};
|
||||
}
|
||||
@ -453,15 +454,8 @@ export default class SendDetails extends Component {
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
if (index === 0) {
|
||||
this.scrollView.scrollTo();
|
||||
} else if (index === this.state.addresses.length - 1) {
|
||||
this.scrollView.scrollToEnd();
|
||||
} else {
|
||||
const page = Math.round(this.state.width * (this.state.addresses.length - 2));
|
||||
this.scrollView.scrollTo({ x: page, y: 0, animated: true });
|
||||
}
|
||||
this.setState({ isLoading: false, recipientsScrollIndex: index });
|
||||
this.scrollView.current.scrollToIndex({ index });
|
||||
this.setState({ isLoading: false });
|
||||
alert(error);
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
return;
|
||||
@ -708,7 +702,8 @@ export default class SendDetails extends Component {
|
||||
const feeSatoshi = new BigNumber(element.amount).multipliedBy(100000000);
|
||||
return element.address.length > 0 && feeSatoshi > 0;
|
||||
}) || this.state.addresses[0];
|
||||
this.setState({ addresses: [firstTransaction], recipientsScrollIndex: 0 }, () => changeWallet());
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
this.setState({ addresses: [firstTransaction] }, () => changeWallet());
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
@ -730,7 +725,8 @@ export default class SendDetails extends Component {
|
||||
return element.amount === BitcoinUnit.MAX;
|
||||
}) || this.state.addresses[0];
|
||||
firstTransaction.amount = 0;
|
||||
this.setState({ addresses: [firstTransaction], recipientsScrollIndex: 0 }, () => changeWallet());
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
this.setState({ addresses: [firstTransaction] }, () => changeWallet());
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
@ -977,14 +973,15 @@ export default class SendDetails extends Component {
|
||||
handleAddRecipient = () => {
|
||||
const { addresses } = this.state;
|
||||
addresses.push(new BitcoinTransaction());
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut, () => this.scrollView.current.scrollToEnd());
|
||||
this.setState(
|
||||
{
|
||||
addresses,
|
||||
isAdvancedTransactionOptionsVisible: false,
|
||||
},
|
||||
() => {
|
||||
this.scrollView.scrollToEnd();
|
||||
if (this.state.addresses.length > 1) this.scrollView.flashScrollIndicators();
|
||||
this.scrollView.current.scrollToEnd();
|
||||
if (this.state.addresses.length > 1) this.scrollView.current.flashScrollIndicators();
|
||||
// after adding recipient it automatically scrolls to the last one
|
||||
this.setState({ recipientsScrollIndex: this.state.addresses.length - 1 });
|
||||
},
|
||||
@ -994,13 +991,14 @@ export default class SendDetails extends Component {
|
||||
handleRemoveRecipient = () => {
|
||||
const { addresses } = this.state;
|
||||
addresses.splice(this.state.recipientsScrollIndex, 1);
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
this.setState(
|
||||
{
|
||||
addresses,
|
||||
isAdvancedTransactionOptionsVisible: false,
|
||||
},
|
||||
() => {
|
||||
if (this.state.addresses.length > 1) this.scrollView.flashScrollIndicators();
|
||||
if (this.state.addresses.length > 1) this.scrollView.current.flashScrollIndicators();
|
||||
// after deletion it automatically scrolls to the last one
|
||||
this.setState({ recipientsScrollIndex: this.state.addresses.length - 1 });
|
||||
},
|
||||
@ -1109,6 +1107,16 @@ export default class SendDetails extends Component {
|
||||
this.setState({ isTransactionReplaceable: value });
|
||||
};
|
||||
|
||||
scrollViewCurrentIndex = () => {
|
||||
Keyboard.dismiss();
|
||||
const offset = this.scrollView.current.contentOffset;
|
||||
if (offset) {
|
||||
const page = Math.round(offset.x / Dimensions.get('window').width);
|
||||
return page;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
renderCreateButton = () => {
|
||||
return (
|
||||
<View style={styles.createButton}>
|
||||
@ -1159,110 +1167,84 @@ export default class SendDetails extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
handlePageChange = e => {
|
||||
Keyboard.dismiss();
|
||||
const offset = e.nativeEvent.contentOffset;
|
||||
if (offset) {
|
||||
const page = Math.round(offset.x / this.state.width);
|
||||
if (this.state.recipientsScrollIndex !== page) {
|
||||
this.setState({ recipientsScrollIndex: page });
|
||||
}
|
||||
}
|
||||
};
|
||||
renderBitcoinTransactionInfoFields = ({ item, index }) => {
|
||||
return (
|
||||
<View style={{ width: this.state.width }}>
|
||||
<BlueBitcoinAmount
|
||||
isLoading={this.state.isLoading}
|
||||
amount={item.amount ? item.amount.toString() : null}
|
||||
onAmountUnitChange={unit => {
|
||||
const units = this.state.units;
|
||||
units[index] = unit;
|
||||
|
||||
scrollViewCurrentIndex = () => {
|
||||
Keyboard.dismiss();
|
||||
const offset = this.scrollView.contentOffset;
|
||||
if (offset) {
|
||||
const page = Math.round(offset.x / this.state.width);
|
||||
return page;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const addresses = this.state.addresses;
|
||||
const item = addresses[index];
|
||||
|
||||
renderBitcoinTransactionInfoFields = () => {
|
||||
const rows = [];
|
||||
switch (unit) {
|
||||
case BitcoinUnit.SATS:
|
||||
item.amountSats = parseInt(item.amount);
|
||||
break;
|
||||
case BitcoinUnit.BTC:
|
||||
item.amountSats = currency.btcToSatoshi(item.amount);
|
||||
break;
|
||||
case BitcoinUnit.LOCAL_CURRENCY:
|
||||
// also accounting for cached fiat->sat conversion to avoid rounding error
|
||||
item.amountSats =
|
||||
BlueBitcoinAmount.getCachedSatoshis(item.amount) || currency.btcToSatoshi(currency.fiatToBTC(item.amount));
|
||||
break;
|
||||
}
|
||||
|
||||
for (const [index, item] of this.state.addresses.entries()) {
|
||||
rows.push(
|
||||
<View key={index} style={{ width: this.state.width }}>
|
||||
<BlueBitcoinAmount
|
||||
isLoading={this.state.isLoading}
|
||||
amount={item.amount ? item.amount.toString() : null}
|
||||
onAmountUnitChange={unit => {
|
||||
const units = this.state.units;
|
||||
units[index] = unit;
|
||||
|
||||
const addresses = this.state.addresses;
|
||||
const item = addresses[index];
|
||||
|
||||
switch (unit) {
|
||||
case BitcoinUnit.SATS:
|
||||
item.amountSats = parseInt(item.amount);
|
||||
break;
|
||||
case BitcoinUnit.BTC:
|
||||
item.amountSats = currency.btcToSatoshi(item.amount);
|
||||
break;
|
||||
case BitcoinUnit.LOCAL_CURRENCY:
|
||||
// also accounting for cached fiat->sat conversion to avoid rounding error
|
||||
item.amountSats =
|
||||
BlueBitcoinAmount.getCachedSatoshis(item.amount) || currency.btcToSatoshi(currency.fiatToBTC(item.amount));
|
||||
break;
|
||||
}
|
||||
|
||||
addresses[index] = item;
|
||||
this.setState({ units, addresses });
|
||||
}}
|
||||
onChangeText={text => {
|
||||
item.amount = text;
|
||||
switch (this.state.units[index] || this.state.amountUnit) {
|
||||
case BitcoinUnit.BTC:
|
||||
item.amountSats = currency.btcToSatoshi(item.amount);
|
||||
break;
|
||||
case BitcoinUnit.LOCAL_CURRENCY:
|
||||
item.amountSats = currency.btcToSatoshi(currency.fiatToBTC(item.amount));
|
||||
break;
|
||||
default:
|
||||
case BitcoinUnit.SATS:
|
||||
item.amountSats = parseInt(text);
|
||||
break;
|
||||
}
|
||||
const addresses = this.state.addresses;
|
||||
addresses[index] = item;
|
||||
this.setState({ addresses }, this.reCalcTx);
|
||||
}}
|
||||
unit={this.state.units[index] || this.state.amountUnit}
|
||||
inputAccessoryViewID={this.state.fromWallet.allowSendMax() ? BlueUseAllFundsButton.InputAccessoryViewID : null}
|
||||
/>
|
||||
<BlueAddressInput
|
||||
onChangeText={async text => {
|
||||
text = text.trim();
|
||||
const transactions = this.state.addresses;
|
||||
const { address, amount, memo, payjoinUrl } = DeeplinkSchemaMatch.decodeBitcoinUri(text);
|
||||
item.address = address || text;
|
||||
item.amount = amount || item.amount;
|
||||
transactions[index] = item;
|
||||
this.setState({
|
||||
addresses: transactions,
|
||||
memo: memo || this.state.memo,
|
||||
isLoading: false,
|
||||
payjoinUrl,
|
||||
});
|
||||
this.reCalcTx();
|
||||
}}
|
||||
onBarScanned={this.processAddressData}
|
||||
address={item.address}
|
||||
isLoading={this.state.isLoading}
|
||||
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
|
||||
launchedBy={this.props.route.name}
|
||||
/>
|
||||
{this.state.addresses.length > 1 && (
|
||||
<BlueText style={styles.of}>{loc.formatString(loc._.of, { number: index + 1, total: this.state.addresses.length })}</BlueText>
|
||||
)}
|
||||
</View>,
|
||||
);
|
||||
}
|
||||
return rows;
|
||||
addresses[index] = item;
|
||||
this.setState({ units, addresses });
|
||||
}}
|
||||
onChangeText={text => {
|
||||
item.amount = text;
|
||||
switch (this.state.units[index] || this.state.amountUnit) {
|
||||
case BitcoinUnit.BTC:
|
||||
item.amountSats = currency.btcToSatoshi(item.amount);
|
||||
break;
|
||||
case BitcoinUnit.LOCAL_CURRENCY:
|
||||
item.amountSats = currency.btcToSatoshi(currency.fiatToBTC(item.amount));
|
||||
break;
|
||||
default:
|
||||
case BitcoinUnit.SATS:
|
||||
item.amountSats = parseInt(text);
|
||||
break;
|
||||
}
|
||||
const addresses = this.state.addresses;
|
||||
addresses[index] = item;
|
||||
this.setState({ addresses }, this.reCalcTx);
|
||||
}}
|
||||
unit={this.state.units[index] || this.state.amountUnit}
|
||||
inputAccessoryViewID={this.state.fromWallet.allowSendMax() ? BlueUseAllFundsButton.InputAccessoryViewID : null}
|
||||
/>
|
||||
<BlueAddressInput
|
||||
onChangeText={async text => {
|
||||
text = text.trim();
|
||||
const transactions = this.state.addresses;
|
||||
const { address, amount, memo, payjoinUrl } = DeeplinkSchemaMatch.decodeBitcoinUri(text);
|
||||
item.address = address || text;
|
||||
item.amount = amount || item.amount;
|
||||
transactions[index] = item;
|
||||
this.setState({
|
||||
addresses: transactions,
|
||||
memo: memo || this.state.memo,
|
||||
isLoading: false,
|
||||
payjoinUrl,
|
||||
});
|
||||
this.reCalcTx();
|
||||
}}
|
||||
onBarScanned={this.processAddressData}
|
||||
address={item.address}
|
||||
isLoading={this.state.isLoading}
|
||||
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
|
||||
launchedBy={this.props.route.name}
|
||||
/>
|
||||
{this.state.addresses.length > 1 && (
|
||||
<BlueText style={styles.of}>{loc.formatString(loc._.of, { number: index + 1, total: this.state.addresses.length })}</BlueText>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
onUseAllPressed = () => {
|
||||
@ -1275,13 +1257,13 @@ export default class SendDetails extends Component {
|
||||
text: loc._.ok,
|
||||
onPress: async () => {
|
||||
Keyboard.dismiss();
|
||||
const recipient = this.state.addresses[this.state.recipientsScrollIndex];
|
||||
const recipient = this.state.addresses[this.scrollViewCurrentIndex()];
|
||||
recipient.amount = BitcoinUnit.MAX;
|
||||
recipient.amountSats = BitcoinUnit.MAX;
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
this.setState({
|
||||
addresses: [recipient],
|
||||
units: [BitcoinUnit.BTC],
|
||||
recipientsScrollIndex: 0,
|
||||
isAdvancedTransactionOptionsVisible: false,
|
||||
});
|
||||
},
|
||||
@ -1308,6 +1290,8 @@ export default class SendDetails extends Component {
|
||||
this.setState({ width: e.nativeEvent.layout.width });
|
||||
};
|
||||
|
||||
keyExtractor = (_item, index) => `${index}`;
|
||||
|
||||
render() {
|
||||
const { fromWallet, utxo } = this.state;
|
||||
if (this.state.isLoading || typeof fromWallet === 'undefined') {
|
||||
@ -1328,20 +1312,20 @@ export default class SendDetails extends Component {
|
||||
<StatusBar barStyle="light-content" />
|
||||
<View>
|
||||
<KeyboardAvoidingView behavior="position">
|
||||
<ScrollView
|
||||
pagingEnabled
|
||||
horizontal
|
||||
contentContainerStyle={styles.scrollViewContent}
|
||||
ref={ref => (this.scrollView = ref)}
|
||||
<FlatList
|
||||
keyboardShouldPersistTaps="always"
|
||||
onContentSizeChange={() => this.scrollView.scrollToEnd()}
|
||||
onLayout={() => this.scrollView.scrollToEnd()}
|
||||
onMomentumScrollEnd={this.handlePageChange}
|
||||
scrollEnabled={this.state.addresses.length > 1}
|
||||
extraData={this.state.addresses}
|
||||
data={this.state.addresses}
|
||||
renderItem={this.renderBitcoinTransactionInfoFields}
|
||||
keyExtractor={this.keyExtractor}
|
||||
ref={this.scrollView}
|
||||
horizontal
|
||||
pagingEnabled
|
||||
onMomentumScrollBegin={Keyboard.dismiss}
|
||||
scrollIndicatorInsets={{ top: 0, left: 8, bottom: 0, right: 8 }}
|
||||
>
|
||||
{this.renderBitcoinTransactionInfoFields()}
|
||||
</ScrollView>
|
||||
contentContainerStyle={styles.scrollViewContent}
|
||||
/>
|
||||
<View hide={!this.state.showMemoRow} style={styles.memo}>
|
||||
<TextInput
|
||||
onChangeText={text => this.setState({ memo: text })}
|
||||
|
Loading…
Reference in New Issue
Block a user