Merge branch 'master' into limpbrains-passphrase

This commit is contained in:
Ivan Vershigora 2021-07-07 09:07:36 +03:00
commit 5938a8f8c1
208 changed files with 5485 additions and 4275 deletions

View file

@ -1,75 +0,0 @@
[ignore]
; We fork some components by platform
.*/*[.]android.js
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
node_modules/warning/.*
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
[include]
[libs]
node_modules/react-native/interface.js
node_modules/react-native/flow/
[options]
emoji=true
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
module.file_ext=.js
module.file_ext=.json
module.file_ext=.ios.js
munge_underscores=true
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\1'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
sketchy-number=warn
untyped-type-import=warn
nonstrict-import=warn
deprecated-type=warn
unsafe-getters-setters=warn
inexact-spread=warn
unnecessary-invariant=warn
signature-verification-failure=warn
deprecated-utility=error
[strict]
deprecated-type
nonstrict-import
sketchy-null
unclear-type
unsafe-getters-setters
untyped-import
untyped-type-import
[version]
^0.122.0

3
.gitattributes vendored
View file

@ -1,4 +1,5 @@
*.pbxproj -text
*.patch -text
# specific for windows script files
# Windows files should use crlf line endings
# https://help.github.com/articles/dealing-with-line-endings/
*.bat text eol=crlf

View file

@ -7,7 +7,7 @@ on: [pull_request]
jobs:
test:
runs-on: macos-latest
runs-on: macos-10.15 # tmp fix for https://github.com/ReactiveCircus/android-emulator-runner/issues/160
steps:
- name: Checkout project
uses: actions/checkout@v2
@ -44,6 +44,9 @@ jobs:
HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }}
HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }}
HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }}
FAULTY_ZPUB: ${{ secrets.FAULTY_ZPUB }}
MNEMONICS_COBO: ${{ secrets.MNEMONICS_COBO }}
MNEMONICS_COLDCARD: ${{ secrets.MNEMONICS_COLDCARD }}
e2e:
runs-on: macos-latest
@ -89,6 +92,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
emulator-build: 6110076 # tmp fix for https://github.com/ReactiveCircus/android-emulator-runner/issues/160
target: google_apis
avd-name: Pixel_API_29_AOSP
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none

View file

@ -90,5 +90,3 @@ script:
- npm i -g detox-cli
- npm run e2e:release-build
- npm run e2e:release-test || npm run e2e:release-test || npm run e2e:release-test
after_failure: ./tests/e2e/upload-artifacts.sh

10
App.js
View file

@ -21,8 +21,6 @@ import * as NavigationService from './NavigationService';
import { BlueTextCentered, BlueButton, SecondButton } from './BlueComponents';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Chain } from './models/bitcoinUnits';
import QuickActions from 'react-native-quick-actions';
import * as Sentry from '@sentry/react-native';
import OnAppLaunch from './class/on-app-launch';
import DeeplinkSchemaMatch from './class/deeplink-schema-match';
import loc from './loc';
@ -42,12 +40,6 @@ const A = require('./blue_modules/analytics');
const eventEmitter = new NativeEventEmitter(NativeModules.EventEmitter);
if (process.env.NODE_ENV !== 'development') {
Sentry.init({
dsn: 'https://23377936131848ca8003448a893cb622@sentry.io/1295736',
});
}
const ClipboardContentType = Object.freeze({
BITCOIN: 'BITCOIN',
LIGHTNING: 'LIGHTNING',
@ -125,7 +117,7 @@ const App = () => {
Linking.addEventListener('url', handleOpenURL);
AppState.addEventListener('change', handleAppStateChange);
DeviceEventEmitter.addListener('quickActionShortcut', walletQuickActions);
QuickActions.popInitialAction().then(popInitialAction);
DeviceQuickActions.popInitialAction().then(popInitialAction);
handleAppStateChange(undefined);
/*
When a notification on iOS is shown while the app is on foreground;

View file

@ -15,6 +15,7 @@ const startAndDecrypt = async retry => {
console.log('App already has some wallets, so we are in already started state, exiting startAndDecrypt');
return true;
}
await BlueApp.migrateKeys();
let password = false;
if (await BlueApp.storageIsEncrypted()) {
do {
@ -28,7 +29,7 @@ const startAndDecrypt = async retry => {
} catch (error) {
// in case of exception reading from keystore, lets retry instead of assuming there is no storage and
// proceeding with no wallets
console.warn(error);
console.warn('exception loading from disk:', error);
wasException = true;
}
@ -37,7 +38,9 @@ const startAndDecrypt = async retry => {
try {
await new Promise(resolve => setTimeout(resolve, 3000)); // sleep
success = await BlueApp.loadFromDisk(password);
} catch (_) {}
} catch (error) {
console.warn('second exception loading from disk:', error);
}
}
if (success) {

View file

@ -80,6 +80,7 @@ export const BlueButton = props => {
alignItems: 'center',
paddingHorizontal: 16,
}}
accessibilityRole="button"
{...props}
>
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
@ -101,6 +102,7 @@ export const SecondButton = forwardRef((props, ref) => {
return (
<TouchableOpacity
accessibilityRole="button"
style={{
flex: 1,
borderWidth: 0.7,
@ -127,7 +129,7 @@ export const SecondButton = forwardRef((props, ref) => {
export const BitcoinButton = props => {
const { colors } = useTheme();
return (
<TouchableOpacity testID={props.testID} onPress={props.onPress}>
<TouchableOpacity accessibilityRole="button" testID={props.testID} onPress={props.onPress}>
<View
style={{
borderColor: (props.active && colors.newBlue) || colors.buttonDisabledBackgroundColor,
@ -146,8 +148,19 @@ export const BitcoinButton = props => {
<Image style={{ width: 34, height: 34, marginRight: 8 }} source={require('./img/addWallet/bitcoin.png')} />
</View>
<View>
<Text style={{ color: colors.newBlue, fontWeight: 'bold', fontSize: 18 }}>{loc.wallets.add_bitcoin}</Text>
<Text style={{ color: colors.alternativeTextColor, fontSize: 13, fontWeight: '500' }}>{loc.wallets.add_bitcoin_explain}</Text>
<Text style={{ color: colors.newBlue, fontWeight: 'bold', fontSize: 18, writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr' }}>
{loc.wallets.add_bitcoin}
</Text>
<Text
style={{
color: colors.alternativeTextColor,
fontSize: 13,
fontWeight: '500',
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
}}
>
{loc.wallets.add_bitcoin_explain}
</Text>
</View>
</View>
</View>
@ -158,7 +171,7 @@ export const BitcoinButton = props => {
export const VaultButton = props => {
const { colors } = useTheme();
return (
<TouchableOpacity testID={props.testID} onPress={props.onPress}>
<TouchableOpacity accessibilityRole="button" testID={props.testID} onPress={props.onPress}>
<View
style={{
borderColor: (props.active && colors.foregroundColor) || colors.buttonDisabledBackgroundColor,
@ -176,8 +189,24 @@ export const VaultButton = props => {
<Image style={{ width: 34, height: 34, marginRight: 8 }} source={require('./img/addWallet/vault.png')} />
</View>
<View>
<Text style={{ color: colors.foregroundColor, fontWeight: 'bold', fontSize: 18 }}>{loc.multisig.multisig_vault}</Text>
<Text style={{ color: colors.alternativeTextColor, fontSize: 13, fontWeight: '500' }}>
<Text
style={{
color: colors.foregroundColor,
fontWeight: 'bold',
fontSize: 18,
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
}}
>
{loc.multisig.multisig_vault}
</Text>
<Text
style={{
color: colors.alternativeTextColor,
fontSize: 13,
fontWeight: '500',
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
}}
>
{loc.multisig.multisig_vault_explain}
</Text>
</View>
@ -190,7 +219,7 @@ export const VaultButton = props => {
export const LightningButton = props => {
const { colors } = useTheme();
return (
<TouchableOpacity onPress={props.onPress}>
<TouchableOpacity accessibilityRole="button" onPress={props.onPress}>
<View
style={{
borderColor: (props.active && colors.lnborderColor) || colors.buttonDisabledBackgroundColor,
@ -209,8 +238,21 @@ export const LightningButton = props => {
<Image style={{ width: 34, height: 34, marginRight: 8 }} source={require('./img/addWallet/lightning.png')} />
</View>
<View>
<Text style={{ color: colors.lnborderColor, fontWeight: 'bold', fontSize: 18 }}>{loc.wallets.add_lightning}</Text>
<Text style={{ color: colors.alternativeTextColor, fontSize: 13, fontWeight: '500' }}>{loc.wallets.add_lightning_explain}</Text>
<Text
style={{ color: colors.lnborderColor, fontWeight: 'bold', fontSize: 18, writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr' }}
>
{loc.wallets.add_lightning}
</Text>
<Text
style={{
color: colors.alternativeTextColor,
fontSize: 13,
fontWeight: '500',
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
}}
>
{loc.wallets.add_lightning_explain}
</Text>
</View>
</View>
</View>
@ -383,6 +425,7 @@ export class BlueWalletNavigationHeader extends Component {
}
/>
<TouchableOpacity
accessibilityRole="button"
style={styles.balance}
onPress={this.changeWalletBalanceUnit}
ref={this.walletBalanceText}
@ -409,7 +452,7 @@ export class BlueWalletNavigationHeader extends Component {
)}
</TouchableOpacity>
{this.state.wallet.type === LightningCustodianWallet.type && this.state.allowOnchainAddress && (
<TouchableOpacity onPress={this.manageFundsPressed}>
<TouchableOpacity accessibilityRole="button" onPress={this.manageFundsPressed}>
<View
style={{
marginTop: 14,
@ -437,7 +480,7 @@ export class BlueWalletNavigationHeader extends Component {
</TouchableOpacity>
)}
{this.state.wallet.type === MultisigHDWallet.type && (
<TouchableOpacity onPress={this.manageFundsPressed}>
<TouchableOpacity accessibilityRole="button" onPress={this.manageFundsPressed}>
<View
style={{
marginTop: 14,
@ -473,6 +516,7 @@ export const BlueButtonLink = forwardRef((props, ref) => {
const { colors } = useTheme();
return (
<TouchableOpacity
accessibilityRole="button"
style={{
minHeight: 60,
minWidth: 100,
@ -517,7 +561,7 @@ export const BluePrivateBalance = () => {
export const BlueCopyToClipboardButton = ({ stringToCopy, displayText = false }) => {
return (
<TouchableOpacity onPress={() => Clipboard.setString(stringToCopy)}>
<TouchableOpacity accessibilityRole="button" onPress={() => Clipboard.setString(stringToCopy)}>
<Text style={{ fontSize: 13, fontWeight: '400', color: '#68bbe1' }}>{displayText || loc.transactions.details_copy}</Text>
</TouchableOpacity>
);
@ -559,7 +603,12 @@ export class BlueCopyTextToClipboard extends Component {
render() {
return (
<View style={{ justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
<TouchableOpacity onPress={this.copyToClipboard} disabled={this.state.hasTappedText} testID="BlueCopyTextToClipboard">
<TouchableOpacity
accessibilityRole="button"
onPress={this.copyToClipboard}
disabled={this.state.hasTappedText}
testID="BlueCopyTextToClipboard"
>
<Animated.Text style={styleCopyTextToClipboard.address} numberOfLines={0}>
{this.state.address}
</Animated.Text>
@ -596,7 +645,9 @@ export const BlueText = props => {
{...props}
style={{
color: colors.foregroundColor,
...props.style,
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
}}
/>
);
@ -670,7 +721,17 @@ export const BlueListItem = React.memo(props => {
export const BlueFormLabel = props => {
const { colors } = useTheme();
return <Text {...props} style={{ color: colors.foregroundColor, fontWeight: '400', marginHorizontal: 20 }} />;
return (
<Text
{...props}
style={{
color: colors.foregroundColor,
fontWeight: '400',
marginHorizontal: 20,
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
}}
/>
);
};
export const BlueFormInput = props => {
@ -1141,7 +1202,7 @@ export const BlueReceiveButtonIcon = props => {
const { colors } = useTheme();
return (
<TouchableOpacity {...props} style={{ flex: 1 }}>
<TouchableOpacity accessibilityRole="button" {...props} style={{ flex: 1 }}>
<View
style={{
flex: 1,
@ -1349,7 +1410,7 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
if (item.hash) {
navigate('TransactionStatus', { hash: item.hash });
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') {
const lightningWallet = wallets.filter(wallet => wallet?.getSecret() === item.fromWallet);
const lightningWallet = wallets.filter(wallet => wallet?.getID() === item.walletID);
if (lightningWallet.length === 1) {
try {
// is it a successful lnurl-pay?
@ -1576,6 +1637,7 @@ export class BlueReplaceFeeSuggestions extends Component {
},
].map(({ label, type, time, rate, active }, index) => (
<TouchableOpacity
accessibilityRole="button"
key={label}
onPress={() => this.onFeeSelected(type)}
style={[
@ -1602,6 +1664,7 @@ export class BlueReplaceFeeSuggestions extends Component {
</TouchableOpacity>
))}
<TouchableOpacity
accessibilityRole="button"
onPress={() => this.customTextInput.focus()}
style={[
{ paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 },
@ -1700,6 +1763,7 @@ export const BlueTabs = ({ active, onSwitch, tabs }) => (
{tabs.map((Tab, i) => (
<TouchableOpacity
key={i}
accessibilityRole="button"
onPress={() => onSwitch(i)}
style={[
tabsStyles.tabRoot,
@ -1807,18 +1871,21 @@ export class DynamicQRCode extends Component {
<BlueSpacing20 />
<View style={animatedQRCodeStyle.controller}>
<TouchableOpacity
accessibilityRole="button"
style={[animatedQRCodeStyle.button, { width: '25%', alignItems: 'flex-start' }]}
onPress={this.moveToPreviousFragment}
>
<Text style={animatedQRCodeStyle.text}>{loc.send.dynamic_prev}</Text>
</TouchableOpacity>
<TouchableOpacity
accessibilityRole="button"
style={[animatedQRCodeStyle.button, { width: '50%' }]}
onPress={this.state.intervalHandler ? this.stopAutoMove : this.startAutoMove}
>
<Text style={animatedQRCodeStyle.text}>{this.state.intervalHandler ? loc.send.dynamic_stop : loc.send.dynamic_start}</Text>
</TouchableOpacity>
<TouchableOpacity
accessibilityRole="button"
style={[animatedQRCodeStyle.button, { width: '25%', alignItems: 'flex-end' }]}
onPress={this.moveToNextFragment}
>

View file

@ -80,7 +80,7 @@ import LnurlPay from './screen/lnd/lnurlPay';
import LnurlPaySuccess from './screen/lnd/lnurlPaySuccess';
import UnlockWith from './UnlockWith';
import DrawerList from './screen/wallets/drawerList';
import { isCatalyst, isTablet } from './blue_modules/environment';
import { isDesktop, isTablet } from './blue_modules/environment';
import SettingsPrivacy from './screen/settings/SettingsPrivacy';
import LNDViewAdditionalInvoicePreImage from './screen/lnd/lndViewAdditionalInvoicePreImage';
@ -89,7 +89,6 @@ const defaultScreenOptions =
? ({ route, navigation }) => ({
gestureEnabled: true,
cardOverlayEnabled: true,
cardStyle: { backgroundColor: '#FFFFFF' },
headerStatusBarHeight: navigation.dangerouslyGetState().routes.indexOf(route) > 0 ? 10 : undefined,
...TransitionPresets.ModalPresentationIOS,
gestureResponseDistance: { vertical: Dimensions.get('window').height, horizontal: 50 },
@ -104,7 +103,6 @@ const defaultStackScreenOptions =
? {
gestureEnabled: true,
cardOverlayEnabled: true,
cardStyle: { backgroundColor: '#FFFFFF' },
headerStatusBarHeight: 10,
}
: {
@ -349,7 +347,7 @@ const Drawer = createDrawerNavigator();
function DrawerRoot() {
const dimensions = useWindowDimensions();
const isLargeScreen =
Platform.OS === 'android' ? isTablet() : dimensions.width >= Dimensions.get('screen').width / 2 && (isTablet() || isCatalyst);
Platform.OS === 'android' ? isTablet() : dimensions.width >= Dimensions.get('screen').width / 2 && (isTablet() || isDesktop);
const drawerStyle = { width: '0%' };
return (

View file

@ -97,13 +97,13 @@ const UnlockWith = () => {
const color = colorScheme === 'dark' ? '#FFFFFF' : '#000000';
if ((biometricType === Biometric.TouchID || biometricType === Biometric.Biometrics) && !isStorageEncryptedEnabled) {
return (
<TouchableOpacity disabled={isAuthenticating} onPress={unlockWithBiometrics}>
<TouchableOpacity accessibilityRole="button" disabled={isAuthenticating} onPress={unlockWithBiometrics}>
<Icon name="fingerprint" size={64} type="font-awesome5" color={color} />
</TouchableOpacity>
);
} else if (biometricType === Biometric.FaceID && !isStorageEncryptedEnabled) {
return (
<TouchableOpacity disabled={isAuthenticating} onPress={unlockWithBiometrics}>
<TouchableOpacity accessibilityRole="button" disabled={isAuthenticating} onPress={unlockWithBiometrics}>
<Image
source={colorScheme === 'dark' ? require('./img/faceid-default.png') : require('./img/faceid-dark.png')}
style={styles.icon}
@ -112,7 +112,7 @@ const UnlockWith = () => {
);
} else if (isStorageEncryptedEnabled) {
return (
<TouchableOpacity disabled={isAuthenticating} onPress={unlockWithKey}>
<TouchableOpacity accessibilityRole="button" disabled={isAuthenticating} onPress={unlockWithKey}>
<Icon name="lock" size={64} type="font-awesome5" color={color} />
</TouchableOpacity>
);

View file

@ -69,11 +69,17 @@ function WatchConnectivity() {
if (message.request === 'createInvoice') {
handleLightningInvoiceCreateRequest(message.walletIndex, message.amount, message.description)
.then(createInvoiceRequest => reply({ invoicePaymentRequest: createInvoiceRequest }))
.catch(e => console.log(e));
.catch(e => {
console.log(e);
reply({});
});
} else if (message.message === 'sendApplicationContext') {
sendWalletsToWatch();
reply({});
} else if (message.message === 'fetchTransactions') {
fetchWalletTransactions().then(() => saveToDisk());
fetchWalletTransactions()
.then(() => saveToDisk())
.finally(() => reply({}));
} else if (message.message === 'hideBalance') {
const walletIndex = message.walletIndex;
const wallet = wallets[walletIndex];
@ -110,7 +116,7 @@ function WatchConnectivity() {
if (!Array.isArray(wallets)) {
console.log('No Wallets set to sync with Watch app. Exiting...');
return;
} else if (wallets.length === 0) {
} else if (walletsInitialized && wallets.length === 0) {
console.log('Wallets array is set. No Wallets set to sync with Watch app. Exiting...');
updateApplicationContext({ wallets: [], randomID: Math.floor(Math.random() * 11) });
return;
@ -207,7 +213,7 @@ function WatchConnectivity() {
hideBalance: wallet.hideBalance,
};
if (wallet.chain === Chain.ONCHAIN && wallet.type !== MultisigHDWallet.type) {
walletInformation.push({ xpub: wallet.getXpub() ? wallet.getXpub() : wallet.getSecret() });
walletInformation.xpub = wallet.getXpub() ? wallet.getXpub() : wallet.getSecret();
}
walletsToProcess.push(walletInformation);
}

4
_editorconfig Normal file
View file

@ -0,0 +1,4 @@
# Windows files
[*.bat]
end_of_line = crlf

View file

@ -20,7 +20,7 @@ import com.android.build.OutputFile
* // default. Can be overridden with ENTRY_FILE environment variable.
* entryFile: "index.android.js",
*
* // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format
* // https://reactnative.dev/docs/performance#enable-the-ram-format
* bundleCommand: "ram-bundle",
*
* // whether to bundle JS and assets in debug mode
@ -121,7 +121,10 @@ def jscFlavor = 'org.webkit:android-jsc-intl:+'
def enableHermes = project.ext.react.get("enableHermes", false);
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@ -136,7 +139,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "6.1.3"
versionName "6.1.7"
multiDexEnabled true
missingDimensionStrategy 'react-native-camera', 'general'
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type
@ -150,10 +153,11 @@ android {
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
buildTypes {
release {
// Caution! In production, you need to generate your own keystore file.
// see https://facebook.github.io/react-native/docs/signed-apk-android.
// see https://reactnative.dev/docs/signed-apk-android.
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
@ -164,11 +168,11 @@ android {
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
// Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
output.versionCodeOverride = versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
@ -177,13 +181,8 @@ android {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation files("../../node_modules/react-native-tor/android/libs/sifir_android.aar")
//noinspection GradleDynamicVersion
androidTestImplementation('com.wix:detox:+') {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
}
implementation files("../../node_modules/react-native-tor/android/libs/sifir_android.aar")
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
@ -194,12 +193,17 @@ dependencies {
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
androidTestImplementation('com.wix:detox:+') {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
}
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
@ -217,4 +221,4 @@ task copyDownloadableDepsToLibs(type: Copy) {
}
apply plugin: 'com.google.gms.google-services' // Google Services plugin
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

View file

@ -5,7 +5,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<application
@ -15,7 +16,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:extractNativeLibs="true"
android:requestLegacyExternalStorage="true"
android:requestLegacyExternalStorage="false"
android:usesCleartextTraffic="true"
android:supportsRtl="true"
android:theme="@style/AppTheme">

View file

@ -1,7 +1,7 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:textColor">#000000</item>
</style>

View file

@ -11,14 +11,15 @@ buildscript {
googlePlayServicesIidVersion = "16.0.1"
firebaseVersion = "17.3.4"
firebaseMessagingVersion = "20.2.1"
ndkVersion = "20.1.5948944"
}
repositories {
google()
jcenter()
mavenCentral()
}
ext.kotlinVersion = '1.4.32'
dependencies {
classpath('com.android.tools.build:gradle:4.0.1')
classpath('com.android.tools.build:gradle:4.2.1')
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath 'com.google.gms:google-services:4.3.5' // Google Services plugin
@ -30,6 +31,25 @@ buildscript {
allprojects {
repositories {
jcenter() {
content {
includeModule("com.facebook.yoga", "proguard-annotations")
includeModule("com.facebook.fbjni", "fbjni-java-only")
includeModule("com.facebook.fresco", "fresco")
includeModule("com.facebook.fresco", "stetho")
includeModule("com.facebook.fresco", "fbcore")
includeModule("com.facebook.fresco", "drawee")
includeModule("com.facebook.fresco", "imagepipeline")
includeModule("com.facebook.fresco", "imagepipeline-native")
includeModule("com.facebook.fresco", "memory-type-native")
includeModule("com.facebook.fresco", "memory-type-java")
includeModule("com.facebook.fresco", "nativeimagefilters")
includeModule("com.facebook.stetho", "stetho")
includeModule("com.wei.android.lib", "fingerprintidentify")
includeModule("com.eightbitlab", "blurview")
}
}
mavenCentral()
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
@ -44,7 +64,6 @@ allprojects {
url "$rootDir/../node_modules/detox/Detox-android"
}
google()
jcenter()
maven { url 'https://www.jitpack.io' }
}
}
@ -54,6 +73,9 @@ subprojects {
if (project.hasProperty("android")) {
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 28
}
}
}
}

View file

@ -31,4 +31,4 @@ org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.54.0
FLIPPER_VERSION=0.75.1

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip

7
android/gradlew.bat vendored
View file

@ -37,7 +37,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -51,7 +51,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -82,8 +82,7 @@ set CMD_LINE_ARGS=%*
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

View file

@ -18,13 +18,7 @@ if [ -f $FILENAME ]; then
PR=`node scripts/appcenter-post-build-get-pr-number.js`
echo PR: $PR
# uploading file
HASH=`date +%s`
FILENAME_UNIQ="$APPCENTER_OUTPUT_DIRECTORY/$BRANCH-$HASH.apk"
cp "$FILENAME" "$FILENAME_UNIQ"
curl "http://filestorage.bluewallet.io:1488/upload.php" -F "fileToUpload=@$FILENAME_UNIQ"
rm "$FILENAME_UNIQ"
DLOAD_APK="http://filestorage.bluewallet.io:1488/$BRANCH-$HASH.apk"
DLOAD_APK="https://lambda-download-android-build.herokuapp.com/download/$BUILD_BUILDID"
curl -X POST --data "{\"body\":\"♫ This was a triumph. I'm making a note here: HUGE SUCCESS ♫\n\n [android in browser] $APPURL\n\n[download apk]($DLOAD_APK) \"}" -u "$GITHUB" "https://api.github.com/repos/BlueWallet/BlueWallet/issues/$PR/comments"
fi

View file

@ -1,7 +1,7 @@
/* global alert */
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Alert } from 'react-native';
import { AppStorage, LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet } from '../class';
import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet } from '../class';
import DefaultPreference from 'react-native-default-preference';
import RNWidgetCenter from 'react-native-widget-center';
import loc from '../loc';
@ -12,6 +12,11 @@ const BigNumber = require('bignumber.js');
const torrific = require('../blue_modules/torrific');
const Realm = require('realm');
const ELECTRUM_HOST = 'electrum_host';
const ELECTRUM_TCP_PORT = 'electrum_tcp_port';
const ELECTRUM_SSL_PORT = 'electrum_ssl_port';
const ELECTRUM_SERVER_HISTORY = 'electrum_server_history';
let _realm;
async function _getRealm() {
if (_realm) return _realm;
@ -79,13 +84,13 @@ async function connectMain() {
try {
if (usingPeer.host.endsWith('onion')) {
const randomPeer = await getRandomHardcodedPeer();
await DefaultPreference.set(AppStorage.ELECTRUM_HOST, randomPeer.host);
await DefaultPreference.set(AppStorage.ELECTRUM_TCP_PORT, randomPeer.tcp);
await DefaultPreference.set(AppStorage.ELECTRUM_SSL_PORT, randomPeer.ssl);
await DefaultPreference.set(ELECTRUM_HOST, randomPeer.host);
await DefaultPreference.set(ELECTRUM_TCP_PORT, randomPeer.tcp);
await DefaultPreference.set(ELECTRUM_SSL_PORT, randomPeer.ssl);
} else {
await DefaultPreference.set(AppStorage.ELECTRUM_HOST, usingPeer.host);
await DefaultPreference.set(AppStorage.ELECTRUM_TCP_PORT, usingPeer.tcp);
await DefaultPreference.set(AppStorage.ELECTRUM_SSL_PORT, usingPeer.ssl);
await DefaultPreference.set(ELECTRUM_HOST, usingPeer.host);
await DefaultPreference.set(ELECTRUM_TCP_PORT, usingPeer.tcp);
await DefaultPreference.set(ELECTRUM_SSL_PORT, usingPeer.ssl);
}
RNWidgetCenter.reloadAllTimelines();
@ -187,14 +192,14 @@ async function presentNetworkErrorAlert(usingPeer) {
text: loc._.ok,
style: 'destructive',
onPress: async () => {
await AsyncStorage.setItem(AppStorage.ELECTRUM_HOST, '');
await AsyncStorage.setItem(AppStorage.ELECTRUM_TCP_PORT, '');
await AsyncStorage.setItem(AppStorage.ELECTRUM_SSL_PORT, '');
await AsyncStorage.setItem(ELECTRUM_HOST, '');
await AsyncStorage.setItem(ELECTRUM_TCP_PORT, '');
await AsyncStorage.setItem(ELECTRUM_SSL_PORT, '');
try {
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
await DefaultPreference.clear(AppStorage.ELECTRUM_HOST);
await DefaultPreference.clear(AppStorage.ELECTRUM_SSL_PORT);
await DefaultPreference.clear(AppStorage.ELECTRUM_TCP_PORT);
await DefaultPreference.clear(ELECTRUM_HOST);
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
RNWidgetCenter.reloadAllTimelines();
} catch (e) {
// Must be running on Android
@ -236,9 +241,9 @@ async function getRandomHardcodedPeer() {
}
async function getSavedPeer() {
const host = await AsyncStorage.getItem(AppStorage.ELECTRUM_HOST);
const port = await AsyncStorage.getItem(AppStorage.ELECTRUM_TCP_PORT);
const sslPort = await AsyncStorage.getItem(AppStorage.ELECTRUM_SSL_PORT);
const host = await AsyncStorage.getItem(ELECTRUM_HOST);
const port = await AsyncStorage.getItem(ELECTRUM_TCP_PORT);
const sslPort = await AsyncStorage.getItem(ELECTRUM_SSL_PORT);
return { host, tcp: port, ssl: sslPort };
}
@ -845,6 +850,10 @@ module.exports.setBatchingEnabled = () => {
module.exports.hardcodedPeers = hardcodedPeers;
module.exports.getRandomHardcodedPeer = getRandomHardcodedPeer;
module.exports.ELECTRUM_HOST = ELECTRUM_HOST;
module.exports.ELECTRUM_TCP_PORT = ELECTRUM_TCP_PORT;
module.exports.ELECTRUM_SSL_PORT = ELECTRUM_SSL_PORT;
module.exports.ELECTRUM_SERVER_HISTORY = ELECTRUM_SERVER_HISTORY;
const splitIntoChunks = function (arr, chunkSize) {
const groups = [];

View file

@ -1,9 +1,16 @@
import * as Sentry from '@sentry/react-native';
import amplitude from 'amplitude-js';
import { getVersion, getSystemName } from 'react-native-device-info';
import { getVersion, getSystemName, getUniqueId } from 'react-native-device-info';
import { Platform } from 'react-native';
const BlueApp = require('../BlueApp');
if (process.env.NODE_ENV !== 'development') {
Sentry.init({
dsn: 'https://23377936131848ca8003448a893cb622@sentry.io/1295736',
});
Sentry.setUser({ id: getUniqueId() });
}
amplitude.getInstance().init('8b7cf19e8eea3cdcf16340f5fbf16330', null, {
useNativeDeviceInfo: true,
platform: getSystemName().toLocaleLowerCase().includes('mac') ? getSystemName() : Platform.OS,

View file

@ -4,9 +4,11 @@ import RNWidgetCenter from 'react-native-widget-center';
import * as RNLocalize from 'react-native-localize';
import BigNumber from 'bignumber.js';
import { AppStorage } from '../class';
import { FiatUnit, getFiatRate } from '../models/fiatUnit';
const PREFERRED_CURRENCY = 'preferredCurrency';
const EXCHANGE_RATES = 'currency';
let preferredFiatCurrency = FiatUnit.USD;
const exchangeRates = {};
@ -22,7 +24,7 @@ const STRUCT = {
* @returns {Promise<void>}
*/
async function setPrefferedCurrency(item) {
await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(item));
await AsyncStorage.setItem(PREFERRED_CURRENCY, JSON.stringify(item));
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
await DefaultPreference.set('preferredCurrency', item.endPointKey);
await DefaultPreference.set('preferredCurrencyLocale', item.locale.replace('-', '_'));
@ -30,7 +32,7 @@ async function setPrefferedCurrency(item) {
}
async function getPreferredCurrency() {
const preferredCurrency = await JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY));
const preferredCurrency = await JSON.parse(await AsyncStorage.getItem(PREFERRED_CURRENCY));
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
await DefaultPreference.set('preferredCurrency', preferredCurrency.endPointKey);
await DefaultPreference.set('preferredCurrencyLocale', preferredCurrency.locale.replace('-', '_'));
@ -44,7 +46,7 @@ async function updateExchangeRate() {
}
try {
preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY));
preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(PREFERRED_CURRENCY));
if (preferredFiatCurrency === null) {
throw Error('No Preferred Fiat selected');
}
@ -61,15 +63,15 @@ async function updateExchangeRate() {
try {
rate = await getFiatRate(preferredFiatCurrency.endPointKey);
} catch (Err) {
const lastSavedExchangeRate = JSON.parse(await AsyncStorage.getItem(AppStorage.EXCHANGE_RATES));
const lastSavedExchangeRate = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES));
exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = lastSavedExchangeRate['BTC_' + preferredFiatCurrency.endPointKey] * 1;
return;
}
exchangeRates[STRUCT.LAST_UPDATED] = +new Date();
exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = rate;
await AsyncStorage.setItem(AppStorage.EXCHANGE_RATES, JSON.stringify(exchangeRates));
await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(preferredFiatCurrency));
await AsyncStorage.setItem(EXCHANGE_RATES, JSON.stringify(exchangeRates));
await AsyncStorage.setItem(PREFERRED_CURRENCY, JSON.stringify(preferredFiatCurrency));
await setPrefferedCurrency(preferredFiatCurrency);
}
@ -179,3 +181,5 @@ module.exports.btcToSatoshi = btcToSatoshi;
module.exports.getCurrencySymbol = getCurrencySymbol;
module.exports._setPreferredFiatCurrency = _setPreferredFiatCurrency; // export it to mock data in tests
module.exports._setExchangeRate = _setExchangeRate; // export it to mock data in tests
module.exports.PREFERRED_CURRENCY = PREFERRED_CURRENCY;
module.exports.EXCHANGE_RATES = EXCHANGE_RATES;

View file

@ -1,8 +1,8 @@
import { getSystemName, isTablet } from 'react-native-device-info';
import isCatalyst from 'react-native-is-catalyst';
import { getSystemName, isTablet, getDeviceType } from 'react-native-device-info';
const isMacCatalina = getSystemName() === 'Mac OS X';
module.exports.isMacCatalina = isMacCatalina;
module.exports.isCatalyst = isCatalyst;
module.exports.isDesktop = getDeviceType() === 'Desktop';
module.exports.isHandset = getDeviceType() === 'Handset';
module.exports.isTablet = isTablet;

View file

@ -4,9 +4,9 @@ import RNFS from 'react-native-fs';
import Share from 'react-native-share';
import loc from '../loc';
import DocumentPicker from 'react-native-document-picker';
import isCatalyst from 'react-native-is-catalyst';
import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
import { presentCameraNotAuthorizedAlert } from '../class/camera';
import { isDesktop } from '../blue_modules/environment';
import ActionSheet from '../screen/ActionSheet';
import BlueClipboard from './clipboard';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
@ -17,7 +17,7 @@ const writeFileAndExport = async function (filename, contents) {
await RNFS.writeFile(filePath, contents);
Share.open({
url: 'file://' + filePath,
saveToFiles: isCatalyst,
saveToFiles: isDesktop,
})
.catch(error => {
console.log(error);

View file

@ -576,16 +576,30 @@
"dev": true
},
"browserslist": {
"version": "4.16.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz",
"integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==",
"version": "4.16.6",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
"integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001208",
"caniuse-lite": "^1.0.30001219",
"colorette": "^1.2.2",
"electron-to-chromium": "^1.3.712",
"electron-to-chromium": "^1.3.723",
"escalade": "^3.1.1",
"node-releases": "^1.1.71"
},
"dependencies": {
"caniuse-lite": {
"version": "1.0.30001228",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz",
"integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==",
"dev": true
},
"electron-to-chromium": {
"version": "1.3.737",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.737.tgz",
"integrity": "sha512-P/B84AgUSQXaum7a8m11HUsYL8tj9h/Pt5f7Hg7Ty6bm5DxlFq+e5+ouHUoNQMsKDJ7u4yGfI8mOErCmSH9wyg==",
"dev": true
}
}
},
"cache-base": {
@ -622,12 +636,6 @@
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001208",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz",
"integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@ -1016,12 +1024,6 @@
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
"dev": true
},
"electron-to-chromium": {
"version": "1.3.713",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.713.tgz",
"integrity": "sha512-HWgkyX4xTHmxcWWlvv7a87RHSINEcpKYZmDMxkUlHcY+CJcfx7xEfBHuXVsO1rzyYs1WQJ7EgDp2CoErakBIow==",
"dev": true
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",

View file

@ -2,10 +2,11 @@
import { useAsyncStorage } from '@react-native-async-storage/async-storage';
import React, { createContext, useEffect, useState } from 'react';
import { LayoutAnimation } from 'react-native';
import { AppStorage } from '../class';
import { FiatUnit } from '../models/fiatUnit';
import loc from '../loc';
const BlueApp = require('../BlueApp');
const BlueElectrum = require('./BlueElectrum');
const currency = require('../blue_modules/currency');
const _lastTimeTriedToRefetchWallet = {}; // hashmap of timestamps we _started_ refetching some wallet
@ -19,14 +20,14 @@ export const BlueStorageProvider = ({ children }) => {
const [walletsInitialized, setWalletsInitialized] = useState(false);
const [preferredFiatCurrency, _setPreferredFiatCurrency] = useState(FiatUnit.USD);
const [language, _setLanguage] = useState();
const getPreferredCurrencyAsyncStorage = useAsyncStorage(AppStorage.PREFERRED_CURRENCY).getItem;
const getLanguageAsyncStorage = useAsyncStorage(AppStorage.LANG).getItem;
const getPreferredCurrencyAsyncStorage = useAsyncStorage(currency.PREFERRED_CURRENCY).getItem;
const getLanguageAsyncStorage = useAsyncStorage(loc.LANG).getItem;
const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false);
const [isDrawerListBlurred, _setIsDrawerListBlurred] = useState(false);
const setIsHandOffUseEnabledAsyncStorage = value => {
setIsHandOffUseEnabled(value);
return BlueApp.setItem(AppStorage.HANDOFF_STORAGE_KEY, value === true ? '1' : '');
return BlueApp.setIsHandoffEnabled(value);
};
const saveToDisk = async () => {
@ -43,7 +44,7 @@ export const BlueStorageProvider = ({ children }) => {
useEffect(() => {
(async () => {
try {
const enabledHandoff = await BlueApp.getItem(AppStorage.HANDOFF_STORAGE_KEY);
const enabledHandoff = await BlueApp.isHandoffEnabled();
setIsHandOffUseEnabled(!!enabledHandoff);
} catch (_e) {
setIsHandOffUseEnabledAsyncStorage(false);

View file

@ -1,6 +1,7 @@
/* global alert */
import AsyncStorage from '@react-native-async-storage/async-storage';
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
import * as Keychain from 'react-native-keychain';
import {
HDLegacyBreadwalletWallet,
HDSegwitP2SHWallet,
@ -20,21 +21,16 @@ import {
SLIP39LegacyP2PKHWallet,
SLIP39SegwitBech32Wallet,
} from './';
import { randomBytes } from './rng';
const encryption = require('../blue_modules/encryption');
const Realm = require('realm');
const createHash = require('create-hash');
let usedBucketNum = false;
let savingInProgress = 0; // its both a flag and a counter of attempts to write to disk
export class AppStorage {
static FLAG_ENCRYPTED = 'data_encrypted';
static LANG = 'lang';
static EXCHANGE_RATES = 'currency';
static LNDHUB = 'lndhub';
static ELECTRUM_HOST = 'electrum_host';
static ELECTRUM_TCP_PORT = 'electrum_tcp_port';
static ELECTRUM_SSL_PORT = 'electrum_ssl_port';
static ELECTRUM_SERVER_HISTORY = 'electrum_server_history';
static PREFERRED_CURRENCY = 'preferredCurrency';
static ADVANCED_MODE_ENABLED = 'advancedmodeenabled';
static DO_NOT_TRACK = 'donottrack';
static HODL_HODL_API_KEY = 'HODL_HODL_API_KEY';
@ -42,6 +38,8 @@ export class AppStorage {
static HODL_HODL_CONTRACTS = 'HODL_HODL_CONTRACTS';
static HANDOFF_STORAGE_KEY = 'HandOff';
static keys2migrate = [AppStorage.HANDOFF_STORAGE_KEY, AppStorage.DO_NOT_TRACK, AppStorage.ADVANCED_MODE_ENABLED];
constructor() {
/** {Array.<AbstractWallet>} */
this.wallets = [];
@ -49,6 +47,19 @@ export class AppStorage {
this.cachedPassword = false;
}
async migrateKeys() {
if (!(typeof navigator !== 'undefined' && navigator.product === 'ReactNative')) return;
for (const key of this.constructor.keys2migrate) {
try {
const value = await RNSecureKeyStore.get(key);
if (value) {
await AsyncStorage.setItem(key, value);
await RNSecureKeyStore.remove(key);
}
} catch (_) {}
}
}
/**
* Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is
* used for cli/tests
@ -80,11 +91,36 @@ export class AppStorage {
}
};
/**
* @throws Error
* @param key {string}
* @returns {Promise<*>|null}
*/
getItemWithFallbackToRealm = async key => {
let value;
try {
return await this.getItem(key);
} catch (error) {
console.warn('error reading', key, error.message);
console.warn('fallback to realm');
const realmKeyValue = await this.openRealmKeyValue();
const obj = realmKeyValue.objectForPrimaryKey('KeyValue', key); // search for a realm object with a primary key
value = obj?.value;
realmKeyValue.close();
if (value) {
console.warn('successfully recovered', value.length, 'bytes from realm for key', key);
return value;
}
return null;
}
};
storageIsEncrypted = async () => {
let data;
try {
data = await this.getItem(AppStorage.FLAG_ENCRYPTED);
data = await this.getItemWithFallbackToRealm(AppStorage.FLAG_ENCRYPTED);
} catch (error) {
console.warn('error reading `' + AppStorage.FLAG_ENCRYPTED + '` key:', error.message);
return false;
}
@ -212,6 +248,58 @@ export class AppStorage {
});
}
/**
* Returns instace of the Realm database, which is encrypted by device unique id
* Database file is static.
*
* @returns {Promise<Realm>}
*/
async openRealmKeyValue() {
const service = 'realm_encryption_key';
let password;
const credentials = await Keychain.getGenericPassword({ service });
if (credentials) {
password = credentials.password;
} else {
const buf = await randomBytes(64);
password = buf.toString('hex');
await Keychain.setGenericPassword(service, password, { service });
}
const buf = Buffer.from(password, 'hex');
const encryptionKey = Int8Array.from(buf);
const path = 'keyvalue.realm';
const schema = [
{
name: 'KeyValue',
primaryKey: 'key',
properties: {
key: { type: 'string', indexed: true },
value: 'string', // stringified json, or whatever
},
},
];
return Realm.open({
schema,
path,
encryptionKey,
});
}
saveToRealmKeyValue(realmkeyValue, key, value) {
realmkeyValue.write(() => {
realmkeyValue.create(
'KeyValue',
{
key: key,
value: value,
},
Realm.UpdateMode.Modified,
);
});
}
/**
* Loads from storage all wallets and
* maps them to `this.wallets`
@ -220,7 +308,7 @@ export class AppStorage {
* @returns {Promise.<boolean>}
*/
async loadFromDisk(password) {
let data = await this.getItem('data');
let data = await this.getItemWithFallbackToRealm('data');
if (password) {
data = this.decryptData(data, password);
if (data) {
@ -406,71 +494,87 @@ export class AppStorage {
* @returns {Promise} Result of storage save
*/
async saveToDisk() {
const walletsToSave = [];
const realm = await this.getRealm();
for (const key of this.wallets) {
if (typeof key === 'boolean' || key.type === PlaceholderWallet.type) continue;
key.prepareForSerialization();
delete key.current;
const keyCloned = Object.assign({}, key); // stripped-down version of a wallet to save to secure keystore
if (key._hdWalletInstance) keyCloned._hdWalletInstance = Object.assign({}, key._hdWalletInstance);
this.offloadWalletToRealm(realm, key);
// stripping down:
if (key._txs_by_external_index) {
keyCloned._txs_by_external_index = {};
keyCloned._txs_by_internal_index = {};
}
if (key._hdWalletInstance) {
keyCloned._hdWalletInstance._txs_by_external_index = {};
keyCloned._hdWalletInstance._txs_by_internal_index = {};
}
walletsToSave.push(JSON.stringify({ ...keyCloned, type: keyCloned.type }));
if (savingInProgress) {
console.warn('saveToDisk is in progress');
if (++savingInProgress > 10) alert('Critical error. Last actions were not saved'); // should never happen
await new Promise(resolve => setTimeout(resolve, 1000 * savingInProgress)); // sleep
return this.saveToDisk();
}
realm.close();
let data = {
wallets: walletsToSave,
tx_metadata: this.tx_metadata,
};
savingInProgress = 1;
if (this.cachedPassword) {
// should find the correct bucket, encrypt and then save
let buckets = await this.getItem('data');
buckets = JSON.parse(buckets);
const newData = [];
let num = 0;
for (const bucket of buckets) {
let decrypted;
// if we had `usedBucketNum` during loadFromDisk(), no point to try to decode each bucket to find the one we
// need, we just to find bucket with the same index
if (usedBucketNum !== false) {
if (num === usedBucketNum) {
decrypted = true;
}
num++;
} else {
// we dont have `usedBucketNum` for whatever reason, so lets try to decrypt each bucket after bucket
// till we find the right one
decrypted = encryption.decrypt(bucket, this.cachedPassword);
}
if (!decrypted) {
// no luck decrypting, its not our bucket
newData.push(bucket);
} else {
// decrypted ok, this is our bucket
// we serialize our object's data, encrypt it, and add it to buckets
newData.push(encryption.encrypt(JSON.stringify(data), this.cachedPassword));
await this.setItem(AppStorage.FLAG_ENCRYPTED, '1');
}
}
data = newData;
} else {
await this.setItem(AppStorage.FLAG_ENCRYPTED, ''); // drop the flag
}
try {
return await this.setItem('data', JSON.stringify(data));
const walletsToSave = [];
const realm = await this.getRealm();
for (const key of this.wallets) {
if (typeof key === 'boolean' || key.type === PlaceholderWallet.type) continue;
key.prepareForSerialization();
delete key.current;
const keyCloned = Object.assign({}, key); // stripped-down version of a wallet to save to secure keystore
if (key._hdWalletInstance) keyCloned._hdWalletInstance = Object.assign({}, key._hdWalletInstance);
this.offloadWalletToRealm(realm, key);
// stripping down:
if (key._txs_by_external_index) {
keyCloned._txs_by_external_index = {};
keyCloned._txs_by_internal_index = {};
}
if (key._hdWalletInstance) {
keyCloned._hdWalletInstance._txs_by_external_index = {};
keyCloned._hdWalletInstance._txs_by_internal_index = {};
}
walletsToSave.push(JSON.stringify({ ...keyCloned, type: keyCloned.type }));
}
realm.close();
let data = {
wallets: walletsToSave,
tx_metadata: this.tx_metadata,
};
if (this.cachedPassword) {
// should find the correct bucket, encrypt and then save
let buckets = await this.getItemWithFallbackToRealm('data');
buckets = JSON.parse(buckets);
const newData = [];
let num = 0;
for (const bucket of buckets) {
let decrypted;
// if we had `usedBucketNum` during loadFromDisk(), no point to try to decode each bucket to find the one we
// need, we just to find bucket with the same index
if (usedBucketNum !== false) {
if (num === usedBucketNum) {
decrypted = true;
}
num++;
} else {
// we dont have `usedBucketNum` for whatever reason, so lets try to decrypt each bucket after bucket
// till we find the right one
decrypted = encryption.decrypt(bucket, this.cachedPassword);
}
if (!decrypted) {
// no luck decrypting, its not our bucket
newData.push(bucket);
} else {
// decrypted ok, this is our bucket
// we serialize our object's data, encrypt it, and add it to buckets
newData.push(encryption.encrypt(JSON.stringify(data), this.cachedPassword));
}
}
data = newData;
}
await this.setItem('data', JSON.stringify(data));
await this.setItem(AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : '');
// now, backing up same data in realm:
const realmkeyValue = await this.openRealmKeyValue();
this.saveToRealmKeyValue(realmkeyValue, 'data', JSON.stringify(data));
this.saveToRealmKeyValue(realmkeyValue, AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : '');
realmkeyValue.close();
} catch (error) {
alert(error.message);
console.error('save to disk exception:', error.message);
alert('save to disk exception: ' + error.message);
} finally {
savingInProgress = 0;
}
}
@ -645,24 +749,35 @@ export class AppStorage {
isAdancedModeEnabled = async () => {
try {
return !!(await this.getItem(AppStorage.ADVANCED_MODE_ENABLED));
return !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED));
} catch (_) {}
return false;
};
setIsAdancedModeEnabled = async value => {
await this.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : '');
await AsyncStorage.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : '');
};
isHandoffEnabled = async () => {
try {
return !!(await AsyncStorage.getItem(AppStorage.HANDOFF_STORAGE_KEY));
} catch (_) {}
return false;
};
setIsHandoffEnabled = async value => {
await AsyncStorage.setItem(AppStorage.HANDOFF_STORAGE_KEY, value ? '1' : '');
};
isDoNotTrackEnabled = async () => {
try {
return !!(await this.getItem(AppStorage.DO_NOT_TRACK));
return !!(await AsyncStorage.getItem(AppStorage.DO_NOT_TRACK));
} catch (_) {}
return false;
};
setDoNotTrack = async value => {
await this.setItem(AppStorage.DO_NOT_TRACK, value ? '1' : '');
await AsyncStorage.setItem(AppStorage.DO_NOT_TRACK, value ? '1' : '');
};
/**

View file

@ -8,9 +8,10 @@ import RNSecureKeyStore from 'react-native-secure-key-store';
import loc from '../loc';
import { useContext } from 'react';
import { BlueStorageContext } from '../blue_modules/storage-context';
import * as Sentry from '@sentry/react-native';
function Biometric() {
const { getItem, setItem, setResetOnAppUninstallTo } = useContext(BlueStorageContext);
const { getItem, setItem } = useContext(BlueStorageContext);
Biometric.STORAGEKEY = 'Biometrics';
Biometric.FaceID = 'Face ID';
Biometric.TouchID = 'Touch ID';
@ -42,10 +43,9 @@ function Biometric() {
try {
const enabledBiometrics = await getItem(Biometric.STORAGEKEY);
return !!enabledBiometrics;
} catch (_e) {
await setItem(Biometric.STORAGEKEY, '');
return false;
}
} catch (_) {}
return false;
};
Biometric.isBiometricUseCapableAndEnabled = async () => {
@ -72,10 +72,10 @@ function Biometric() {
};
Biometric.clearKeychain = async () => {
Sentry.captureMessage('Biometric.clearKeychain()');
await RNSecureKeyStore.remove('data');
await RNSecureKeyStore.remove('data_encrypted');
await RNSecureKeyStore.remove(Biometric.STORAGEKEY);
await setResetOnAppUninstallTo(true);
NavigationService.dispatch(StackActions.replace('WalletsRoot'));
};

View file

@ -254,8 +254,7 @@ class DeeplinkSchemaMatch {
{
screen: 'LappBrowser',
params: {
fromSecret: lnWallet.getSecret(),
fromWallet: lnWallet,
walletID: lnWallet.getID(),
url: urlObject.query.url,
},
},

View file

@ -34,6 +34,12 @@ function DeviceQuickActions() {
});
};
DeviceQuickActions.popInitialAction = async () => {
const data = await QuickActions.popInitialAction();
return data;
};
DeviceQuickActions.getEnabled = async () => {
try {
const isEnabled = await AsyncStorage.getItem(DeviceQuickActions.STORAGE_KEY);

View file

@ -7,6 +7,8 @@ function DeviceQuickActions() {
return false;
};
DeviceQuickActions.popInitialAction = () => {};
return null;
}

View file

@ -230,6 +230,15 @@ function WalletImport() {
// if we're here - nope, its not a valid WIF
// maybe its a watch-only address?
const watchOnly = new WatchOnlyWallet();
watchOnly.setSecret(importText);
if (watchOnly.valid()) {
await watchOnly.fetchBalance();
return WalletImport._saveWallet(watchOnly, additionalProperties);
}
// nope, not watch-only
try {
const hdElectrumSeedLegacy = new HDSegwitElectrumSeedP2WPKHWallet();
hdElectrumSeedLegacy.setSecret(importText);
@ -270,15 +279,6 @@ function WalletImport() {
}
} catch (_) {}
// not valid? maybe its a watch-only address?
const watchOnly = new WatchOnlyWallet();
watchOnly.setSecret(importText);
if (watchOnly.valid()) {
await watchOnly.fetchBalance();
return WalletImport._saveWallet(watchOnly, additionalProperties);
}
// if it is multi-line string, then it is probably SLIP39 wallet
// each line - one share
if (importText.includes('\n')) {
@ -298,9 +298,7 @@ function WalletImport() {
const s3 = new SLIP39SegwitBech32Wallet();
s3.setSecret(importText);
if (await s3.wasEverUsed()) {
return WalletImport._saveWallet(s3);
}
return WalletImport._saveWallet(s3);
}
}

View file

@ -824,11 +824,18 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
}
weOwnAddress(address) {
if (!address) return false;
let cleanAddress = address;
if (this.segwitType === 'p2wpkh') {
cleanAddress = address.toLowerCase();
}
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
if (this._getExternalAddressByIndex(c) === address) return true;
if (this._getExternalAddressByIndex(c) === cleanAddress) return true;
}
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
if (this._getInternalAddressByIndex(c) === address) return true;
if (this._getInternalAddressByIndex(c) === cleanAddress) return true;
}
return false;
}

View file

@ -247,15 +247,6 @@ export class AbstractHDWallet extends LegacyWallet {
throw new Error('Not implemented');
}
weOwnAddress(addr) {
const hashmap = {};
for (const a of this.usedAddresses) {
hashmap[a] = 1;
}
return hashmap[addr] === 1;
}
_getDerivationPathByAddress(address) {
throw new Error('Not implemented');
}

View file

@ -162,4 +162,9 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
return psbt;
}
_getDerivationPathByAddress(address, BIP = 44) {
// only changing defaults for function arguments
return super._getDerivationPathByAddress(address, BIP);
}
}

View file

@ -148,4 +148,9 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
});
return address;
}
_getDerivationPathByAddress(address, BIP = 49) {
// only changing defaults for function arguments
return super._getDerivationPathByAddress(address, BIP);
}
}

View file

@ -465,7 +465,14 @@ export class LegacyWallet extends AbstractWallet {
}
weOwnAddress(address) {
return this.getAddress() === address || this._address === address;
if (!address) return false;
let cleanAddress = address;
if (this.segwitType === 'p2wpkh') {
cleanAddress = address.toLowerCase();
}
return this.getAddress() === cleanAddress || this._address === cleanAddress;
}
weOwnTransaction(txid) {

View file

@ -377,7 +377,7 @@ export class LightningCustodianWallet extends LegacyWallet {
txs = txs.concat(this.pending_transactions_raw.slice(), this.transactions_raw.slice().reverse(), this.user_invoices_raw.slice()); // slice so array is cloned
// transforming to how wallets/list screen expects it
for (const tx of txs) {
tx.fromWallet = this.getSecret();
tx.walletID = this.getID();
if (tx.amount) {
// pending tx
tx.amt = tx.amount * -100000000;

View file

@ -221,6 +221,8 @@ export class WatchOnlyWallet extends LegacyWallet {
throw new Error('Not initialized');
}
if (address && address.startsWith('BC1')) address = address.toLowerCase();
return this.getAddress() === address;
}

View file

@ -66,10 +66,15 @@ const AddressInput = ({
});
}
}}
accessibilityRole="button"
style={[styles.scan, stylesHook.scan]}
accessibilityLabel={loc.send.details_scan}
accessibilityHint={loc.send.details_scan_hint}
>
<Image source={require('../img/scan-white.png')} />
<Text style={[styles.scanText, stylesHook.scanText]}>{loc.send.details_scan}</Text>
<Image source={require('../img/scan-white.png')} accessible={false} />
<Text style={[styles.scanText, stylesHook.scanText]} accessible={false}>
{loc.send.details_scan}
</Text>
</TouchableOpacity>
</View>
);

View file

@ -240,7 +240,12 @@ class AmountInput extends Component {
</View>
</View>
{!disabled && amount !== BitcoinUnit.MAX && (
<TouchableOpacity testID="changeAmountUnitButton" style={styles.changeAmountUnit} onPress={this.changeAmountUnit}>
<TouchableOpacity
accessibilityRole="button"
testID="changeAmountUnitButton"
style={styles.changeAmountUnit}
onPress={this.changeAmountUnit}
>
<Image source={require('../img/round-compare-arrows-24-px.png')} />
</TouchableOpacity>
)}

View file

@ -1,21 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, useWindowDimensions } from 'react-native';
import { StyleSheet, Platform, useWindowDimensions, View } from 'react-native';
import Modal from 'react-native-modal';
import { BlueButton, BlueSpacing40 } from '../BlueComponents';
import loc from '../loc';
import { useTheme } from '@react-navigation/native';
const styles = StyleSheet.create({
root: {
justifyContent: 'flex-end',
margin: 0,
},
hasDoneButton: {
padding: 16,
paddingBottom: 24,
},
});
const BottomModal = ({ onBackButtonPress, onBackdropPress, onClose, windowHeight, windowWidth, ...props }) => {
const BottomModal = ({ onBackButtonPress, onBackdropPress, onClose, windowHeight, windowWidth, doneButton, ...props }) => {
const valueWindowHeight = useWindowDimensions().height;
const valueWindowWidth = useWindowDimensions().width;
const handleBackButtonPress = onBackButtonPress ?? onClose;
const handleBackdropPress = onBackdropPress ?? onClose;
const { colors } = useTheme();
const stylesHook = StyleSheet.create({
hasDoneButton: {
backgroundColor: colors.elevated,
},
});
return (
<Modal
style={styles.root}
@ -26,8 +38,16 @@ const BottomModal = ({ onBackButtonPress, onBackdropPress, onClose, windowHeight
{...props}
accessibilityViewIsModal
useNativeDriver
useNativeDriverForBackdrop
/>
useNativeDriverForBackdrop={Platform.OS === 'android'}
>
{props.children}
{doneButton && (
<View style={[styles.hasDoneButton, stylesHook.hasDoneButton]}>
<BlueButton title={loc.send.input_done} onPress={onClose} />
<BlueSpacing40 />
</View>
)}
</Modal>
);
};
@ -36,6 +56,7 @@ BottomModal.propTypes = {
onBackButtonPress: PropTypes.func,
onBackdropPress: PropTypes.func,
onClose: PropTypes.func,
doneButton: PropTypes.bool,
windowHeight: PropTypes.number,
windowWidth: PropTypes.number,
};

View file

@ -35,11 +35,11 @@ const styles = StyleSheet.create({
});
const CoinsSelected = ({ number, onContainerPress, onClose }) => (
<TouchableOpacity style={styles.root} onPress={onContainerPress}>
<TouchableOpacity accessibilityRole="button" style={styles.root} onPress={onContainerPress}>
<View style={styles.labelContainer}>
<Text style={styles.labelText}>{loc.formatString(loc.cc.coins_selected, { number })}</Text>
</View>
<TouchableOpacity style={styles.buttonContainer} onPress={onClose}>
<TouchableOpacity accessibilityRole="button" style={styles.buttonContainer} onPress={onClose}>
<Avatar rounded containerStyle={[styles.ball]} icon={{ name: 'close', size: 22, type: 'ionicons', color: 'white' }} />
</TouchableOpacity>
</TouchableOpacity>

View file

@ -105,6 +105,7 @@ export class DynamicQRCode extends Component {
return (
<View style={animatedQRCodeStyle.container}>
<TouchableOpacity
accessibilityRole="button"
testID="DynamicCode"
style={animatedQRCodeStyle.qrcodeContainer}
onPress={() => {
@ -136,18 +137,21 @@ export class DynamicQRCode extends Component {
<BlueSpacing20 />
<View style={animatedQRCodeStyle.controller}>
<TouchableOpacity
accessibilityRole="button"
style={[animatedQRCodeStyle.button, { width: '25%', alignItems: 'flex-start' }]}
onPress={this.moveToPreviousFragment}
>
<Text style={animatedQRCodeStyle.text}>{loc.send.dynamic_prev}</Text>
</TouchableOpacity>
<TouchableOpacity
accessibilityRole="button"
style={[animatedQRCodeStyle.button, { width: '50%' }]}
onPress={this.state.intervalHandler ? this.stopAutoMove : this.startAutoMove}
>
<Text style={animatedQRCodeStyle.text}>{this.state.intervalHandler ? loc.send.dynamic_stop : loc.send.dynamic_start}</Text>
</TouchableOpacity>
<TouchableOpacity
accessibilityRole="button"
style={[animatedQRCodeStyle.button, { width: '25%', alignItems: 'flex-end' }]}
onPress={this.moveToNextFragment}
>

View file

@ -119,7 +119,7 @@ export const FButton = ({ text, icon, width, first, last, ...props }) => {
}
return (
<TouchableOpacity style={[bStyles.root, bStylesHook.root, style]} {...props}>
<TouchableOpacity accessibilityRole="button" style={[bStyles.root, bStylesHook.root, style]} {...props}>
<View style={bStyles.icon}>{icon}</View>
<Text numberOfLines={1} style={[bStyles.text, props.disabled ? bStylesHook.textDisabled : bStylesHook.text]}>
{text}

View file

@ -139,6 +139,7 @@ const MultipleStepsListItem = props => {
{props.button.buttonType === undefined ||
(props.button.buttonType === MultipleStepsListItemButtohType.full && (
<TouchableOpacity
accessibilityRole="button"
disabled={props.button.disabled}
style={[styles.provideKeyButton, stylesHook.provideKeyButton, buttonOpacity]}
onPress={props.button.onPress}
@ -152,6 +153,7 @@ const MultipleStepsListItem = props => {
{props.button.leftText}
</Text>
<TouchableOpacity
accessibilityRole="button"
disabled={props.button.disabled}
style={[styles.rowPartialRightButton, stylesHook.provideKeyButton, rightButtonOpacity]}
onPress={props.button.onPress}
@ -166,7 +168,12 @@ const MultipleStepsListItem = props => {
)}
{!showActivityIndicator && props.rightButton && checked && (
<View style={styles.rightButtonContainer} accessibilityComponentType>
<TouchableOpacity disabled={props.rightButton.disabled} style={styles.rightButton} onPress={props.rightButton.onPress}>
<TouchableOpacity
accessibilityRole="button"
disabled={props.rightButton.disabled}
style={styles.rightButton}
onPress={props.rightButton.onPress}
>
<Text style={[styles.provideKeyButtonText, stylesHook.provideKeyButtonText]}>{props.rightButton.text}</Text>
</TouchableOpacity>
</View>

View file

@ -29,6 +29,7 @@ export const SquareButton = forwardRef((props, ref) => {
}}
{...props}
ref={ref}
accessibilityRole="button"
>
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}

View file

@ -35,7 +35,11 @@ const SquareEnumeratedWords = props => {
);
} else {
component.push(
<TouchableOpacity style={[styles.entryTextContainer, stylesHook.entryTextContainer]} key={`${secret}${index}`}>
<TouchableOpacity
accessibilityRole="button"
style={[styles.entryTextContainer, stylesHook.entryTextContainer]}
key={`${secret}${index}`}
>
<Text textBreakStrategy="simple" style={[styles.entryText, stylesHook.entryText]}>
{secret}
</Text>

View file

@ -1,4 +1,4 @@
import React, { useRef, useCallback, useState, useImperativeHandle, forwardRef, useContext } from 'react';
import React, { useRef, useCallback, useImperativeHandle, forwardRef, useContext } from 'react';
import PropTypes from 'prop-types';
import {
ActivityIndicator,
@ -12,22 +12,19 @@ import {
TouchableWithoutFeedback,
useWindowDimensions,
View,
FlatList,
} from 'react-native';
import { useTheme } from '@react-navigation/native';
import LinearGradient from 'react-native-linear-gradient';
import Carousel from 'react-native-snap-carousel';
import loc, { formatBalance, transactionTimeToReadable } from '../loc';
import { LightningCustodianWallet, MultisigHDWallet, PlaceholderWallet } from '../class';
import WalletGradient from '../class/wallet-gradient';
import { BluePrivateBalance } from '../BlueComponents';
import { BlueStorageContext } from '../blue_modules/storage-context';
import { isHandset } from '../blue_modules/environment';
const nStyles = StyleSheet.create({
root: {
marginVertical: 17,
paddingRight: 10,
},
root: {},
container: {
paddingHorizontal: 24,
paddingVertical: 16,
@ -58,9 +55,12 @@ const nStyles = StyleSheet.create({
const NewWalletPanel = ({ onPress }) => {
const { colors } = useTheme();
const { width } = useWindowDimensions();
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
return (
<TouchableOpacity testID="CreateAWallet" onPress={onPress} style={nStyles.root}>
<View style={[nStyles.container, { backgroundColor: WalletGradient.createWallet() }]}>
<TouchableOpacity accessibilityRole="button" testID="CreateAWallet" onPress={onPress} style={{ width: itemWidth * 1.2 }}>
<View style={[nStyles.container, { backgroundColor: WalletGradient.createWallet(), width: itemWidth }]}>
<Text style={[nStyles.addAWAllet, { color: colors.foregroundColor }]}>{loc.wallets.list_create_a_wallet}</Text>
<Text style={[nStyles.addLine, { color: colors.alternativeTextColor }]}>{loc.wallets.list_create_a_wallet_text}</Text>
<View style={nStyles.button}>
@ -76,10 +76,7 @@ NewWalletPanel.propTypes = {
};
const iStyles = StyleSheet.create({
root: {
paddingRight: 10,
marginVertical: 17,
},
root: { paddingRight: 20 },
grad: {
padding: 15,
borderRadius: 12,
@ -132,6 +129,8 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
const scaleValue = new Animated.Value(1.0);
const { colors } = useTheme();
const { walletTransactionUpdateStatus } = useContext(BlueStorageContext);
const { width } = useWindowDimensions();
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
const onPressedIn = () => {
const props = { duration: 50 };
@ -160,7 +159,7 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
if (item.type === PlaceholderWallet.type) {
return (
<Animated.View
style={[iStyles.root, { transform: [{ scale: scaleValue }] }]}
style={[iStyles.root, { width: itemWidth }, { transform: [{ scale: scaleValue }] }]}
shadowOpacity={40 / 100}
shadowOffset={{ width: 0, height: 0 }}
shadowRadius={5}
@ -224,7 +223,7 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
return (
<Animated.View
style={[iStyles.root, { opacity, transform: [{ scale: scaleValue }] }]}
style={[iStyles.root, { width: itemWidth }, { opacity, transform: [{ scale: scaleValue }] }]}
shadowOpacity={25 / 100}
shadowOffset={{ width: 0, height: 3 }}
shadowRadius={8}
@ -286,14 +285,12 @@ const cStyles = StyleSheet.create({
alignItems: 'center',
},
content: {
left: 16,
flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
paddingTop: 16,
},
separatorStyle: { width: 16, height: 20 },
});
const WalletsCarousel = forwardRef((props, ref) => {
const carouselRef = useRef();
const [loading, setLoading] = useState(true);
const { preferredFiatCurrency, language } = useContext(BlueStorageContext);
const renderItem = useCallback(
({ item, index }) => (
@ -308,49 +305,44 @@ const WalletsCarousel = forwardRef((props, ref) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
[props.vertical, props.selectedWallet, props.handleLongPress, props.onPress, preferredFiatCurrency, language],
);
const flatListRef = useRef();
const ListHeaderComponent = () => <View style={cStyles.separatorStyle} />;
useImperativeHandle(ref, () => ({
snapToItem: item => carouselRef?.current?.snapToItem(item),
scrollToItem: ({ item }) => {
setTimeout(() => {
flatListRef?.current?.scrollToItem({ item, viewPosition: 0.3 });
}, 300);
},
scrollToIndex: index => {
setTimeout(() => {
flatListRef?.current?.scrollToIndex({ index, viewPosition: 0.3 });
}, 300);
},
}));
const { width } = useWindowDimensions();
const sliderWidth = width * 1;
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
const sliderHeight = 190;
const onLayout = () => setLoading(false);
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
return (
<>
{loading && (
<View
style={[
cStyles.loading,
{
paddingVertical: sliderHeight / 2,
paddingHorizontal: sliderWidth / 2,
},
]}
>
<ActivityIndicator />
</View>
)}
<Carousel
ref={carouselRef}
renderItem={renderItem}
sliderWidth={sliderWidth}
sliderHeight={sliderHeight}
itemWidth={itemWidth}
inactiveSlideScale={1}
inactiveSlideOpacity={I18nManager.isRTL ? 1.0 : 0.7}
activeSlideAlignment="start"
initialNumToRender={10}
inverted={I18nManager.isRTL && Platform.OS === 'android'}
onLayout={onLayout}
contentContainerCustomStyle={cStyles.content}
{...props}
/>
</>
<FlatList
ref={flatListRef}
renderItem={renderItem}
keyExtractor={(_, index) => index.toString()}
showsVerticalScrollIndicator={false}
pagingEnabled
disableIntervalMomentum={isHandset}
snapToInterval={itemWidth} // Adjust to your content width
decelerationRate="fast"
contentContainerStyle={cStyles.content}
directionalLockEnabled
showsHorizontalScrollIndicator={false}
initialNumToRender={10}
ListHeaderComponent={ListHeaderComponent}
style={props.vertical ? {} : { height: sliderHeight + 9 }}
{...props}
/>
);
});

View file

@ -1,6 +1,6 @@
import React, { useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useTheme } from '@react-navigation/native';
import { useNavigation, useTheme } from '@react-navigation/native';
import { ListItem } from 'react-native-elements';
import PropTypes from 'prop-types';
import { AddressTypeBadge } from './AddressTypeBadge';
@ -9,11 +9,13 @@ import TooltipMenu from '../TooltipMenu';
import Clipboard from '@react-native-clipboard/clipboard';
import Share from 'react-native-share';
const AddressItem = ({ item, balanceUnit, onPress }) => {
const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }) => {
const { colors } = useTheme();
const tooltip = useRef();
const listItem = useRef();
const hasTransactions = item.transactions > 0;
const stylesHook = StyleSheet.create({
container: {
borderBottomColor: colors.lightBorder,
@ -28,8 +30,33 @@ const AddressItem = ({ item, balanceUnit, onPress }) => {
balance: {
color: colors.alternativeTextColor,
},
address: {
color: hasTransactions ? colors.darkGray : colors.buttonTextColor,
},
});
const { navigate } = useNavigation();
const navigateToReceive = () => {
navigate('ReceiveDetailsRoot', {
screen: 'ReceiveDetails',
params: {
walletID,
address: item.address,
},
});
};
const navigateToSignVerify = () => {
navigate('SignVerifyRoot', {
screen: 'SignVerify',
params: {
walletID,
address: item.address,
},
});
};
const showToolTipMenu = () => {
tooltip.current.showMenu();
};
@ -44,30 +71,40 @@ const AddressItem = ({ item, balanceUnit, onPress }) => {
Share.open({ message: item.address }).catch(error => console.log(error));
};
const getAvailableActions = () => {
const actions = [
{
id: 'copyToClipboard',
text: loc.transactions.details_copy,
onPress: handleCopyPress,
},
{
id: 'share',
text: loc.receive.details_share,
onPress: handleSharePress,
},
];
if (allowSignVerifyMessage) {
actions.push({
id: 'signVerify',
text: loc.addresses.sign_title,
onPress: navigateToSignVerify,
});
}
return actions;
};
const render = () => {
return (
<View>
<TooltipMenu
ref={tooltip}
anchorRef={listItem}
actions={[
{
id: 'copyToClipboard',
text: loc.transactions.details_copy,
onPress: handleCopyPress,
},
{
id: 'share',
text: loc.receive.details_share,
onPress: handleSharePress,
},
]}
/>
<TooltipMenu ref={tooltip} anchorRef={listItem} actions={getAvailableActions()} />
<ListItem
ref={listItem}
key={`${item.key}`}
button
onPress={onPress}
onPress={navigateToReceive}
containerStyle={stylesHook.container}
onLongPress={showToolTipMenu}
>
@ -76,9 +113,16 @@ const AddressItem = ({ item, balanceUnit, onPress }) => {
<Text style={[styles.index, stylesHook.index]}>{item.index + 1}</Text>{' '}
<Text style={[stylesHook.address, styles.address]}>{item.address}</Text>
</ListItem.Title>
<ListItem.Subtitle style={[stylesHook.list, styles.balance, stylesHook.balance]}>{balance}</ListItem.Subtitle>
<View style={styles.subtitle}>
<Text style={[stylesHook.list, styles.balance, stylesHook.balance]}>{balance}</Text>
</View>
</ListItem.Content>
<AddressTypeBadge isInternal={item.isInternal} />
<View style={styles.labels}>
<AddressTypeBadge isInternal={item.isInternal} hasTransactions={hasTransactions} />
<Text style={[stylesHook.list, styles.balance, stylesHook.balance]}>
{loc.addresses.transactions}: {item.transactions}
</Text>
</View>
</ListItem>
</View>
);
@ -89,7 +133,7 @@ const AddressItem = ({ item, balanceUnit, onPress }) => {
const styles = StyleSheet.create({
address: {
fontWeight: '600',
fontWeight: 'bold',
marginHorizontal: 40,
},
index: {
@ -99,6 +143,12 @@ const styles = StyleSheet.create({
marginTop: 8,
marginLeft: 14,
},
subtitle: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
width: '100%',
},
});
AddressItem.propTypes = {

View file

@ -9,27 +9,46 @@ const styles = StyleSheet.create({
paddingVertical: 4,
paddingHorizontal: 10,
borderRadius: 20,
alignSelf: 'flex-end',
},
badgeText: {
fontSize: 12,
textAlign: 'center',
},
});
const AddressTypeBadge = ({ isInternal }) => {
const AddressTypeBadge = ({ isInternal, hasTransactions }) => {
const { colors } = useTheme();
const stylesHook = StyleSheet.create({
changeBadge: { backgroundColor: colors.changeBackground },
receiveBadge: { backgroundColor: colors.receiveBackground },
usedBadge: { backgroundColor: colors.buttonDisabledBackgroundColor },
changeText: { color: colors.changeText },
receiveText: { color: colors.receiveText },
usedText: { color: colors.alternativeTextColor },
});
const badgeLabel = isInternal ? loc.addresses.type_change : loc.addresses.type_receive;
// eslint-disable-next-line prettier/prettier
const badgeLabel = hasTransactions
? loc.addresses.type_used
: isInternal
? loc.addresses.type_change
: loc.addresses.type_receive;
const badgeStyle = isInternal ? stylesHook.changeBadge : stylesHook.receiveBadge;
// eslint-disable-next-line prettier/prettier
const badgeStyle = hasTransactions
? stylesHook.usedBadge
: isInternal
? stylesHook.changeBadge
: stylesHook.receiveBadge;
const textStyle = isInternal ? stylesHook.changeText : stylesHook.receiveText;
// eslint-disable-next-line prettier/prettier
const textStyle = hasTransactions
? stylesHook.usedText
: isInternal
? stylesHook.changeText
: stylesHook.receiveText;
return (
<View style={[styles.container, badgeStyle]}>
@ -40,6 +59,7 @@ const AddressTypeBadge = ({ isInternal }) => {
AddressTypeBadge.propTypes = {
isInternal: PropTypes.bool,
hasTransactions: PropTypes.bool,
};
export { AddressTypeBadge };

View file

@ -0,0 +1,94 @@
import { useTheme } from '@react-navigation/native';
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import loc from '../../loc';
export const TABS = {
EXTERNAL: 'receive',
INTERNAL: 'change',
};
const AddressTypeTabs = ({ currentTab, setCurrentTab }) => {
const { colors } = useTheme();
const stylesHook = StyleSheet.create({
activeTab: {
backgroundColor: colors.modal,
},
activeText: {
fontWeight: 'bold',
color: colors.foregroundColor,
},
inactiveTab: {
fontWeight: 'normal',
color: colors.foregroundColor,
},
backTabs: {
backgroundColor: colors.buttonDisabledBackgroundColor,
},
});
const tabs = Object.entries(TABS).map(([key, value]) => {
return {
key,
value,
name: loc.addresses[`type_${value}`],
};
});
const changeToTab = tabKey => {
if (tabKey in TABS) {
setCurrentTab(TABS[tabKey]);
}
};
const render = () => {
const tabsButtons = tabs.map(tab => {
const isActive = tab.value === currentTab;
const tabStyle = isActive ? stylesHook.activeTab : stylesHook.inactiveTab;
const textStyle = isActive ? stylesHook.activeText : stylesHook.inactiveTab;
return (
<View key={tab.key} onPress={() => changeToTab(tab.key)} style={[styles.tab, tabStyle]}>
<Text onPress={() => changeToTab(tab.key)} style={textStyle}>{tab.name}</Text>
</View>
);
});
return (
<View style={styles.container}>
<View style={[stylesHook.backTabs, styles.backTabs]}>
<View style={styles.tabs}>{tabsButtons}</View>
</View>
</View>
);
};
return render();
};
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
},
backTabs: {
padding: 4,
marginVertical: 8,
borderRadius: 8,
},
tabs: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
},
tab: {
borderRadius: 6,
paddingVertical: 8,
paddingHorizontal: 16,
},
});
export { AddressTypeTabs };

View file

@ -21,7 +21,7 @@ const navigationStyle = ({ closeButton = false, closeButtonFunc, ...opts }, form
navigation.goBack(null);
};
headerRight = () => (
<TouchableOpacity style={styles.button} onPress={handleClose} testID="NavigationCloseButton">
<TouchableOpacity accessibilityRole="button" style={styles.button} onPress={handleClose} testID="NavigationCloseButton">
<Image source={theme.closeImage} />
</TouchableOpacity>
);
@ -71,6 +71,7 @@ export const navigationStyleTx = (opts, formatter) => {
headerTintColor: theme.colors.foregroundColor,
headerLeft: () => (
<TouchableOpacity
accessibilityRole="button"
style={styles.button}
onPress={() => {
Keyboard.dismiss();

View file

@ -0,0 +1,44 @@
A Bitcoin wallet that allows you to store, send Bitcoin, receive Bitcoin and buy Bitcoin with focus on security and simplicity.
On BlueWallet, a bitcoin wallet you own you private keys. A Bitcoin wallet made by Bitcoin users for the community.
You can instantly transact with anyone in the world and transform the financial system right from your pocket.
Create for free unlimited number of bitcoin wallets or import your existing one on your Android device. It's simple and fast.
_____
Here's what you get:
1 - Security by design
• Open Source
MIT licensed, you can build it and run it on your own! Made with ReactNative
• Plausible deniability
Password which decrypts fake bitcoin wallets if you are forced to disclose your access
• Full encryption
On top of the iOS multi-layer encryption, we encrypt everything with added passwords
• SegWit & HD wallets
SegWit supported and HD wallets enable
2 - Focused on your experience
• Be in control
Private keys never leave your device.You control your private keys
• Flexible fees
Starting from 1 Satoshi. Defined by you, the user
• Replace-By-Fee
(RBF) Speed-up your transactions by increasing the fee (BIP125)
• Watch-only wallets
Watch-only wallets allow you to keep an eye on your cold storage without touching the hardware.
• Lightning Network
Lightning wallet with zero-configuration. Unfairly cheap and fast transactions with the best Bitcoin user experience.
• Buy Bitcoin
Enter in the open financial revolution with the ability to buy Bitcoin directly in your wallet.
• Local Trader
A p2p Bitcoin Trading platform, that allows you to buy and sell bitcoin directly to other users without 3rd parties.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -0,0 +1 @@
Thin Bitcoin Wallet Built with React Native and Electrum

View file

@ -0,0 +1 @@
BlueWallet Bitcoin Wallet

File diff suppressed because it is too large Load diff

View file

@ -65,10 +65,8 @@
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "8"
notificationPayloadFile = "BlueWalletWatch Extension/PushNotificationPayload.apns">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/BlueWallet">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
@ -76,7 +74,7 @@
BlueprintName = "BlueWalletWatch"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</RemoteRunnable>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@ -86,10 +84,8 @@
debugDocumentVersioning = "YES"
launchAutomaticallySubstyle = "8"
notificationPayloadFile = "BlueWalletWatch Extension/PushNotificationPayload.apns">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/BlueWallet">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
@ -97,16 +93,7 @@
BlueprintName = "BlueWalletWatch"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
BuildableName = "BlueWalletWatch.app"
BlueprintName = "BlueWalletWatch"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View file

@ -64,10 +64,8 @@
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
notificationPayloadFile = "BlueWalletWatch Extension/PushNotificationPayload.apns">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/BlueWallet">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
@ -75,7 +73,7 @@
BlueprintName = "BlueWalletWatch"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</RemoteRunnable>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@ -83,10 +81,8 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/BlueWallet">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
@ -94,16 +90,7 @@
BlueprintName = "BlueWalletWatch"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
BuildableName = "BlueWalletWatch.app"
BlueprintName = "BlueWalletWatch"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View file

@ -167,10 +167,6 @@
<string>In order to import an image for scanning, we need your permission to access your photo library.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>NSUserActivityTypes</key>
<array>
<string>ConfigurationIntent</string>
</array>
<key>UIAppFonts</key>
<array>
<string>AntDesign.ttf</string>

View file

@ -39,12 +39,14 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
let entry: CLKComplicationTimelineEntry
let date: Date
let valueLabel: String
let valueSmallLabel: String
let currencySymbol: String
let timeLabel: String
if let price = marketData?.formattedRateForComplication, let marketDatadata = marketData?.date, let lastUpdated = marketData?.formattedDate {
if let price = marketData?.formattedRateForComplication, let priceAbbreviated = marketData?.formattedRateForSmallComplication, let marketDatadata = marketData?.date, let lastUpdated = marketData?.formattedDate {
date = marketDatadata
valueLabel = price
timeLabel = lastUpdated
valueSmallLabel = priceAbbreviated
if let preferredFiatCurrency = UserDefaults.standard.string(forKey: "preferredFiatCurrency"), let preferredFiatUnit = fiatUnit(currency: preferredFiatCurrency) {
currencySymbol = preferredFiatUnit.symbol
} else {
@ -53,24 +55,25 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
} else {
valueLabel = "--"
timeLabel = "--"
valueSmallLabel = "--"
currencySymbol = fiatUnit(currency: "USD")!.symbol
date = Date()
}
let line1Text = CLKSimpleTextProvider(text:valueLabel)
let line2Text = CLKSimpleTextProvider(text:currencySymbol)
let line1SmallText = CLKSimpleTextProvider(text: valueSmallLabel)
switch complication.family {
case .circularSmall:
let template = CLKComplicationTemplateCircularSmallStackText()
template.line1TextProvider = line1Text
template.line1TextProvider = line1SmallText
template.line2TextProvider = line2Text
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
handler(entry)
case .utilitarianSmallFlat:
let template = CLKComplicationTemplateUtilitarianSmallFlat()
if #available(watchOSApplicationExtension 6.0, *) {
template.textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
template.textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueSmallLabel)
} else {
handler(nil)
}
@ -85,7 +88,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
case .graphicCircular:
if #available(watchOSApplicationExtension 6.0, *) {
let template = CLKComplicationTemplateGraphicCircularStackText()
template.line1TextProvider = line1Text
template.line1TextProvider = line1SmallText
template.line2TextProvider = line2Text
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
handler(entry)
@ -94,14 +97,14 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
}
case .modularSmall:
let template = CLKComplicationTemplateModularSmallStackText()
template.line1TextProvider = line1Text
template.line1TextProvider = line1SmallText
template.line2TextProvider = line2Text
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
handler(entry)
case .graphicCorner:
let template = CLKComplicationTemplateGraphicCornerStackText()
if #available(watchOSApplicationExtension 6.0, *) {
template.outerTextProvider = CLKTextProvider(format: "%@", valueLabel)
template.outerTextProvider = CLKTextProvider(format: "%@", valueSmallLabel)
template.innerTextProvider = CLKTextProvider(format: "%@", currencySymbol)
} else {
handler(nil)
@ -111,7 +114,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
case .graphicBezel:
let template = CLKComplicationTemplateGraphicBezelCircularText()
if #available(watchOSApplicationExtension 6.0, *) {
template.textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
template.textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueSmallLabel)
let imageProvider = CLKFullColorImageProvider(fullColorImage: UIImage(named: "Complication/Graphic Bezel")!)
let circularTemplate = CLKComplicationTemplateGraphicCircularImage()
circularTemplate.imageProvider = imageProvider
@ -135,7 +138,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
if #available(watchOSApplicationExtension 6.0, *) {
template.headerTextProvider = CLKTextProvider(format: "Bitcoin Price")
template.body1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
template.body2TextProvider = CLKTextProvider(format: "%@", timeLabel)
template.body2TextProvider = CLKTextProvider(format: "at %@", timeLabel)
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
handler(entry)
} else {
@ -145,7 +148,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
let template = CLKComplicationTemplateExtraLargeStackText()
if #available(watchOSApplicationExtension 6.0, *) {
template.line1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
template.line2TextProvider = CLKTextProvider(format: "%@", timeLabel)
template.line2TextProvider = CLKTextProvider(format: "at %@", timeLabel)
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
handler(entry)
} else {
@ -156,7 +159,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
if #available(watchOSApplicationExtension 6.0, *) {
template.headerTextProvider = CLKTextProvider(format: "Bitcoin Price")
template.body1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
template.body2TextProvider = CLKTextProvider(format: "%@", timeLabel)
template.body2TextProvider = CLKTextProvider(format: "at %@", timeLabel)
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
handler(entry)
} else {
@ -166,7 +169,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
if #available(watchOSApplicationExtension 7.0, *) {
let template = CLKComplicationTemplateGraphicExtraLargeCircularStackText()
template.line1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
template.line1TextProvider = CLKTextProvider(format: "%@", timeLabel)
template.line1TextProvider = CLKTextProvider(format: "at %@", timeLabel)
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
handler(entry)
} else {

View file

@ -2,19 +2,23 @@ platform :ios, '11.1'
workspace 'BlueWallet'
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
target 'BlueWallet' do
config = use_native_modules!
use_react_native!(:path => config["reactNativePath"])
use_react_native!(
:path => config[:reactNativePath],
# to enable hermes on iOS, change `false` to `true` and then install pods
:hermes_enabled => false
)
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable these next few lines.
use_flipper!({ 'Flipper-Folly' => '2.5.3', 'Flipper' => '0.87.0', 'Flipper-RSocket' => '1.3.1' })
use_flipper!()
post_install do |installer|
flipper_post_install(installer)
react_native_post_install(installer)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0'
@ -30,10 +34,6 @@ target 'BlueWallet' do
end
target 'MarketWidgetExtension' do
pod 'SwiftSocket', :git => 'https://github.com/swiftsocket/SwiftSocket.git', :branch => 'master'
end
target 'WalletInformationAndMarketWidgetExtension' do
target 'WidgetsExtension' do
pod 'SwiftSocket', :git => 'https://github.com/swiftsocket/SwiftSocket.git', :branch => 'master'
end

View file

@ -4,15 +4,15 @@ PODS:
- React
- CocoaAsyncSocket (7.6.5)
- DoubleConversion (1.1.6)
- FBLazyVector (0.63.4)
- FBReactNativeSpec (0.63.4):
- Folly (= 2020.01.13.00)
- RCTRequired (= 0.63.4)
- RCTTypeSafety (= 0.63.4)
- React-Core (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- Flipper (0.87.0):
- FBLazyVector (0.64.2)
- FBReactNativeSpec (0.64.2):
- RCT-Folly (= 2020.01.13.00)
- RCTRequired (= 0.64.2)
- RCTTypeSafety (= 0.64.2)
- React-Core (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- Flipper (0.75.1):
- Flipper-Folly (~> 2.5)
- Flipper-RSocket (~> 1.3)
- Flipper-DoubleConversion (1.1.7)
@ -26,58 +26,38 @@ PODS:
- Flipper-PeerTalk (0.0.4)
- Flipper-RSocket (1.3.1):
- Flipper-Folly (~> 2.5)
- FlipperKit (0.87.0):
- FlipperKit/Core (= 0.87.0)
- FlipperKit/Core (0.87.0):
- Flipper (~> 0.87.0)
- FlipperKit (0.75.1):
- FlipperKit/Core (= 0.75.1)
- FlipperKit/Core (0.75.1):
- Flipper (~> 0.75.1)
- FlipperKit/CppBridge
- FlipperKit/FBCxxFollyDynamicConvert
- FlipperKit/FBDefines
- FlipperKit/FKPortForwarding
- FlipperKit/CppBridge (0.87.0):
- Flipper (~> 0.87.0)
- FlipperKit/FBCxxFollyDynamicConvert (0.87.0):
- FlipperKit/CppBridge (0.75.1):
- Flipper (~> 0.75.1)
- FlipperKit/FBCxxFollyDynamicConvert (0.75.1):
- Flipper-Folly (~> 2.5)
- FlipperKit/FBDefines (0.87.0)
- FlipperKit/FKPortForwarding (0.87.0):
- FlipperKit/FBDefines (0.75.1)
- FlipperKit/FKPortForwarding (0.75.1):
- CocoaAsyncSocket (~> 7.6)
- Flipper-PeerTalk (~> 0.0.4)
- FlipperKit/FlipperKitHighlightOverlay (0.87.0)
- FlipperKit/FlipperKitLayoutHelpers (0.87.0):
- FlipperKit/FlipperKitHighlightOverlay (0.75.1)
- FlipperKit/FlipperKitLayoutPlugin (0.75.1):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutTextSearchable
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.87.0):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutHelpers
- YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutPlugin (0.87.0):
- FlipperKit/FlipperKitLayoutTextSearchable (0.75.1)
- FlipperKit/FlipperKitNetworkPlugin (0.75.1):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutHelpers
- FlipperKit/FlipperKitLayoutIOSDescriptors
- FlipperKit/FlipperKitLayoutTextSearchable
- YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutTextSearchable (0.87.0)
- FlipperKit/FlipperKitNetworkPlugin (0.87.0):
- FlipperKit/FlipperKitReactPlugin (0.75.1):
- FlipperKit/Core
- FlipperKit/FlipperKitReactPlugin (0.87.0):
- FlipperKit/FlipperKitUserDefaultsPlugin (0.75.1):
- FlipperKit/Core
- FlipperKit/FlipperKitUserDefaultsPlugin (0.87.0):
- FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.87.0):
- FlipperKit/SKIOSNetworkPlugin (0.75.1):
- FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin
- Folly (2020.01.13.00):
- boost-for-react-native
- DoubleConversion
- Folly/Default (= 2020.01.13.00)
- glog
- Folly/Default (2020.01.13.00):
- boost-for-react-native
- DoubleConversion
- glog
- GCDWebServer (3.5.4):
- GCDWebServer/Core (= 3.5.4)
- GCDWebServer/Core (3.5.4)
@ -90,183 +70,210 @@ PODS:
- OpenSSL-Universal (1.1.180)
- PasscodeAuth (1.0.0):
- React
- RCTRequired (0.63.4)
- RCTTypeSafety (0.63.4):
- FBLazyVector (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTRequired (= 0.63.4)
- React-Core (= 0.63.4)
- React (0.63.4):
- React-Core (= 0.63.4)
- React-Core/DevSupport (= 0.63.4)
- React-Core/RCTWebSocket (= 0.63.4)
- React-RCTActionSheet (= 0.63.4)
- React-RCTAnimation (= 0.63.4)
- React-RCTBlob (= 0.63.4)
- React-RCTImage (= 0.63.4)
- React-RCTLinking (= 0.63.4)
- React-RCTNetwork (= 0.63.4)
- React-RCTSettings (= 0.63.4)
- React-RCTText (= 0.63.4)
- React-RCTVibration (= 0.63.4)
- React-callinvoker (0.63.4)
- React-Core (0.63.4):
- Folly (= 2020.01.13.00)
- RCT-Folly (2020.01.13.00):
- boost-for-react-native
- DoubleConversion
- glog
- React-Core/Default (= 0.63.4)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- RCT-Folly/Default (= 2020.01.13.00)
- RCT-Folly/Default (2020.01.13.00):
- boost-for-react-native
- DoubleConversion
- glog
- RCTRequired (0.64.2)
- RCTTypeSafety (0.64.2):
- FBLazyVector (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTRequired (= 0.64.2)
- React-Core (= 0.64.2)
- React (0.64.2):
- React-Core (= 0.64.2)
- React-Core/DevSupport (= 0.64.2)
- React-Core/RCTWebSocket (= 0.64.2)
- React-RCTActionSheet (= 0.64.2)
- React-RCTAnimation (= 0.64.2)
- React-RCTBlob (= 0.64.2)
- React-RCTImage (= 0.64.2)
- React-RCTLinking (= 0.64.2)
- React-RCTNetwork (= 0.64.2)
- React-RCTSettings (= 0.64.2)
- React-RCTText (= 0.64.2)
- React-RCTVibration (= 0.64.2)
- React-callinvoker (0.64.2)
- React-Core (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default (= 0.64.2)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/CoreModulesHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/CoreModulesHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/Default (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/Default (0.64.2):
- glog
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- RCT-Folly (= 2020.01.13.00)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/DevSupport (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/DevSupport (0.64.2):
- glog
- React-Core/Default (= 0.63.4)
- React-Core/RCTWebSocket (= 0.63.4)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-jsinspector (= 0.63.4)
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default (= 0.64.2)
- React-Core/RCTWebSocket (= 0.64.2)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-jsinspector (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTActionSheetHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/RCTActionSheetHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTAnimationHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/RCTAnimationHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTBlobHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/RCTBlobHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTImageHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/RCTImageHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTLinkingHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/RCTLinkingHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTNetworkHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/RCTNetworkHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTSettingsHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/RCTSettingsHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTTextHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/RCTTextHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTVibrationHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/RCTVibrationHeaders (0.64.2):
- glog
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-Core/RCTWebSocket (0.63.4):
- Folly (= 2020.01.13.00)
- React-Core/RCTWebSocket (0.64.2):
- glog
- React-Core/Default (= 0.63.4)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- RCT-Folly (= 2020.01.13.00)
- React-Core/Default (= 0.64.2)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsiexecutor (= 0.64.2)
- React-perflogger (= 0.64.2)
- Yoga
- React-CoreModules (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.63.4)
- React-Core/CoreModulesHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- React-RCTImage (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-cxxreact (0.63.4):
- React-CoreModules (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.64.2)
- React-Core/CoreModulesHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- React-RCTImage (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-cxxreact (0.64.2):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2020.01.13.00)
- glog
- React-callinvoker (= 0.63.4)
- React-jsinspector (= 0.63.4)
- React-jsi (0.63.4):
- RCT-Folly (= 2020.01.13.00)
- React-callinvoker (= 0.64.2)
- React-jsi (= 0.64.2)
- React-jsinspector (= 0.64.2)
- React-perflogger (= 0.64.2)
- React-runtimeexecutor (= 0.64.2)
- React-jsi (0.64.2):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2020.01.13.00)
- glog
- React-jsi/Default (= 0.63.4)
- React-jsi/Default (0.63.4):
- RCT-Folly (= 2020.01.13.00)
- React-jsi/Default (= 0.64.2)
- React-jsi/Default (0.64.2):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2020.01.13.00)
- glog
- React-jsiexecutor (0.63.4):
- RCT-Folly (= 2020.01.13.00)
- React-jsiexecutor (0.64.2):
- DoubleConversion
- Folly (= 2020.01.13.00)
- glog
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsinspector (0.63.4)
- RCT-Folly (= 2020.01.13.00)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-perflogger (= 0.64.2)
- React-jsinspector (0.64.2)
- react-native-blue-crypto (1.0.0):
- React
- react-native-blur (0.8.0):
- React
- react-native-camera (3.43.5):
- react-native-camera (3.44.1):
- React-Core
- react-native-camera/RCT (= 3.43.5)
- react-native-camera/RN (= 3.43.5)
- react-native-camera/RCT (3.43.5):
- react-native-camera/RCT (= 3.44.1)
- react-native-camera/RN (= 3.44.1)
- react-native-camera/RCT (3.44.1):
- React-Core
- react-native-camera/RN (3.43.5):
- react-native-camera/RN (3.44.1):
- React-Core
- react-native-document-picker (3.5.4):
- React
@ -274,11 +281,9 @@ PODS:
- React
- react-native-idle-timer (2.1.6):
- React-Core
- react-native-image-picker (3.3.2):
- react-native-image-picker (3.8.1):
- React-Core
- react-native-is-catalyst (1.0.0):
- React
- react-native-randombytes (3.6.0):
- react-native-randombytes (3.6.1):
- React-Core
- react-native-safe-area-context (3.2.0):
- React-Core
@ -287,111 +292,115 @@ PODS:
- React
- react-native-tor (0.1.7):
- React
- react-native-webview (11.4.0):
- react-native-webview (11.6.4):
- React-Core
- react-native-widget-center (0.0.4):
- React
- React-RCTActionSheet (0.63.4):
- React-Core/RCTActionSheetHeaders (= 0.63.4)
- React-RCTAnimation (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.63.4)
- React-Core/RCTAnimationHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTBlob (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- React-Core/RCTBlobHeaders (= 0.63.4)
- React-Core/RCTWebSocket (= 0.63.4)
- React-jsi (= 0.63.4)
- React-RCTNetwork (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTImage (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.63.4)
- React-Core/RCTImageHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- React-RCTNetwork (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTLinking (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- React-Core/RCTLinkingHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTNetwork (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.63.4)
- React-Core/RCTNetworkHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTSettings (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.63.4)
- React-Core/RCTSettingsHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTText (0.63.4):
- React-Core/RCTTextHeaders (= 0.63.4)
- React-RCTVibration (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- React-Core/RCTVibrationHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- ReactCommon/turbomodule/core (0.63.4):
- React-perflogger (0.64.2)
- React-RCTActionSheet (0.64.2):
- React-Core/RCTActionSheetHeaders (= 0.64.2)
- React-RCTAnimation (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.64.2)
- React-Core/RCTAnimationHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTBlob (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- React-Core/RCTBlobHeaders (= 0.64.2)
- React-Core/RCTWebSocket (= 0.64.2)
- React-jsi (= 0.64.2)
- React-RCTNetwork (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTImage (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.64.2)
- React-Core/RCTImageHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- React-RCTNetwork (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTLinking (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- React-Core/RCTLinkingHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTNetwork (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.64.2)
- React-Core/RCTNetworkHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTSettings (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.64.2)
- React-Core/RCTSettingsHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-RCTText (0.64.2):
- React-Core/RCTTextHeaders (= 0.64.2)
- React-RCTVibration (0.64.2):
- FBReactNativeSpec (= 0.64.2)
- RCT-Folly (= 2020.01.13.00)
- React-Core/RCTVibrationHeaders (= 0.64.2)
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (= 0.64.2)
- React-runtimeexecutor (0.64.2):
- React-jsi (= 0.64.2)
- ReactCommon/turbomodule/core (0.64.2):
- DoubleConversion
- Folly (= 2020.01.13.00)
- glog
- React-callinvoker (= 0.63.4)
- React-Core (= 0.63.4)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- RealmJS (10.2.0):
- RCT-Folly (= 2020.01.13.00)
- React-callinvoker (= 0.64.2)
- React-Core (= 0.64.2)
- React-cxxreact (= 0.64.2)
- React-jsi (= 0.64.2)
- React-perflogger (= 0.64.2)
- RealmJS (10.4.2):
- GCDWebServer
- React
- RemobileReactNativeQrcodeLocalImage (1.0.4):
- React
- RNCAsyncStorage (1.15.2):
- RNCAsyncStorage (1.15.5):
- React-Core
- RNCClipboard (1.7.0):
- React-Core
- RNCMaskedView (0.1.10):
- RNCMaskedView (0.1.11):
- React
- RNCPushNotificationIOS (1.8.0):
- React-Core
- RNDefaultPreference (1.4.3):
- React
- RNDeviceInfo (8.1.2):
- RNDeviceInfo (8.1.3):
- React-Core
- RNFS (2.16.6):
- RNFS (2.18.0):
- React
- RNGestureHandler (1.10.3):
- React-Core
- RNHandoff (0.0.3):
- React
- RNInAppBrowser (3.5.1):
- RNKeychain (7.0.0):
- React-Core
- RNLocalize (2.0.2):
- RNLocalize (2.1.1):
- React-Core
- RNPrivacySnapshot (1.0.0):
- React
- RNQuickAction (0.3.13):
- React
- RNRate (1.2.4):
- React
- RNRate (1.2.6):
- React-Core
- RNReactNativeHapticFeedback (1.11.0):
- React-Core
- RNReanimated (2.1.0):
- RNReanimated (2.2.0):
- DoubleConversion
- FBLazyVector
- FBReactNativeSpec
- Folly
- glog
- RCT-Folly
- RCTRequired
- RCTTypeSafety
- React
@ -415,14 +424,15 @@ PODS:
- React-RCTVibration
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (2.18.1):
- RNScreens (3.4.0):
- React-Core
- React-RCTImage
- RNSecureKeyStore (1.0.0):
- React
- RNSentry (2.5.0-beta.1):
- RNSentry (2.6.0):
- React-Core
- Sentry (= 7.0.0)
- RNShare (5.2.2):
- RNShare (6.2.1):
- React-Core
- RNSVG (12.1.1):
- React
@ -444,31 +454,31 @@ DEPENDENCIES:
- BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`)
- Flipper (= 0.87.0)
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
- Flipper (~> 0.75.1)
- Flipper-DoubleConversion (= 1.1.7)
- Flipper-Folly (= 2.5.3)
- Flipper-Folly (~> 2.5.3)
- Flipper-Glog (= 0.3.6)
- Flipper-PeerTalk (~> 0.0.4)
- Flipper-RSocket (= 1.3.1)
- FlipperKit (= 0.87.0)
- FlipperKit/Core (= 0.87.0)
- FlipperKit/CppBridge (= 0.87.0)
- FlipperKit/FBCxxFollyDynamicConvert (= 0.87.0)
- FlipperKit/FBDefines (= 0.87.0)
- FlipperKit/FKPortForwarding (= 0.87.0)
- FlipperKit/FlipperKitHighlightOverlay (= 0.87.0)
- FlipperKit/FlipperKitLayoutPlugin (= 0.87.0)
- FlipperKit/FlipperKitLayoutTextSearchable (= 0.87.0)
- FlipperKit/FlipperKitNetworkPlugin (= 0.87.0)
- FlipperKit/FlipperKitReactPlugin (= 0.87.0)
- FlipperKit/FlipperKitUserDefaultsPlugin (= 0.87.0)
- FlipperKit/SKIOSNetworkPlugin (= 0.87.0)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- Flipper-RSocket (~> 1.3)
- FlipperKit (~> 0.75.1)
- FlipperKit/Core (~> 0.75.1)
- FlipperKit/CppBridge (~> 0.75.1)
- FlipperKit/FBCxxFollyDynamicConvert (~> 0.75.1)
- FlipperKit/FBDefines (~> 0.75.1)
- FlipperKit/FKPortForwarding (~> 0.75.1)
- FlipperKit/FlipperKitHighlightOverlay (~> 0.75.1)
- FlipperKit/FlipperKitLayoutPlugin (~> 0.75.1)
- FlipperKit/FlipperKitLayoutTextSearchable (~> 0.75.1)
- FlipperKit/FlipperKitNetworkPlugin (~> 0.75.1)
- FlipperKit/FlipperKitReactPlugin (~> 0.75.1)
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.75.1)
- FlipperKit/SKIOSNetworkPlugin (~> 0.75.1)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- lottie-ios (from `../node_modules/lottie-ios`)
- lottie-react-native (from `../node_modules/lottie-react-native`)
- PasscodeAuth (from `../node_modules/react-native-passcode-auth`)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`)
@ -488,13 +498,13 @@ DEPENDENCIES:
- react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`)
- react-native-idle-timer (from `../node_modules/react-native-idle-timer`)
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- react-native-is-catalyst (from `../node_modules/react-native-is-catalyst`)
- react-native-randombytes (from `../node_modules/react-native-randombytes`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-tcp-socket (from `../node_modules/react-native-tcp-socket`)
- react-native-tor (from `../node_modules/react-native-tor`)
- react-native-webview (from `../node_modules/react-native-webview`)
- react-native-widget-center (from `../node_modules/react-native-widget-center`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
- React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`)
@ -504,6 +514,7 @@ DEPENDENCIES:
- React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- RealmJS (from `../node_modules/realm`)
- "RemobileReactNativeQrcodeLocalImage (from `../node_modules/@remobile/react-native-qrcode-local-image`)"
@ -516,7 +527,7 @@ DEPENDENCIES:
- RNFS (from `../node_modules/react-native-fs`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNHandoff (from `../node_modules/react-native-handoff`)
- RNInAppBrowser (from `../node_modules/react-native-inappbrowser-reborn`)
- RNKeychain (from `../node_modules/react-native-keychain`)
- RNLocalize (from `../node_modules/react-native-localize`)
- RNPrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`)
- RNQuickAction (from `../node_modules/react-native-quick-actions`)
@ -559,9 +570,7 @@ EXTERNAL SOURCES:
FBLazyVector:
:path: "../node_modules/react-native/Libraries/FBLazyVector"
FBReactNativeSpec:
:path: "../node_modules/react-native/Libraries/FBReactNativeSpec"
Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/Folly.podspec"
:path: "../node_modules/react-native/React/FBReactNativeSpec"
glog:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
lottie-ios:
@ -570,6 +579,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/lottie-react-native"
PasscodeAuth:
:path: "../node_modules/react-native-passcode-auth"
RCT-Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
RCTRequired:
:path: "../node_modules/react-native/Libraries/RCTRequired"
RCTTypeSafety:
@ -604,8 +615,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-idle-timer"
react-native-image-picker:
:path: "../node_modules/react-native-image-picker"
react-native-is-catalyst:
:path: "../node_modules/react-native-is-catalyst"
react-native-randombytes:
:path: "../node_modules/react-native-randombytes"
react-native-safe-area-context:
@ -618,6 +627,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-webview"
react-native-widget-center:
:path: "../node_modules/react-native-widget-center"
React-perflogger:
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
React-RCTActionSheet:
:path: "../node_modules/react-native/Libraries/ActionSheetIOS"
React-RCTAnimation:
@ -636,6 +647,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/Libraries/Text"
React-RCTVibration:
:path: "../node_modules/react-native/Libraries/Vibration"
React-runtimeexecutor:
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
RealmJS:
@ -660,8 +673,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-gesture-handler"
RNHandoff:
:path: "../node_modules/react-native-handoff"
RNInAppBrowser:
:path: "../node_modules/react-native-inappbrowser-reborn"
RNKeychain:
:path: "../node_modules/react-native-keychain"
RNLocalize:
:path: "../node_modules/react-native-localize"
RNPrivacySnapshot:
@ -705,89 +718,90 @@ SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: cde416483dac037923206447da6e1454df403714
FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e
FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e
Flipper: 1bd2db48dcc31e4b167b9a33ec1df01c2ded4893
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
FBReactNativeSpec: da2b9104721789106ad3943049ccf61ef6f4db39
Flipper: d3da1aa199aad94455ae725e9f3aa43f3ec17021
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
Flipper-Folly: 755929a4f851b2fb2c347d533a23f191b008554c
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: 127954abe8b162fcaf68d2134d34dc2bd7076154
FlipperKit: 651f50a42eb95c01b3e89a60996dd6aded529eeb
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
FlipperKit: 8a20b5c5fcf9436cac58551dc049867247f64b00
GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
lottie-ios: 3a3758ef5a008e762faec9c9d50a39842f26d124
lottie-react-native: 4dff8fe8d10ddef9e7880e770080f4a56121397e
OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b
PasscodeAuth: 1cc99b13d8e4de4716d7e2b4069af2f1a9de30b2
RCTRequired: 082f10cd3f905d6c124597fd1c14f6f2655ff65e
RCTTypeSafety: 8c9c544ecbf20337d069e4ae7fd9a377aadf504b
React: b0a957a2c44da4113b0c4c9853d8387f8e64e615
React-callinvoker: c3f44dd3cb195b6aa46621fff95ded79d59043fe
React-Core: d3b2a1ac9a2c13c3bcde712d9281fc1c8a5b315b
React-CoreModules: 0581ff36cb797da0943d424f69e7098e43e9be60
React-cxxreact: c1480d4fda5720086c90df537ee7d285d4c57ac3
React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31
React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949
React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a
RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c
RCTRequired: 6d3e854f0e7260a648badd0d44fc364bc9da9728
RCTTypeSafety: c1f31d19349c6b53085766359caac425926fafaa
React: bda6b6d7ae912de97d7a61aa5c160db24aa2ad69
React-callinvoker: 9840ea7e8e88ed73d438edb725574820b29b5baa
React-Core: b5e385da7ce5f16a220fc60fd0749eae2c6120f0
React-CoreModules: 17071a4e2c5239b01585f4aa8070141168ab298f
React-cxxreact: 9be7b6340ed9f7c53e53deca7779f07cd66525ba
React-jsi: 67747b9722f6dab2ffe15b011bcf6b3f2c3f1427
React-jsiexecutor: 80c46bd381fd06e418e0d4f53672dc1d1945c4c3
React-jsinspector: cc614ec18a9ca96fd275100c16d74d62ee11f0ae
react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-camera: 8a79f048c146e08e416c542bcf57984cbc7ed404
react-native-camera: 6e6d25f6318980dd2837747760628b4442aac01a
react-native-document-picker: c5752781fbc0c126c627c1549b037c139444a4cf
react-native-fingerprint-scanner: c68136ca57e3704d7bdf5faa554ea535ce15b1d0
react-native-idle-timer: 97b8283237d45146a7a5c25bdebe9e1e85f3687b
react-native-image-picker: 5b2f1ea1f9230b131abbfb8a4aa1eb209aba9ed9
react-native-is-catalyst: 52ee70e0123c82419dd4ce47dc4cc94b22467512
react-native-randombytes: 45ae693012df24c9a07a5e578b3b567c01468581
react-native-image-picker: 5a7dd93d4f9dd0d910da5dde3707fd96f70d03b3
react-native-randombytes: 5fc412efe7b5c55b9002c0004d75fe5fabcaa507
react-native-safe-area-context: e471852c5ed67eea4b10c5d9d43c1cebae3b231d
react-native-tcp-socket: 96a4f104cdcc9c6621aafe92937f163d88447c5b
react-native-tor: 4f389f5719dad633542b57ea32744e954730e7ef
react-native-webview: fdd3c7c2ad173e8ca8c82729ed05f3fda2491a82
react-native-webview: 4288b81c682bca4dcc9037afb3ee118af2655c03
react-native-widget-center: 0f81d17beb163e7fb5848b06754d7d277fe7d99a
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0
React-RCTImage: c1b1f2d3f43a4a528c8946d6092384b5c880d2f0
React-RCTLinking: 35ae4ab9dc0410d1fcbdce4d7623194a27214fb2
React-RCTNetwork: 29ec2696f8d8cfff7331fac83d3e893c95ef43ae
React-RCTSettings: 60f0691bba2074ef394f95d4c2265ec284e0a46a
React-RCTText: 5c51df3f08cb9dedc6e790161195d12bac06101c
React-RCTVibration: ae4f914cfe8de7d4de95ae1ea6cc8f6315d73d9d
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
RealmJS: 5195064e9aeccf94ae3756bd9d0f2301b9074b07
React-perflogger: 25373e382fed75ce768a443822f07098a15ab737
React-RCTActionSheet: af7796ba49ffe4ca92e7277a5d992d37203f7da5
React-RCTAnimation: 6a2e76ab50c6f25b428d81b76a5a45351c4d77aa
React-RCTBlob: 02a2887023e0eed99391b6445b2e23a2a6f9226d
React-RCTImage: ce5bf8e7438f2286d9b646a05d6ab11f38b0323d
React-RCTLinking: ccd20742de14e020cb5f99d5c7e0bf0383aefbd9
React-RCTNetwork: dfb9d089ab0753e5e5f55fc4b1210858f7245647
React-RCTSettings: b14aef2d83699e48b410fb7c3ba5b66cd3291ae2
React-RCTText: 41a2e952dd9adc5caf6fb68ed46b275194d5da5f
React-RCTVibration: 24600e3b1aaa77126989bc58b6747509a1ba14f3
React-runtimeexecutor: a9904c6d0218fb9f8b19d6dd88607225927668f9
ReactCommon: 149906e01aa51142707a10665185db879898e966
RealmJS: 90f2a558fdda19ebb9fcbc0a52c7b1d1b049fb88
RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa
RNCAsyncStorage: 502fbf425b9ee2d73ca6fa4b299dce0be411b422
RNCAsyncStorage: 8324611026e8dc3706f829953aa6e3899f581589
RNCClipboard: dac13db8b1ce9b998f1cbc7ca33440113602847f
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
RNCMaskedView: f127cd9652acfa31b91dcff613e07ba18b774db6
RNCPushNotificationIOS: 5b1cf9ad2aaa107ecb92d5d2d7005ba521b2b97a
RNDefaultPreference: 21816c0a6f61a2829ccc0cef034392e9b509ee5f
RNDeviceInfo: 4f480456c7ac8c9919448375399c1a6f14479549
RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df
RNDeviceInfo: 49f6d50f861c7810fac2dd9b71cfb56cc1940e14
RNFS: 3ab21fa6c56d65566d1fb26c2228e2b6132e5e32
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
RNInAppBrowser: 3733c1aa6699983a1c9b4963e85d5e5a48ad297e
RNLocalize: 47e22ef8c36df1d572e42a87c8ae22e3fcf551dd
RNKeychain: f75b8c8b2f17d3b2aa1f25b4a0ac5b83d947ff8f
RNLocalize: b9a5d22c7e4ecb5db604861f1b1fff6013e03340
RNPrivacySnapshot: 71919dde3c6a29dd332115409c2aec564afee8f4
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNRate: 2b31dad120cd1b78e33c6034808561c386a3dddf
RNRate: e0af7e724e5fcf89578dbd22ab6395c85402ef29
RNReactNativeHapticFeedback: 653a8c126a0f5e88ce15ffe280b3ff37e1fbb285
RNReanimated: 70f662b5232dd5d19ccff581e919a54ea73df51c
RNScreens: f7ad633b2e0190b77b6a7aab7f914fad6f198d8d
RNReanimated: 9c13c86454bfd54dab7505c1a054470bfecd2563
RNScreens: 21b73c94c9117e1110a79ee0ee80c93ccefed8ce
RNSecureKeyStore: f1ad870e53806453039f650720d2845c678d89c8
RNSentry: 1868bcfe8c69b2c3b2451439a38b3ebea0a7510f
RNShare: 5cfe16bfd42cd2c4869a7692462181ac8cc15a6d
RNSentry: a2b02b326ae4fce91ce7c57d3f7dcf8df4f72269
RNShare: 5ac8f6532ca4cd80fc71caef1cfbba1854a6a045
RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f
RNVectorIcons: bc69e6a278b14842063605de32bec61f0b251a59
RNWatch: e4c5d19506c94506860032fb68aedd5991beb985
Sentry: 89d26e036063b9cb9caa59b6951dd2f8277aa13b
SwiftSocket: c8d482e867ae4d3eb4c769e9382e123c1f1f833b
ToolTipMenu: 4d89d95ddffd7539230bdbe02ee51bbde362e37e
Yoga: 4bd86afe9883422a7c4028c00e34790f560923d6
Yoga: 575c581c63e0d35c9a83f4b46d01d63abc1100ac
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: e5cd06c8e5cb8f554b34954eef0d4d4f18e7d9d2
PODFILE CHECKSUM: d6f6c7992cb83adb1c2ca00fa4d3e3a3402f4787
COCOAPODS: 1.10.1

View file

@ -1,14 +0,0 @@
<?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.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.bluewallet.bluewallet</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View file

@ -1,29 +0,0 @@
<?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>WalletInformationWidget</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>

View file

@ -9,34 +9,35 @@
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
return SimpleEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000)))
struct WalletInformationWidgetProvider: TimelineProvider {
typealias Entry = WalletInformationWidgetEntry
func placeholder(in context: Context) -> WalletInformationWidgetEntry {
return WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000)))
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry: SimpleEntry
func getSnapshot(in context: Context, completion: @escaping (WalletInformationWidgetEntry) -> ()) {
let entry: WalletInformationWidgetEntry
if (context.isPreview) {
entry = SimpleEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000)))
entry = WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 10000), allWalletsBalance: WalletData(balance: 1000000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000)))
} else {
entry = SimpleEntry(date: Date(), marketData: emptyMarketData)
entry = WalletInformationWidgetEntry(date: Date(), marketData: emptyMarketData)
}
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
var entries: [WalletInformationWidgetEntry] = []
let userPreferredCurrency = WidgetAPI.getUserPreferredCurrency();
let marketDataEntry = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0)
let allwalletsBalance = WalletData(balance: UserDefaultsGroup.getAllWalletsBalance(), latestTransactionTime: UserDefaultsGroup.getAllWalletsLatestTransactionTime())
WidgetAPI.fetchPrice(currency: userPreferredCurrency, completion: { (result, error) in
let entry: SimpleEntry
let entry: WalletInformationWidgetEntry
if let result = result {
entry = SimpleEntry(date: Date(), marketData: MarketData(nextBlock: "", sats: "", price: result.formattedRate ?? "!", rate: result.rateDouble), allWalletsBalance: allwalletsBalance)
entry = WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "", sats: "", price: result.formattedRate ?? "!", rate: result.rateDouble), allWalletsBalance: allwalletsBalance)
} else {
entry = SimpleEntry(date: Date(), marketData: marketDataEntry, allWalletsBalance: allwalletsBalance)
entry = WalletInformationWidgetEntry(date: Date(), marketData: marketDataEntry, allWalletsBalance: allwalletsBalance)
}
entries.append(entry)
let timeline = Timeline(entries: entries, policy: .atEnd)
@ -45,14 +46,14 @@ struct Provider: TimelineProvider {
}
}
struct SimpleEntry: TimelineEntry {
struct WalletInformationWidgetEntry: TimelineEntry {
let date: Date
let marketData: MarketData
var allWalletsBalance: WalletData = WalletData(balance: 0)
}
struct WalletInformationWidgetEntryView : View {
var entry: Provider.Entry
let entry: WalletInformationWidgetEntry
var WalletBalance: some View {
WalletInformationView(allWalletsBalance: entry.allWalletsBalance, marketData: entry.marketData)
@ -65,22 +66,21 @@ struct WalletInformationWidgetEntryView : View {
}
}
@main
struct WalletInformationWidget: Widget {
let kind: String = "WalletInformationWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
StaticConfiguration(kind: kind, provider: WalletInformationWidgetProvider()) { entry in
WalletInformationWidgetEntryView(entry: entry)
}
.configurationDisplayName("Wallets")
.description("View your total wallet balance.").supportedFamilies([.systemSmall])
.configurationDisplayName("Balance")
.description("View your accumulated balance.").supportedFamilies([.systemSmall])
}
}
struct WalletInformationWidget_Previews: PreviewProvider {
static var previews: some View {
WalletInformationWidgetEntryView(entry: SimpleEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: Double(0)), allWalletsBalance: WalletData(balance: 10000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))))
WalletInformationWidgetEntryView(entry: WalletInformationWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: Double(0)), allWalletsBalance: WalletData(balance: 10000, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: 1568804029000))))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}

View file

@ -1,29 +0,0 @@
<?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>MarketWidget</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>

View file

@ -1,30 +0,0 @@
//
// FiatUnit.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 11/20/20.
// Copyright © 2020 BlueWallet. All rights reserved.
//
import Foundation
typealias FiatUnits = [FiatUnit]
struct FiatUnit: Codable {
let endPointKey: String
let symbol: String
let locale: String
let source: String
}
func fiatUnit(currency: String) -> FiatUnit? {
guard let file = Bundle.main.path(forResource: "FiatUnits", ofType: "plist") else {
return nil
}
let fileURL: URL = URL(fileURLWithPath: file)
var fiatUnits: FiatUnits?
if let data = try? Data(contentsOf: fileURL) {
let decoder = PropertyListDecoder()
fiatUnits = try? decoder.decode(FiatUnits.self, from: data)
}
return fiatUnits?.first(where: {$0.endPointKey == currency})
}

View file

@ -1,416 +0,0 @@
<?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">
<array>
<dict>
<key>endPointKey</key>
<string>USD</string>
<key>symbol</key>
<string>$</string>
<key>locale</key>
<string>en-US</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>ANG</string>
<key>symbol</key>
<string>ƒ</string>
<key>locale</key>
<string>en-SX</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>ARS</string>
<key>symbol</key>
<string>$</string>
<key>locale</key>
<string>es-AR</string>
<key>source</key>
<string>Yadio</string>
</dict>
<dict>
<key>endPointKey</key>
<string>AUD</string>
<key>symbol</key>
<string>$</string>
<key>locale</key>
<string>en-AU</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>AWG</string>
<key>symbol</key>
<string>ƒ</string>
<key>locale</key>
<string>nl-AW</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>BRL</string>
<key>symbol</key>
<string>R$</string>
<key>locale</key>
<string>pt-BR</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>CAD</string>
<key>symbol</key>
<string>$</string>
<key>locale</key>
<string>en-CA</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>CHF</string>
<key>symbol</key>
<string>CHF</string>
<key>locale</key>
<string>de-CH</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>CLP</string>
<key>symbol</key>
<string>$</string>
<key>locale</key>
<string>es-CL</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>COP</string>
<key>symbol</key>
<string>$</string>
<key>locale</key>
<string>es-CO</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>CZK</string>
<key>symbol</key>
<string>Kč</string>
<key>locale</key>
<string>cs-CZ</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>CNY</string>
<key>symbol</key>
<string>¥</string>
<key>locale</key>
<string>zh-CN</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>EUR</string>
<key>symbol</key>
<string>€</string>
<key>locale</key>
<string>en-IE</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>GBP</string>
<key>symbol</key>
<string>£</string>
<key>locale</key>
<string>en-GB</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>HRK</string>
<key>symbol</key>
<string>HRK</string>
<key>locale</key>
<string>hr-HR</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>HUF</string>
<key>symbol</key>
<string>Ft</string>
<key>locale</key>
<string>hu-HU</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>ILS</string>
<key>symbol</key>
<string>₪</string>
<key>locale</key>
<string>he-IL</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>INR</string>
<key>symbol</key>
<string>₹</string>
<key>locale</key>
<string>hi-HN</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>JPY</string>
<key>symbol</key>
<string>¥</string>
<key>locale</key>
<string>ja-JP</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>KES</string>
<key>symbol</key>
<string>Ksh</string>
<key>locale</key>
<string>en-KE</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>KRW</string>
<key>symbol</key>
<string>₩</string>
<key>locale</key>
<string>ko-KR</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>LBP</string>
<key>symbol</key>
<string>ل.ل.</string>
<key>locale</key>
<string>ar-LB</string>
<key>source</key>
<string>BitcoinduLiban</string>
</dict>
<dict>
<key>endPointKey</key>
<string>MXN</string>
<key>symbol</key>
<string>$</string>
<key>locale</key>
<string>es-MX</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>MYR</string>
<key>symbol</key>
<string>RM</string>
<key>locale</key>
<string>ms-MY</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>NGN</string>
<key>symbol</key>
<string>₦</string>
<key>locale</key>
<string>en-NG</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>NOK</string>
<key>symbol</key>
<string>kr</string>
<key>locale</key>
<string>nb-NO</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>NZD</string>
<key>symbol</key>
<string>$</string>
<key>locale</key>
<string>en-NZ</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>PLN</string>
<key>symbol</key>
<string>zł</string>
<key>locale</key>
<string>pl-PL</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>PHP</string>
<key>symbol</key>
<string>₱</string>
<key>locale</key>
<string>en-PH</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>RUB</string>
<key>symbol</key>
<string>₽</string>
<key>locale</key>
<string>ru-RU</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>SGD</string>
<key>symbol</key>
<string>S$</string>
<key>locale</key>
<string>zh-SG</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>SEK</string>
<key>symbol</key>
<string>kr</string>
<key>locale</key>
<string>sv-SE</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>TRY</string>
<key>symbol</key>
<string>₺</string>
<key>locale</key>
<string>tr-TR</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>THB</string>
<key>symbol</key>
<string>฿</string>
<key>locale</key>
<string>th-TH</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>TWD</string>
<key>symbol</key>
<string>NT$</string>
<key>locale</key>
<string>zh-Hant-TW</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>TZS</string>
<key>symbol</key>
<string>TSh</string>
<key>locale</key>
<string>en-TZ</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>UAH</string>
<key>symbol</key>
<string>₴</string>
<key>locale</key>
<string>uk-UA</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>UYU</string>
<key>symbol</key>
<string>$</string>
<key>locale</key>
<string>es-UY</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>VEF</string>
<key>symbol</key>
<string>Bs.</string>
<key>locale</key>
<string>es-VE</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
<dict>
<key>endPointKey</key>
<string>VES</string>
<key>symbol</key>
<string>Bs.</string>
<key>locale</key>
<string>es-VE</string>
<key>source</key>
<string>Yadio</string>
</dict>
<dict>
<key>endPointKey</key>
<string>ZAR</string>
<key>symbol</key>
<string>R</string>
<key>locale</key>
<string>en-ZA</string>
<key>source</key>
<string>CoinDesk</string>
</dict>
</array>
</plist>

View file

@ -1,29 +0,0 @@
<?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>WalletInformationAndMarketWidget</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>

View file

@ -1,14 +0,0 @@
<?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.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.bluewallet.bluewallet</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View file

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>PriceWidget</string>
<string>Widgets</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>

View file

@ -9,25 +9,25 @@
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
return SimpleEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000))
struct MarketWidgetProvider: TimelineProvider {
func placeholder(in context: Context) -> MarketWidgetEntry {
return MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000))
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry: SimpleEntry
func getSnapshot(in context: Context, completion: @escaping (MarketWidgetEntry) -> ()) {
let entry: MarketWidgetEntry
if (context.isPreview) {
entry = SimpleEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000))
entry = MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000))
} else {
entry = SimpleEntry(date: Date(), marketData: emptyMarketData)
entry = MarketWidgetEntry(date: Date(), marketData: emptyMarketData)
}
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
var entries: [MarketWidgetEntry] = []
if context.isPreview {
let entry = SimpleEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000))
let entry = MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10 000", rate: 10000))
entries.append(entry)
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
@ -35,12 +35,12 @@ struct Provider: TimelineProvider {
let userPreferredCurrency = WidgetAPI.getUserPreferredCurrency();
let marketDataEntry = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0)
WidgetAPI.fetchMarketData(currency: userPreferredCurrency, completion: { (result, error) in
let entry: SimpleEntry
let entry: MarketWidgetEntry
if let result = result {
entry = SimpleEntry(date: Date(), marketData: result)
entry = MarketWidgetEntry(date: Date(), marketData: result)
} else {
entry = SimpleEntry(date: Date(), marketData: marketDataEntry)
entry = MarketWidgetEntry(date: Date(), marketData: marketDataEntry)
}
entries.append(entry)
let timeline = Timeline(entries: entries, policy: .atEnd)
@ -50,13 +50,13 @@ struct Provider: TimelineProvider {
}
}
struct SimpleEntry: TimelineEntry {
struct MarketWidgetEntry: TimelineEntry {
let date: Date
let marketData: MarketData
}
struct MarketWidgetEntryView : View {
var entry: Provider.Entry
var entry: MarketWidgetProvider.Entry
var MarketStack: some View {
MarketView(marketData: entry.marketData).padding(EdgeInsets(top: 18, leading: 11, bottom: 18, trailing: 11))
@ -69,12 +69,11 @@ struct MarketWidgetEntryView : View {
}
}
@main
struct MarketWidget: Widget {
let kind: String = "MarketWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
StaticConfiguration(kind: kind, provider: MarketWidgetProvider()) { entry in
MarketWidgetEntryView(entry: entry)
}
.configurationDisplayName("Market")
@ -84,7 +83,7 @@ struct MarketWidget: Widget {
struct MarketWidget_Previews: PreviewProvider {
static var previews: some View {
MarketWidgetEntryView(entry: SimpleEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 0)))
MarketWidgetEntryView(entry: MarketWidgetEntry(date: Date(), marketData: MarketData(nextBlock: "26", sats: "9 134", price: "$10,000", rate: 0)))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}

View file

@ -10,26 +10,27 @@ import WidgetKit
import SwiftUI
var marketData: [MarketDataTimeline: MarketData?] = [ .Current: nil, .Previous: nil]
struct Provider: TimelineProvider {
struct PriceWidgetProvider: TimelineProvider {
typealias Entry = PriceWidgetEntry
func placeholder(in context: Context) -> SimpleEntry {
return SimpleEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"))
func placeholder(in context: Context) -> PriceWidgetEntry {
return PriceWidgetEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"))
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry: SimpleEntry
func getSnapshot(in context: Context, completion: @escaping (PriceWidgetEntry) -> ()) {
let entry: PriceWidgetEntry
if (context.isPreview) {
entry = SimpleEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"))
entry = PriceWidgetEntry(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)
entry = PriceWidgetEntry(date: Date(), currentMarketData: emptyMarketData)
}
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
var entries: [PriceWidgetEntry] = []
if (context.isPreview) {
let entry = SimpleEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"))
let entry = PriceWidgetEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"))
entries.append(entry)
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
@ -48,9 +49,9 @@ struct Provider: TimelineProvider {
marketData[.Previous] = marketData[.Current]
marketData[.Current] = currentMarketData
entryMarketData = currentMarketData
entries.append(SimpleEntry(date:Date(), currentMarketData: entryMarketData))
entries.append(PriceWidgetEntry(date:Date(), currentMarketData: entryMarketData))
} else {
entries.append(SimpleEntry(date:Date(), currentMarketData: currentMarketData))
entries.append(PriceWidgetEntry(date:Date(), currentMarketData: currentMarketData))
}
}
@ -61,7 +62,7 @@ struct Provider: TimelineProvider {
}
}
struct SimpleEntry: TimelineEntry {
struct PriceWidgetEntry: TimelineEntry {
let date: Date
let currentMarketData: MarketData?
var previousMarketData: MarketData? {
@ -70,7 +71,7 @@ struct SimpleEntry: TimelineEntry {
}
struct PriceWidgetEntryView : View {
var entry: Provider.Entry
let entry: PriceWidgetEntry
var priceView: some View {
PriceView(currentMarketData: entry.currentMarketData, previousMarketData: marketData[.Previous] ?? emptyMarketData).padding()
}
@ -82,12 +83,11 @@ struct PriceWidgetEntryView : View {
}
}
@main
struct PriceWidget: Widget {
let kind: String = "PriceWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
StaticConfiguration(kind: kind, provider: PriceWidgetProvider()) { entry in
PriceWidgetEntryView(entry: entry)
}
.configurationDisplayName("Price")
@ -97,7 +97,7 @@ struct PriceWidget: Widget {
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")))
PriceWidgetEntryView(entry: PriceWidgetEntry(date: Date(), currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00")))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}

View file

@ -0,0 +1,51 @@
//
// FiatUnit.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 11/20/20.
// Copyright © 2020 BlueWallet. All rights reserved.
//
import Foundation
struct FiatUnit: Codable {
let endPointKey: String
let symbol: String
let locale: String
let source: String
}
func fiatUnit(currency: String) -> FiatUnit? {
return Bundle.main.decode([String: FiatUnit].self, from: "fiatUnit.json").first(where: {$1.endPointKey == currency})?.value
}
extension Bundle {
func decode<T: Decodable>(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = dateDecodingStrategy
decoder.keyDecodingStrategy = keyDecodingStrategy
do {
return try decoder.decode(T.self, from: data)
} catch DecodingError.keyNotFound(let key, let context) {
fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' not found \(context.debugDescription)")
} catch DecodingError.typeMismatch(_, let context) {
fatalError("Failed to decode \(file) from bundle due to type mismatch \(context.debugDescription)")
} catch DecodingError.valueNotFound(let type, let context) {
fatalError("Failed to decode \(file) from bundle due to missing \(type) value \(context.debugDescription)")
} catch DecodingError.dataCorrupted(_) {
fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON")
} catch {
fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
}
}
}

View file

@ -31,6 +31,8 @@ class WidgetAPI {
urlString = "https://api.yadio.io/json/\(endPointKey)"
case "BitcoinduLiban":
urlString = "https://bitcoinduliban.org/api.php?key=lbpusd"
case "Exir":
urlString = "https://api.exir.io/v1/ticker?symbol=btc-irt"
default:
urlString = "https://api.coindesk.com/v1/bpi/currentprice/\(endPointKey).json"
}
@ -59,7 +61,12 @@ class WidgetAPI {
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "BitcoinduLiban":
guard let rateString = json["BTC/LBP"] as? String else { break }
guard let rateDouble = Double(rateString) else {return}
guard let rateDouble = Double(rateString) else { break }
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "Exir":
guard let rateDouble = json["last"] as? Double else { break }
let rateString = String(rateDouble)
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
default:

View file

@ -8,6 +8,23 @@
import Foundation
extension Numeric {
var abbreviated: String {
let bytecountFormatter = ByteCountFormatter()
bytecountFormatter.zeroPadsFractionDigits = true
bytecountFormatter.countStyle = .decimal
bytecountFormatter.isAdaptive = false
let bytesString = bytecountFormatter.string(fromByteCount: (self as! NSNumber).int64Value)
let numericString = bytesString
.replacingOccurrences(of: "bytes", with: "")
.replacingOccurrences(of: "B", with: "") // removes B (bytes) in 'KB'/'MB'/'GB'
.replacingOccurrences(of: "G", with: "B") // replace G (Giga) to just B (billions)
return numericString.replacingOccurrences(of: " ", with: "")
}
}
struct WidgetDataStore: Codable {
let rate: String
let lastUpdate: String
@ -23,19 +40,21 @@ struct WidgetDataStore: Codable {
}
return rate
}
var formattedRateForSmallComplication: String? {
return rateDouble.abbreviated
}
var formattedRateForComplication: String? {
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: WidgetAPI.getUserPreferredCurrencyLocale())
numberFormatter.numberStyle = .currency
numberFormatter.usesSignificantDigits = true
numberFormatter.maximumFractionDigits = 0
numberFormatter.minimumFractionDigits = 0
numberFormatter.currencySymbol = ""
if let rateString = numberFormatter.string(from: NSNumber(value: rateDouble)) {
return rateString
}
return rate
}
var date: Date? {
let isoDateFormatter = ISO8601DateFormatter()
let dateFormatter = DateFormatter()

Some files were not shown because too many files have changed in this diff Show more