diff --git a/App.js b/App.js index 02cdaa5d..ad778bd2 100644 --- a/App.js +++ b/App.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Linking, AppState, Clipboard, StyleSheet, KeyboardAvoidingView, Platform, View } from 'react-native'; +import { Linking, DeviceEventEmitter, AppState, Clipboard, StyleSheet, KeyboardAvoidingView, Platform, View } from 'react-native'; import AsyncStorage from '@react-native-community/async-storage'; import Modal from 'react-native-modal'; import { NavigationActions } from 'react-navigation'; @@ -10,8 +10,9 @@ import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import url from 'url'; import { AppStorage, LightningCustodianWallet } from './class'; import { Chain } from './models/bitcoinUnits'; - +import QuickActions from 'react-native-quick-actions'; import * as Sentry from '@sentry/react-native'; +import OnAppLaunch from './class/onAppLaunch'; if (process.env.NODE_ENV !== 'development') { Sentry.init({ @@ -36,18 +37,71 @@ export default class App extends React.Component { clipboardContent: '', }; - componentDidMount() { - Linking.getInitialURL() - .then(url => { - if (this.hasSchema(url)) { - this.handleOpenURL({ url }); - } - }) - .catch(console.error); + async componentDidMount() { Linking.addEventListener('url', this.handleOpenURL); AppState.addEventListener('change', this._handleAppStateChange); + QuickActions.popInitialAction().then(this.popInitialAction); + DeviceEventEmitter.addListener('quickActionShortcut', this.walletQuickActions); } + popInitialAction = async data => { + if (data) { + // eslint-disable-next-line no-unused-expressions + this.navigator.dismiss; + const wallet = BlueApp.getWallets().find(wallet => wallet.getID() === data.userInfo.url.split('wallet/')[1]); + this.navigator.dispatch( + NavigationActions.navigate({ + key: `WalletTransactions-${wallet.getID()}`, + routeName: 'WalletTransactions', + params: { + wallet, + }, + }), + ); + } else { + const url = await Linking.getInitialURL(); + if (url) { + if (this.hasSchema(url)) { + this.handleOpenURL({ url }); + } + } else { + const isViewAllWalletsEnabled = await OnAppLaunch.isViewAllWalletsEnabled(); + if (!isViewAllWalletsEnabled) { + // eslint-disable-next-line no-unused-expressions + this.navigator.dismiss; + const selectedDefaultWallet = await OnAppLaunch.getSelectedDefaultWallet(); + const wallet = BlueApp.getWallets().find(wallet => wallet.getID() === selectedDefaultWallet.getID()); + if (wallet) { + this.navigator.dispatch( + NavigationActions.navigate({ + routeName: 'WalletTransactions', + key: `WalletTransactions-${wallet.getID()}`, + params: { + wallet, + }, + }), + ); + } + } + } + } + }; + + walletQuickActions = data => { + const wallet = BlueApp.getWallets().find(wallet => wallet.getID() === data.userInfo.url.split('wallet/')[1]); + // eslint-disable-next-line no-unused-expressions + this.navigator.dismiss; + this.navigator.dispatch( + NavigationActions.navigate({ + routeName: 'WalletTransactions', + key: `WalletTransactions-${wallet.getID()}`, + params: { + wallet, + }, + }), + ); + }; + componentWillUnmount() { Linking.removeEventListener('url', this.handleOpenURL); AppState.removeEventListener('change', this._handleAppStateChange); diff --git a/BlueApp.js b/BlueApp.js index 641b6e09..945fe75e 100644 --- a/BlueApp.js +++ b/BlueApp.js @@ -2,6 +2,7 @@ * @exports {AppStorage} */ import { AppStorage } from './class'; +import DeviceQuickActions from './class/quickActions'; let prompt = require('./prompt'); let EV = require('./events'); let currency = require('./currency'); @@ -19,6 +20,7 @@ async function startAndDecrypt(retry) { } let password = false; if (await BlueApp.storageIsEncrypted()) { + DeviceQuickActions.clearShortcutItems(); do { password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, false); } while (!password); diff --git a/MainBottomTabs.js b/MainBottomTabs.js index 38e54f68..2d1365a3 100644 --- a/MainBottomTabs.js +++ b/MainBottomTabs.js @@ -64,6 +64,8 @@ const WalletsStackNavigator = createStackNavigator( }, WalletTransactions: { screen: WalletTransactions, + path: 'WalletTransactions', + routeName: 'WalletTransactions', }, TransactionStatus: { screen: TransactionStatus, diff --git a/WatchConnectivity.ios.js b/WatchConnectivity.ios.js index 32402f0b..5efe3581 100644 --- a/WatchConnectivity.ios.js +++ b/WatchConnectivity.ios.js @@ -65,8 +65,6 @@ export default class WatchConnectivity { } return InteractionManager.runAfterInteractions(async () => { - console.warn(WatchConnectivity.shared.isAppInstalled); - if (WatchConnectivity.shared.isAppInstalled) { let wallets = []; diff --git a/android/app/src/main/res/drawable/quickactions.png b/android/app/src/main/res/drawable/quickactions.png new file mode 100644 index 00000000..147d063e Binary files /dev/null and b/android/app/src/main/res/drawable/quickactions.png differ diff --git a/class/app-storage.js b/class/app-storage.js index 2b0f78b8..41a6121a 100644 --- a/class/app-storage.js +++ b/class/app-storage.js @@ -12,6 +12,7 @@ import { } from './'; import { LightningCustodianWallet } from './lightning-custodian-wallet'; import WatchConnectivity from '../WatchConnectivity'; +import DeviceQuickActions from './quickActions'; const encryption = require('../encryption'); export class AppStorage { @@ -137,6 +138,8 @@ export class AppStorage { this.cachedPassword = password; await this.setItem('data', data); await this.setItem(AppStorage.FLAG_ENCRYPTED, '1'); + DeviceQuickActions.clearShortcutItems(); + DeviceQuickActions.removeAllWallets(); } /** @@ -251,6 +254,8 @@ export class AppStorage { await this.saveToDisk(); }; await WatchConnectivity.shared.sendWalletsToWatch(this.wallets); + DeviceQuickActions.setWallets(this.wallets); + DeviceQuickActions.setQuickActions(); return true; } else { return false; // failed loading data or loading/decryptin data @@ -326,7 +331,9 @@ export class AppStorage { } WatchConnectivity.shared.wallets = this.wallets; WatchConnectivity.shared.tx_metadata = this.tx_metadata; - await WatchConnectivity.shared.sendWalletsToWatch(); + WatchConnectivity.shared.sendWalletsToWatch(); + DeviceQuickActions.setWallets(this.wallets); + DeviceQuickActions.setQuickActions(); return this.setItem('data', JSON.stringify(data)); } diff --git a/class/quickActions.js b/class/quickActions.js new file mode 100644 index 00000000..55fc08d8 --- /dev/null +++ b/class/quickActions.js @@ -0,0 +1,46 @@ +import QuickActions from 'react-native-quick-actions'; +import { Platform } from 'react-native'; + +export default class DeviceQuickActions { + static shared = new DeviceQuickActions(); + wallets; + + static setWallets(wallets) { + DeviceQuickActions.shared.wallets = wallets; + } + + static removeAllWallets() { + DeviceQuickActions.shared.wallets = undefined; + } + + static setQuickActions() { + if (DeviceQuickActions.shared.wallets === undefined) { + return; + } + QuickActions.isSupported((error, supported) => { + if (supported && error === null) { + let shortcutItems = []; + const loc = require('../loc/'); + for (const wallet of DeviceQuickActions.shared.wallets) { + shortcutItems.push({ + type: 'Wallets', // Required + title: wallet.getLabel(), // Optional, if empty, `type` will be used instead + subtitle: + wallet.hideBalance || wallet.getBalance() <= 0 + ? '' + : loc.formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true), + userInfo: { + url: `bluewallet://wallet/${wallet.getID()}`, // Provide any custom data like deep linking URL + }, + icon: Platform.select({ android: 'quickactions', ios: 'bookmark' }), + }); + } + QuickActions.setShortcutItems(shortcutItems); + } + }); + } + + static clearShortcutItems() { + QuickActions.clearShortcutItems(); + } +} diff --git a/currency.js b/currency.js index 8f687d4e..474ca493 100644 --- a/currency.js +++ b/currency.js @@ -2,6 +2,8 @@ import Frisbee from 'frisbee'; import AsyncStorage from '@react-native-community/async-storage'; import { AppStorage } from './class'; import { FiatUnit } from './models/fiatUnit'; +import DefaultPreference from 'react-native-default-preference'; +import DeviceQuickActions from './class/quickActions'; let BigNumber = require('bignumber.js'); let preferredFiatCurrency = FiatUnit.USD; let exchangeRates = {}; @@ -19,10 +21,17 @@ const STRUCT = { */ async function setPrefferedCurrency(item) { await AsyncStorage.setItem(AppStorage.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('-', '_')); + DeviceQuickActions.setQuickActions(); } async function getPreferredCurrency() { - return JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY)); + let preferredCurrency = await JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY)); + await DefaultPreference.set('preferredCurrency', preferredCurrency.endPointKey); + await DefaultPreference.set('preferredCurrencyLocale', preferredCurrency.locale.replace('-', '_')); + return preferredCurrency; } async function updateExchangeRate() { @@ -57,6 +66,7 @@ async function updateExchangeRate() { exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = json.bpi[preferredFiatCurrency.endPointKey].rate_float * 1; await AsyncStorage.setItem(AppStorage.EXCHANGE_RATES, JSON.stringify(exchangeRates)); await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(preferredFiatCurrency)); + DeviceQuickActions.setQuickActions(); } let interval = false; @@ -71,7 +81,10 @@ async function startUpdater() { } function satoshiToLocalCurrency(satoshi) { - if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) return '...'; + if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) { + startUpdater(); + return '...'; + } let b = new BigNumber(satoshi); b = b diff --git a/edit-version-number.sh b/edit-version-number.sh index 95e5af05..4cd1fbce 100755 --- a/edit-version-number.sh +++ b/edit-version-number.sh @@ -1,6 +1,7 @@ vim ios/BlueWallet/Info.plist vim ios/BlueWalletWatch/Info.plist vim "ios/BlueWalletWatch Extension/Info.plist" +vim "ios/TodayExtension/Info.plist" vim android/app/build.gradle vim package.json vim package-lock.json diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index 199d2f1f..9c919f11 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -19,7 +19,13 @@ 2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 2D16E6881FA4F8E400B85C8A /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D16E6891FA4F8E400B85C8A /* libReact.a */; }; 2DCD954D1E0B4F2C00145EB5 /* BlueWalletTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* BlueWalletTests.m */; }; + 32002D9D236FAA9F00B93396 /* TodayDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32002D9C236FAA9F00B93396 /* TodayDataStore.swift */; }; 3208E93922F63279007F5A27 /* AppCenter-Config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3208E93822F63279007F5A27 /* AppCenter-Config.plist */; }; + 3271B0AB236E2E0700DA766F /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3271B0AA236E2E0700DA766F /* NotificationCenter.framework */; }; + 3271B0AE236E2E0700DA766F /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3271B0AD236E2E0700DA766F /* TodayViewController.swift */; }; + 3271B0B1236E2E0700DA766F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3271B0AF236E2E0700DA766F /* MainInterface.storyboard */; }; + 3271B0B5236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 3271B0BB236E329400DA766F /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3271B0BA236E329400DA766F /* API.swift */; }; 32B5A32A2334450100F8D608 /* Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5A3292334450100F8D608 /* Bridge.swift */; }; 32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F0A2992311DBB20095C559 /* ComplicationController.swift */; }; 398DED6337DF58F0ECFD8F2E /* libPods-BlueWalletTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 70089FECE936F9A0AC45B7CE /* libPods-BlueWalletTests.a */; }; @@ -68,6 +74,13 @@ remoteGlobalIDString = 2D02E47A1E0B4A5D006451C7; remoteInfo = "BlueWallet-tvOS"; }; + 3271B0B3236E2E0700DA766F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3271B0A8236E2E0700DA766F; + remoteInfo = TodayExtension; + }; B40D4E3E225841ED00428FCC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; @@ -85,6 +98,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 3271B0B6236E2E0700DA766F /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 3271B0B5236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; B40D4E2D225841C300428FCC /* Embed Watch Content */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -131,7 +155,15 @@ 2D02E4901E0B4A5D006451C7 /* BlueWallet-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "BlueWallet-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D16E6891FA4F8E400B85C8A /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; }; 2FCC2CD6FF4448229D0CE0F3 /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = ""; }; + 32002D9C236FAA9F00B93396 /* TodayDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayDataStore.swift; sourceTree = ""; }; 3208E93822F63279007F5A27 /* AppCenter-Config.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "AppCenter-Config.plist"; sourceTree = ""; }; + 32475F792370F6D30070E6CF /* BlueWallet - Bitcoin Price.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BlueWallet - Bitcoin Price.entitlements"; sourceTree = ""; }; + 3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "BlueWallet - Bitcoin Price.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3271B0AA236E2E0700DA766F /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; + 3271B0AD236E2E0700DA766F /* TodayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewController.swift; sourceTree = ""; }; + 3271B0B0236E2E0700DA766F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + 3271B0B2236E2E0700DA766F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3271B0BA236E329400DA766F /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; 32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWallet-Bridging-Header.h"; sourceTree = ""; }; 32B5A3292334450100F8D608 /* Bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bridge.swift; sourceTree = ""; }; 32F0A24F2310B0700095C559 /* BlueWalletWatch Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BlueWalletWatch Extension.entitlements"; sourceTree = ""; }; @@ -255,6 +287,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3271B0A6236E2E0700DA766F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3271B0AB236E2E0700DA766F /* NotificationCenter.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 421830728822A20A50D8A07C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -326,10 +366,33 @@ 154B05BEF3C3512F67A08374 /* libPods-BlueWalletWatch Extension.a */, 70089FECE936F9A0AC45B7CE /* libPods-BlueWalletTests.a */, 731973BA0AC6EA78962CE5B6 /* libPods-BlueWallet.a */, + 3271B0AA236E2E0700DA766F /* NotificationCenter.framework */, ); name = Frameworks; sourceTree = ""; }; + 3271B0AC236E2E0700DA766F /* TodayExtension */ = { + isa = PBXGroup; + children = ( + 32475F792370F6D30070E6CF /* BlueWallet - Bitcoin Price.entitlements */, + 32A5F95E236E4A2A00443927 /* API */, + 3271B0AD236E2E0700DA766F /* TodayViewController.swift */, + 3271B0AF236E2E0700DA766F /* MainInterface.storyboard */, + 3271B0B2236E2E0700DA766F /* Info.plist */, + 32002D9C236FAA9F00B93396 /* TodayDataStore.swift */, + ); + path = TodayExtension; + sourceTree = ""; + }; + 32A5F95E236E4A2A00443927 /* API */ = { + isa = PBXGroup; + children = ( + 3271B0BA236E329400DA766F /* API.swift */, + ); + name = API; + path = "New Group"; + sourceTree = ""; + }; 4B0CACE36C3348E1BCEA92C8 /* Resources */ = { isa = PBXGroup; children = ( @@ -359,6 +422,7 @@ 00E356EF1AD99517003FC87E /* BlueWalletTests */, B40D4E31225841EC00428FCC /* BlueWalletWatch */, B40D4E40225841ED00428FCC /* BlueWalletWatch Extension */, + 3271B0AC236E2E0700DA766F /* TodayExtension */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, B40FE50A21FAD228005D5578 /* Recovered References */, @@ -379,6 +443,7 @@ 2D02E4901E0B4A5D006451C7 /* BlueWallet-tvOSTests.xctest */, B40D4E30225841EC00428FCC /* BlueWalletWatch.app */, B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */, + 3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */, ); name = Products; sourceTree = ""; @@ -508,11 +573,13 @@ B40D4E2D225841C300428FCC /* Embed Watch Content */, 791C03B6EF06B63A39F55115 /* [CP] Copy Pods Resources */, 2130DE983D1D45AC8FC45F7E /* Upload Debug Symbols to Sentry */, + 3271B0B6236E2E0700DA766F /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( B40D4E4C225841ED00428FCC /* PBXTargetDependency */, + 3271B0B4236E2E0700DA766F /* PBXTargetDependency */, ); name = BlueWallet; productName = "Hello World"; @@ -555,6 +622,23 @@ productReference = 2D02E4901E0B4A5D006451C7 /* BlueWallet-tvOSTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 3271B0A8236E2E0700DA766F /* TodayExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3271B0B9236E2E0700DA766F /* Build configuration list for PBXNativeTarget "TodayExtension" */; + buildPhases = ( + 3271B0A5236E2E0700DA766F /* Sources */, + 3271B0A6236E2E0700DA766F /* Frameworks */, + 3271B0A7236E2E0700DA766F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TodayExtension; + productName = TodayExtension; + productReference = 3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */; + productType = "com.apple.product-type.app-extension"; + }; B40D4E2F225841EC00428FCC /* BlueWalletWatch */ = { isa = PBXNativeTarget; buildConfigurationList = B40D4E52225841ED00428FCC /* Build configuration list for PBXNativeTarget "BlueWalletWatch" */; @@ -598,7 +682,7 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1020; + LastSwiftUpdateCheck = 1120; LastUpgradeCheck = 1020; ORGANIZATIONNAME = Facebook; TargetAttributes = { @@ -610,7 +694,7 @@ 13B07F861A680F5B00A75B9A = { DevelopmentTeam = A7W54YZ4WU; LastSwiftMigration = 1030; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Keychain = { enabled = 0; @@ -628,6 +712,11 @@ ProvisioningStyle = Automatic; TestTargetID = 2D02E47A1E0B4A5D006451C7; }; + 3271B0A8236E2E0700DA766F = { + CreatedOnToolsVersion = 11.2; + DevelopmentTeam = A7W54YZ4WU; + ProvisioningStyle = Automatic; + }; B40D4E2F225841EC00428FCC = { CreatedOnToolsVersion = 10.2; DevelopmentTeam = A7W54YZ4WU; @@ -665,6 +754,7 @@ 2D02E48F1E0B4A5D006451C7 /* BlueWallet-tvOSTests */, B40D4E2F225841EC00428FCC /* BlueWalletWatch */, B40D4E3B225841ED00428FCC /* BlueWalletWatch Extension */, + 3271B0A8236E2E0700DA766F /* TodayExtension */, ); }; /* End PBXProject section */ @@ -702,6 +792,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3271B0A7236E2E0700DA766F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3271B0B1236E2E0700DA766F /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; B40D4E2E225841EC00428FCC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -938,6 +1036,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3271B0A5236E2E0700DA766F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3271B0BB236E329400DA766F /* API.swift in Sources */, + 3271B0AE236E2E0700DA766F /* TodayViewController.swift in Sources */, + 32002D9D236FAA9F00B93396 /* TodayDataStore.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; B40D4E38225841ED00428FCC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -973,6 +1081,11 @@ target = 2D02E47A1E0B4A5D006451C7 /* BlueWallet-tvOS */; targetProxy = 2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */; }; + 3271B0B4236E2E0700DA766F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3271B0A8236E2E0700DA766F /* TodayExtension */; + targetProxy = 3271B0B3236E2E0700DA766F /* PBXContainerItemProxy */; + }; B40D4E3F225841ED00428FCC /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B40D4E3B225841ED00428FCC /* BlueWalletWatch Extension */; @@ -995,6 +1108,14 @@ path = BlueWallet; sourceTree = ""; }; + 3271B0AF236E2E0700DA766F /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 3271B0B0236E2E0700DA766F /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; B40D4E32225841EC00428FCC /* Interface.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -1109,16 +1230,18 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9B3A324B70BC8C6D9314FD4F /* Pods-BlueWallet.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; - DEVELOPMENT_TEAM = A7W54YZ4WU; + DEVELOPMENT_TEAM = ""; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = BlueWallet/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -1131,7 +1254,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet; PRODUCT_NAME = BlueWallet; - PROVISIONING_PROFILE_SPECIFIER = "io.bluewallet.bluewallet AppStore"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.2; @@ -1144,15 +1267,17 @@ isa = XCBuildConfiguration; baseConfigurationReference = B459EE96941AE09BCB547DC0 /* Pods-BlueWallet.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = BlueWallet/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -1387,6 +1512,67 @@ }; name = Release; }; + 3271B0B7236E2E0700DA766F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = A7W54YZ4WU; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = TodayExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension; + PRODUCT_NAME = "BlueWallet - Bitcoin Price"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3271B0B8236E2E0700DA766F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = A7W54YZ4WU; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = TodayExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension; + PRODUCT_NAME = "BlueWallet - Bitcoin Price"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 83CBBA201A601CBA00E9B192 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1438,7 +1624,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1486,7 +1672,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 4.2; VALIDATE_PRODUCT = YES; }; name = Release; @@ -1502,6 +1688,7 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = dwarf; @@ -1535,6 +1722,7 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; @@ -1584,7 +1772,7 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 5.0; }; @@ -1617,7 +1805,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 5.0; }; @@ -1662,6 +1850,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 3271B0B9236E2E0700DA766F /* Build configuration list for PBXNativeTarget "TodayExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3271B0B7236E2E0700DA766F /* Debug */, + 3271B0B8236E2E0700DA766F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "BlueWallet" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/ios/BlueWallet/AppDelegate.m b/ios/BlueWallet/AppDelegate.m index d8267177..8be47a13 100644 --- a/ios/BlueWallet/AppDelegate.m +++ b/ios/BlueWallet/AppDelegate.m @@ -10,6 +10,7 @@ #import #import #import +#import "RNQuickActionManager.h" #import #import #import @@ -49,4 +50,8 @@ return NO; } +- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded)) completionHandler { + [RNQuickActionManager onQuickActionPress:shortcutItem completionHandler:completionHandler]; +} + @end diff --git a/ios/BlueWallet/BlueWallet.entitlements b/ios/BlueWallet/BlueWallet.entitlements index 0c67376e..86bfd6c5 100644 --- a/ios/BlueWallet/BlueWallet.entitlements +++ b/ios/BlueWallet/BlueWallet.entitlements @@ -1,5 +1,10 @@ - + + com.apple.security.application-groups + + group.io.bluewallet.bluewallet + + diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index ff5dfd7f..0ec27310 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -47,21 +47,6 @@ NSExceptionDomains - electrum3.bluewallet.io - - NSExceptionAllowsInsecureHTTPLoads - - - electrum2.bluewallet.io - - NSExceptionAllowsInsecureHTTPLoads - - - electrum1.bluewallet.io - - NSExceptionAllowsInsecureHTTPLoads - - localhost NSExceptionAllowsInsecureHTTPLoads diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ec4e7339..c9993f76 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -10,13 +10,13 @@ PODS: - AppCenter/Crashes - AppCenterReactNativeShared - React - - AppCenter/Analytics (2.5.0): + - AppCenter/Analytics (2.5.1): - AppCenter/Core - - AppCenter/Core (2.5.0) - - AppCenter/Crashes (2.5.0): + - AppCenter/Core (2.5.1) + - AppCenter/Crashes (2.5.1): - AppCenter/Core - - AppCenterReactNativeShared (2.5.0): - - AppCenter/Core (= 2.5.0) + - AppCenterReactNativeShared (2.6.0): + - AppCenter/Core (= 2.5.1) - boost-for-react-native (1.63.0) - BVLinearGradient (2.5.4): - React @@ -133,6 +133,8 @@ PODS: - React - RNCAsyncStorage (1.6.2): - React + - RNDefaultPreference (1.4.1): + - React - RNDeviceInfo (4.0.1): - React - RNFS (2.13.3): @@ -141,6 +143,8 @@ PODS: - React - RNHandoff (0.0.3): - React + - RNQuickAction (0.3.12): + - React - RNRate (1.0.1): - React - RNSecureKeyStore (1.0.0): @@ -156,9 +160,9 @@ PODS: - React - RNWatch (0.4.1): - React - - Sentry (4.4.1): - - Sentry/Core (= 4.4.1) - - Sentry/Core (4.4.1) + - Sentry (4.4.2): + - Sentry/Core (= 4.4.2) + - Sentry/Core (4.4.2) - swift_qrcodejs (1.1.2) - TcpSockets (3.3.2): - React @@ -204,10 +208,12 @@ DEPENDENCIES: - ReactNativePrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`) - "RemobileReactNativeQrcodeLocalImage (from `../node_modules/@remobile/react-native-qrcode-local-image`)" - "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)" + - RNDefaultPreference (from `../node_modules/react-native-default-preference`) - RNDeviceInfo (from `../node_modules/react-native-device-info`) - RNFS (from `../node_modules/react-native-fs`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNHandoff (from `../node_modules/react-native-handoff`) + - RNQuickAction (from `../node_modules/react-native-quick-actions`) - RNRate (from `../node_modules/react-native-rate/ios`) - RNSecureKeyStore (from `../node_modules/react-native-secure-key-store/ios`) - "RNSentry (from `../node_modules/@sentry/react-native`)" @@ -302,6 +308,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@remobile/react-native-qrcode-local-image" RNCAsyncStorage: :path: "../node_modules/@react-native-community/async-storage" + RNDefaultPreference: + :path: "../node_modules/react-native-default-preference" RNDeviceInfo: :path: "../node_modules/react-native-device-info" RNFS: @@ -310,6 +318,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-gesture-handler" RNHandoff: :path: "../node_modules/react-native-handoff" + RNQuickAction: + :path: "../node_modules/react-native-quick-actions" RNRate: :path: "../node_modules/react-native-rate/ios" RNSecureKeyStore: @@ -332,11 +342,11 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - AppCenter: 637f180deefc61e8ab3f94223869ee50f61dabea + AppCenter: fddcbac6e4baae3d93a196ceb0bfe0e4ce407dec appcenter: bde9923b687332a25607fc1aa9577c9361cfed85 appcenter-analytics: 0ee7a35def715d4bce58ec435f54161770195166 appcenter-crashes: 9f9c5647dba19026ff09509576fb7233f69697ff - AppCenterReactNativeShared: 99e7f662ec66b1cb41306ecf357aabac35931c08 + AppCenterReactNativeShared: d5e360f8a4cb5126d29e31ab98051d2f070ba631 boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c BVLinearGradient: 8cbc5155c978f2e43098818c91d206d07aae6d30 DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 @@ -373,10 +383,12 @@ SPEC CHECKSUMS: ReactNativePrivacySnapshot: cc295e45dc22810e9ff2c93380d643de20a77015 RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa RNCAsyncStorage: 5ae4d57458804e99f73d427214442a6b10a53856 + RNDefaultPreference: 12d246dd2222e66dadcd76cc1250560663befc3a RNDeviceInfo: 12faae605ba42a1a5041c3c41a77834bc23f049d RNFS: c9bbde46b0d59619f8e7b735991c60e0f73d22c1 RNGestureHandler: 5329a942fce3d41c68b84c2c2276ce06a696d8b0 RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa + RNQuickAction: eca9a5dd04b5cdf8a0dd32d8be8844dc33aba2bd RNRate: 29be49c24b314c4e8ec09d848c3965f61cb0be47 RNSecureKeyStore: f1ad870e53806453039f650720d2845c678d89c8 RNSentry: 2803ba8c8129dcf26b79e9b4d8c80168be6e4390 @@ -384,7 +396,7 @@ SPEC CHECKSUMS: RNSVG: 0eb087cfb5d7937be93c45b163b26352a647e681 RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4 RNWatch: a14e378448e187cc12f307f61d41fe8a65400e86 - Sentry: 5d312a04e369154aeac616214f4dfc3cbcc8b296 + Sentry: bba998b0fb157fdd6596aa73290a9d67ae47be79 swift_qrcodejs: 4d024fc98b0778b804ec6a5c810880fd092aec9d TcpSockets: 8d839b9b14f6f344d98e4642ded13ab3112b462d ToolTipMenu: bdcaa0e888bcf44778a67fe34639b094352e904e diff --git a/ios/TodayExtension/Base.lproj/MainInterface.storyboard b/ios/TodayExtension/Base.lproj/MainInterface.storyboard new file mode 100644 index 00000000..316789da --- /dev/null +++ b/ios/TodayExtension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/TodayExtension/BlueWallet - Bitcoin Price.entitlements b/ios/TodayExtension/BlueWallet - Bitcoin Price.entitlements new file mode 100644 index 00000000..86bfd6c5 --- /dev/null +++ b/ios/TodayExtension/BlueWallet - Bitcoin Price.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.io.bluewallet.bluewallet + + + diff --git a/ios/TodayExtension/Info.plist b/ios/TodayExtension/Info.plist new file mode 100644 index 00000000..2f58e60a --- /dev/null +++ b/ios/TodayExtension/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + $(PRODUCT_NAME) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 4.7.3 + CFBundleVersion + 1 + NSExtension + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.widget-extension + + + diff --git a/ios/TodayExtension/New Group/API.swift b/ios/TodayExtension/New Group/API.swift new file mode 100644 index 00000000..3a49ad4c --- /dev/null +++ b/ios/TodayExtension/New Group/API.swift @@ -0,0 +1,65 @@ +// +// API.swift +// TodayExtension +// +// Created by Marcos Rodriguez on 11/2/19. +// Copyright © 2019 Facebook. All rights reserved. +// + +import Foundation + +class API { + + static func fetchPrice(currency: String, completion: @escaping ((Dictionary?, Error?) -> Void)) { + guard let url = URL(string: "https://api.coindesk.com/v1/bpi/currentPrice/\(currency).json") else {return} + + URLSession.shared.dataTask(with: url) { (data, response, error) in + guard let dataResponse = data, + let json = try? JSONSerialization.jsonObject(with: dataResponse, options: .mutableContainers) as? Dictionary, + error == nil else { + print(error?.localizedDescription ?? "Response Error") + completion(nil, error) + return } + + completion(json, nil) + }.resume() + } + + static func getUserPreferredCurrency() -> String { + guard let userDefaults = UserDefaults(suiteName: "group.io.bluewallet.bluewallet"), + let preferredCurrency = userDefaults.string(forKey: "preferredCurrency") + else { + return "USD" + } + + if preferredCurrency != API.getLastSelectedCurrency() { + UserDefaults.standard.removeObject(forKey: TodayData.TodayCachedDataStoreKey) + UserDefaults.standard.removeObject(forKey: TodayData.TodayDataStoreKey) + UserDefaults.standard.synchronize() + } + + return preferredCurrency + } + + static func getUserPreferredCurrencyLocale() -> String { + guard let userDefaults = UserDefaults(suiteName: "group.io.bluewallet.bluewallet"), + let preferredCurrency = userDefaults.string(forKey: "preferredCurrencyLocale") + else { + return "en_US" + } + return preferredCurrency + } + + static func getLastSelectedCurrency() -> String { + guard let dataStore = UserDefaults.standard.string(forKey: "currency") else { + return "USD" + } + + return dataStore + } + + static func saveNewSelectedCurrency() { + UserDefaults.standard.setValue(API.getUserPreferredCurrency(), forKey: "currency") + } + +} diff --git a/ios/TodayExtension/TodayDataStore.swift b/ios/TodayExtension/TodayDataStore.swift new file mode 100644 index 00000000..5059b112 --- /dev/null +++ b/ios/TodayExtension/TodayDataStore.swift @@ -0,0 +1,89 @@ +// +// TodayDataStore.swift +// TodayExtension +// +// Created by Marcos Rodriguez on 11/3/19. +// Copyright © 2019 Facebook. All rights reserved. +// + +import Foundation + +struct TodayDataStore { + let rate: String + let lastUpdate: String + + var formattedDate: String? { + let isoDateFormatter = ISO8601DateFormatter() + let dateFormatter = DateFormatter() + dateFormatter.timeStyle = .short + dateFormatter.dateStyle = .short + + if let date = isoDateFormatter.date(from: lastUpdate) { + return dateFormatter.string(from: date) + } + return nil + } + + var rateDoubleValue: Double? { + let rateDigits = rate.replacingOccurrences(of: ",", with: ""); + let numberFormatter = NumberFormatter() + numberFormatter.numberStyle = .decimal + numberFormatter.maximumFractionDigits = 2 + numberFormatter.minimumFractionDigits = 2 + + if let rateDoubleValue = numberFormatter.number(from: rateDigits) { + return rateDoubleValue.doubleValue + } + + return nil + } + + var formattedRate: String? { + let rateDigits = rate.replacingOccurrences(of: ",", with: ""); + let numberFormatter = NumberFormatter() + numberFormatter.numberStyle = .decimal + numberFormatter.maximumFractionDigits = 2 + numberFormatter.minimumFractionDigits = 2 + if let rateNumber = numberFormatter.number(from: rateDigits) { + numberFormatter.numberStyle = .currency + numberFormatter.locale = Locale(identifier: API.getUserPreferredCurrencyLocale()) + return numberFormatter.string(from: rateNumber); + } + return nil + } +} + +class TodayData { + + static let TodayDataStoreKey = "TodayDataStoreKey" + static let TodayCachedDataStoreKey = "TodayCachedDataStoreKey" + + static func savePriceRateAndLastUpdate(rate: String, lastUpdate: String) { + UserDefaults.standard.setValue(["rate": rate, "lastUpdate": lastUpdate], forKey: TodayDataStoreKey) + UserDefaults.standard.synchronize() + } + + static func getPriceRateAndLastUpdate() -> TodayDataStore? { + guard let dataStore = UserDefaults.standard.value(forKey: TodayDataStoreKey) as? [String: String], let rate = dataStore["rate"], let lastUpdate = dataStore["lastUpdate"] else { + return nil + } + return TodayDataStore(rate: rate, lastUpdate: lastUpdate) + } + + static func saveCachePriceRateAndLastUpdate(rate: String, lastUpdate: String) { + UserDefaults.standard.setValue(["rate": rate, "lastUpdate": lastUpdate], forKey: TodayCachedDataStoreKey) + UserDefaults.standard.synchronize() + } + + static func getCachedPriceRateAndLastUpdate() -> TodayDataStore? { + guard let dataStore = UserDefaults.standard.value(forKey: TodayCachedDataStoreKey) as? [String: String], var rate = dataStore["rate"], let lastUpdate = dataStore["lastUpdate"] else { + return nil + } + rate = rate.replacingOccurrences(of: ",", with: ""); + return TodayDataStore(rate: rate, lastUpdate: lastUpdate) + } + + + + +} diff --git a/ios/TodayExtension/TodayViewController.swift b/ios/TodayExtension/TodayViewController.swift new file mode 100644 index 00000000..576b2c0a --- /dev/null +++ b/ios/TodayExtension/TodayViewController.swift @@ -0,0 +1,130 @@ +// +// TodayViewController.swift +// TodayExtension +// +// Created by Marcos Rodriguez on 11/2/19. +// Copyright © 2019 Facebook. All rights reserved. +// + +import UIKit +import NotificationCenter + +class TodayViewController: UIViewController, NCWidgetProviding { + + + @IBOutlet weak var currencyLabel: UILabel! + @IBOutlet weak var lastUpdatedDate: UILabel! + @IBOutlet weak var priceLabel: UILabel! + + @IBOutlet weak var lastPriceArrowImage: UIImageView! + @IBOutlet weak var lastPrice: UILabel! + @IBOutlet weak var lastPriceFromLabel: UILabel! + private var lastPriceNumber: NSNumber? + + + override func viewDidLoad() { + super.viewDidLoad() + + setLastPriceOutletsHidden(isHidden: true) + if let lastStoredTodayStore = TodayData.getPriceRateAndLastUpdate() { + processRateAndLastUpdate(todayStore: lastStoredTodayStore) + } else { + setLastPriceOutletsHidden(isHidden: true) + } + } + + func setLastPriceOutletsHidden(isHidden: Bool) { + lastPrice.isHidden = isHidden + lastPriceFromLabel.isHidden = isHidden + lastPriceArrowImage.isHidden = isHidden + } + + func processRateAndLastUpdate(todayStore: TodayDataStore) { + guard let rateString = todayStore.formattedRate, let dateFormatted = todayStore.formattedDate else { return } + + priceLabel.text = rateString + lastUpdatedDate.text = dateFormatted + } + + func processStoredRateAndLastUpdate(todayStore: TodayDataStore) { + guard let lastPriceNumber = todayStore.formattedRate else { return } + + lastPrice.text = lastPriceNumber + } + + func processCachedStoredRateAndLastUpdate(new: TodayDataStore, cached: TodayDataStore) { + guard let newPriceDoubleValue = new.rateDoubleValue, let cachedPriceNumber = cached.formattedRate, let cachedPriceNumberDoubleValue = cached.rateDoubleValue else { return } + + lastPrice.text = cachedPriceNumber + + + if newPriceDoubleValue > cachedPriceNumberDoubleValue { + self.lastPriceArrowImage.image = UIImage(systemName: "arrow.up") + self.setLastPriceOutletsHidden(isHidden: false) + } else { + self.lastPriceArrowImage.image = UIImage(systemName: "arrow.down") + self.setLastPriceOutletsHidden(isHidden: false) + } + } + + func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) { + // Perform any setup necessary in order to update the view. + + // If an error is encountered, use NCUpdateResult.Failed + // If there's no update required, use NCUpdateResult.NoData + // If there's an update, use NCUpdateResult.NewData + let userPreferredCurrency = API.getUserPreferredCurrency(); + self.currencyLabel.text = userPreferredCurrency + API.fetchPrice(currency: userPreferredCurrency, completion: { (result, error) in + DispatchQueue.main.async { [unowned self] in + guard let result = result else { + completionHandler(.failed) + return + } + + guard let bpi = result["bpi"] as? Dictionary, let preferredCurrency = bpi[userPreferredCurrency] as? Dictionary, let rateString = preferredCurrency["rate"] as? String, + let time = result["time"] as? Dictionary, let lastUpdatedString = time["updatedISO"] as? String + else { + return + } + + let latestRateDataStore = TodayDataStore(rate: rateString, lastUpdate: lastUpdatedString) + + if let lastStoredTodayStore = TodayData.getPriceRateAndLastUpdate(), lastStoredTodayStore.lastUpdate == latestRateDataStore.lastUpdate, rateString == lastStoredTodayStore.rate, API.getLastSelectedCurrency() == userPreferredCurrency { + if let cached = TodayData.getCachedPriceRateAndLastUpdate() { + self.processCachedStoredRateAndLastUpdate(new: lastStoredTodayStore, cached: cached) + } else { + self.setLastPriceOutletsHidden(isHidden: true) + } + completionHandler(.noData) + } else { + self.processRateAndLastUpdate(todayStore: latestRateDataStore) + let priceRiceAndLastUpdate = TodayData.getPriceRateAndLastUpdate() + + if let rate = priceRiceAndLastUpdate?.rate, let lastUpdate = priceRiceAndLastUpdate?.lastUpdate { + TodayData.saveCachePriceRateAndLastUpdate(rate: rate, lastUpdate: lastUpdate); + } + + if let latestRateDataStore = latestRateDataStore.rateDoubleValue, let lastStoredPriceNumber = priceRiceAndLastUpdate?.rateDoubleValue, API.getLastSelectedCurrency() == userPreferredCurrency { + + if latestRateDataStore > lastStoredPriceNumber { + self.lastPriceArrowImage.image = UIImage(systemName: "arrow.up") + self.setLastPriceOutletsHidden(isHidden: false) + } else { + self.lastPriceArrowImage.image = UIImage(systemName: "arrow.down") + self.setLastPriceOutletsHidden(isHidden: false) + } + self.lastPrice.text = priceRiceAndLastUpdate?.formattedRate + } else { + self.setLastPriceOutletsHidden(isHidden: true) + } + + TodayData.savePriceRateAndLastUpdate(rate: latestRateDataStore.rate, lastUpdate: latestRateDataStore.lastUpdate) + API.saveNewSelectedCurrency() + completionHandler(.newData) + } + } + }) + } + +} diff --git a/package-lock.json b/package-lock.json index 790d72d4..3b855675 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12227,6 +12227,11 @@ } } }, + "react-native-default-preference": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/react-native-default-preference/-/react-native-default-preference-1.4.1.tgz", + "integrity": "sha512-UHX8Hsgq2AwEI8SA1IkjU+u6y+Uroht0sCP70UVLhEZWyqRnn8elrcEjRSInWFNUwxSeFF/c7Tnf17fWFHZEcw==" + }, "react-native-device-info": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-4.0.1.tgz", @@ -12331,6 +12336,11 @@ "qrcode": "^1.2.0" } }, + "react-native-quick-actions": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/react-native-quick-actions/-/react-native-quick-actions-0.3.12.tgz", + "integrity": "sha512-jQkzbA6L1/+FIqvHnPenHUe2IrBihh48KOMTOa0QbOaxWzsejSB8kkWpSQOjYxjGu7k+3DVgosQ8wGJTB/l4JA==" + }, "react-native-randombytes": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/react-native-randombytes/-/react-native-randombytes-3.5.3.tgz", @@ -12391,9 +12401,9 @@ "integrity": "sha512-J8Xl3mq0L9KDFtSYtKsQDAnZWw/niZIpAD1PRiNfZFHo44Rc+oS2bEIhskNnoQXKEgBNdPzCl/DenMXYAHXRYg==" }, "react-native-snap-carousel": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/react-native-snap-carousel/-/react-native-snap-carousel-3.8.0.tgz", - "integrity": "sha512-YtDZLv2Di77HfnH4yf+NyyYyISPNjrnjXDQLrzOkG9kkSlho/BROgHLQKGRLg3C2YSUMArFOePrb9CH3yeV5FA==", + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/react-native-snap-carousel/-/react-native-snap-carousel-3.8.4.tgz", + "integrity": "sha512-KMxLl75Tyf12DzeTCtV7xU0KuPZQQs/2+2iM90cgxcm3lrg4+hLXff5/61ynasBkzzJ5v4bTRq5CEy/qGUJVQw==", "requires": { "prop-types": "^15.6.1", "react-addons-shallow-compare": "15.6.2" diff --git a/package.json b/package.json index a385b2ba..d455a5ea 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "react-native": "0.60.5", "react-native-biometrics": "git+https://github.com/BlueWallet/react-native-biometrics.git", "react-native-camera": "3.4.0", + "react-native-default-preference": "1.4.1", "react-native-device-info": "4.0.1", "react-native-elements": "0.19.0", "react-native-flexi-radio-button": "0.2.2", @@ -106,11 +107,12 @@ "react-native-privacy-snapshot": "git+https://github.com/BlueWallet/react-native-privacy-snapshot.git", "react-native-prompt-android": "git+https://github.com/marcosrdz/react-native-prompt-android.git", "react-native-qrcode-svg": "5.1.2", + "react-native-quick-actions": "0.3.12", "react-native-randombytes": "3.5.3", "react-native-rate": "1.1.7", "react-native-secure-key-store": "git+https://github.com/marcosrdz/react-native-secure-key-store.git", "react-native-share": "2.0.0", - "react-native-snap-carousel": "3.8.0", + "react-native-snap-carousel": "3.8.4", "react-native-sortable-list": "0.0.23", "react-native-svg": "9.5.1", "react-native-tcp": "git+https://github.com/aprock/react-native-tcp.git", diff --git a/screen/wallets/list.js b/screen/wallets/list.js index 010f94b0..e3a6f1ab 100644 --- a/screen/wallets/list.js +++ b/screen/wallets/list.js @@ -5,8 +5,6 @@ import { Icon } from 'react-native-elements'; import { NavigationEvents } from 'react-navigation'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import PropTypes from 'prop-types'; -import WalletGradient from '../../class/walletGradient'; -import OnAppLaunch from '../../class/onAppLaunch'; let EV = require('../../events'); let A = require('../../analytics'); /** @type {AppStorage} */ @@ -51,13 +49,6 @@ export default class WalletsList extends Component { // the idea is that upon wallet launch we will refresh // all balances and all transactions here: InteractionManager.runAfterInteractions(async () => { - const isViewAllWalletsEnabled = await OnAppLaunch.isViewAllWalletsEnabled(); - if (!isViewAllWalletsEnabled) { - const selectedDefaultWallet = await OnAppLaunch.getSelectedDefaultWallet(); - const walletIndex = this.state.wallets.findIndex(wallet => wallet.getID() === selectedDefaultWallet.getID()); - this.handleClick(walletIndex); - } - let noErr = true; try { await BlueElectrum.waitTillConnected(); @@ -143,7 +134,7 @@ export default class WalletsList extends Component { if (wallet) { this.props.navigation.navigate('WalletTransactions', { wallet: wallet, - headerColor: WalletGradient.headerColorFor(wallet.type), + key: `WalletTransactions-${wallet.getID()}`, }); } else { // if its out of index - this must be last card with incentive to create wallet @@ -266,6 +257,7 @@ export default class WalletsList extends Component { > this.props.navigation.navigate('AddWallet')} /> { this.handleClick(index); diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index 0e706d5b..0b460577 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -26,6 +26,7 @@ import { BlueTransactionListItem, BlueWalletNavigationHeader, } from '../../BlueComponents'; +import WalletGradient from '../../class/walletGradient'; import { Icon } from 'react-native-elements'; import { LightningCustodianWallet } from '../../class'; import Handoff from 'react-native-handoff'; @@ -54,7 +55,7 @@ export default class WalletTransactions extends Component { ), headerStyle: { - backgroundColor: navigation.getParam('headerColor'), + backgroundColor: WalletGradient.headerColorFor(navigation.state.params.wallet.type), borderBottomWidth: 0, elevation: 0, shadowRadius: 0, @@ -354,29 +355,28 @@ export default class WalletTransactions extends Component { }; onWalletSelect = async wallet => { - NavigationService.navigate('WalletTransactions'); - /** @type {LightningCustodianWallet} */ - let toAddress = false; - if (this.state.wallet.refill_addressess.length > 0) { - toAddress = this.state.wallet.refill_addressess[0]; - } else { - try { - await this.state.wallet.fetchBtcAddress(); + if (wallet) { + NavigationService.navigate('WalletTransactions', { + key: `WalletTransactions-${wallet.getID()}`, + }); + /** @type {LightningCustodianWallet} */ + let toAddress = false; + if (this.state.wallet.refill_addressess.length > 0) { toAddress = this.state.wallet.refill_addressess[0]; - } catch (Err) { - return alert(Err.message); + } else { + try { + await this.state.wallet.fetchBtcAddress(); + toAddress = this.state.wallet.refill_addressess[0]; + } catch (Err) { + return alert(Err.message); + } } - } - - if (wallet) { this.props.navigation.navigate('SendDetails', { memo: loc.lnd.refill_lnd_balance, fromSecret: wallet.getSecret(), address: toAddress, fromWallet: wallet, }); - } else { - return alert('Internal error'); } }; diff --git a/tests/setup.js b/tests/setup.js index cd1071f6..967ee57b 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -5,3 +5,18 @@ jest.mock('react-native-watch-connectivity', () => { updateApplicationContext: jest.fn(), } }) + +jest.mock('react-native-quick-actions', () => { + return { + clearShortcutItems: jest.fn(), + setQuickActions: jest.fn(), + isSupported: jest.fn(), + } +}) + +jest.mock('react-native-default-preference', () => { + return { + setName: jest.fn(), + set: jest.fn(), + } +}) \ No newline at end of file