mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
REF: old watchOS app
This commit is contained in:
parent
cebdea6d25
commit
345565cd82
@ -1,246 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import {
|
||||
transferCurrentComplicationUserInfo,
|
||||
transferUserInfo,
|
||||
updateApplicationContext,
|
||||
useInstalled,
|
||||
useReachability,
|
||||
watchEvents,
|
||||
} from 'react-native-watch-connectivity';
|
||||
import Notifications from '../blue_modules/notifications';
|
||||
import { MultisigHDWallet } from '../class';
|
||||
import loc, { formatBalance, transactionTimeToReadable } from '../loc';
|
||||
import { Chain } from '../models/bitcoinUnits';
|
||||
import { FiatUnit } from '../models/fiatUnit';
|
||||
import { useSettings } from '../hooks/context/useSettings';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
|
||||
function WatchConnectivity() {
|
||||
const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk, txMetadata } = useStorage();
|
||||
const { preferredFiatCurrency } = useSettings();
|
||||
const isReachable = useReachability();
|
||||
const isInstalled = useInstalled(); // true | false
|
||||
const messagesListenerActive = useRef(false);
|
||||
const lastPreferredCurrency = useRef(FiatUnit.USD.endPointKey);
|
||||
|
||||
useEffect(() => {
|
||||
let messagesListener = () => {};
|
||||
if (isInstalled && isReachable && walletsInitialized && messagesListenerActive.current === false) {
|
||||
messagesListener = watchEvents.addListener('message', handleMessages);
|
||||
messagesListenerActive.current = true;
|
||||
} else {
|
||||
messagesListener();
|
||||
messagesListenerActive.current = false;
|
||||
}
|
||||
return () => {
|
||||
messagesListener();
|
||||
messagesListenerActive.current = false;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletsInitialized, isReachable, isInstalled]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`Apple Watch: isInstalled: ${isInstalled}, isReachable: ${isReachable}, walletsInitialized: ${walletsInitialized}`);
|
||||
if (isInstalled && walletsInitialized) {
|
||||
constructWalletsToSendToWatch().then(walletsToProcess => {
|
||||
if (walletsToProcess) {
|
||||
if (isReachable) {
|
||||
transferUserInfo(walletsToProcess);
|
||||
console.log('Apple Watch: sent info to watch transferUserInfo');
|
||||
} else {
|
||||
updateApplicationContext(walletsToProcess);
|
||||
console.log('Apple Watch: sent info to watch context');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [walletsInitialized, wallets, isReachable, isInstalled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (walletsInitialized && isReachable && isInstalled) {
|
||||
updateApplicationContext({ isWalletsInitialized: walletsInitialized, randomID: Math.floor(Math.random() * 11) });
|
||||
}
|
||||
}, [isInstalled, isReachable, walletsInitialized]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInstalled && isReachable && walletsInitialized && preferredFiatCurrency) {
|
||||
const preferredFiatCurrencyParsed = preferredFiatCurrency ?? FiatUnit.USD;
|
||||
try {
|
||||
if (lastPreferredCurrency.current !== preferredFiatCurrencyParsed.endPointKey) {
|
||||
transferCurrentComplicationUserInfo({
|
||||
preferredFiatCurrency: preferredFiatCurrencyParsed.endPointKey,
|
||||
});
|
||||
lastPreferredCurrency.current = preferredFiatCurrency.endPointKey;
|
||||
} else {
|
||||
console.log('WatchConnectivity lastPreferredCurrency has not changed');
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('WatchConnectivity useEffect preferredFiatCurrency error');
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}, [preferredFiatCurrency, walletsInitialized, isReachable, isInstalled]);
|
||||
|
||||
const handleMessages = (message, reply) => {
|
||||
if (message.request === 'createInvoice') {
|
||||
handleLightningInvoiceCreateRequest(message.walletIndex, message.amount, message.description)
|
||||
.then(createInvoiceRequest => reply({ invoicePaymentRequest: createInvoiceRequest }))
|
||||
.catch(e => {
|
||||
console.log(e);
|
||||
reply({});
|
||||
});
|
||||
} else if (message.message === 'sendApplicationContext') {
|
||||
constructWalletsToSendToWatch().then(walletsToProcess => {
|
||||
if (walletsToProcess) {
|
||||
updateApplicationContext(walletsToProcess);
|
||||
}
|
||||
});
|
||||
} else if (message.message === 'fetchTransactions') {
|
||||
fetchWalletTransactions()
|
||||
.then(() => saveToDisk())
|
||||
.finally(() => reply({}));
|
||||
} else if (message.message === 'hideBalance') {
|
||||
const walletIndex = message.walletIndex;
|
||||
const wallet = wallets[walletIndex];
|
||||
wallet.hideBalance = message.hideBalance;
|
||||
saveToDisk().finally(() => reply({}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleLightningInvoiceCreateRequest = async (walletIndex, amount, description = loc.lnd.placeholder) => {
|
||||
const wallet = wallets[walletIndex];
|
||||
if (wallet.allowReceive() && amount > 0) {
|
||||
try {
|
||||
const invoiceRequest = await wallet.addInvoice(amount, description);
|
||||
|
||||
// lets decode payreq and subscribe groundcontrol so we can receive push notification when our invoice is paid
|
||||
try {
|
||||
// Let's verify if notifications are already configured. Otherwise the watch app will freeze waiting for user approval in iOS app
|
||||
if (await Notifications.isNotificationsEnabled()) {
|
||||
const decoded = await wallet.decodeInvoice(invoiceRequest);
|
||||
Notifications.majorTomToGroundControl([], [decoded.payment_hash], []);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('WatchConnectivity - Running in Simulator');
|
||||
console.log(e);
|
||||
}
|
||||
return invoiceRequest;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const constructWalletsToSendToWatch = async () => {
|
||||
if (!Array.isArray(wallets)) {
|
||||
console.log('No Wallets set to sync with Watch app. Exiting...');
|
||||
return;
|
||||
}
|
||||
if (!walletsInitialized) {
|
||||
console.log('Wallets not initialized. Exiting...');
|
||||
return;
|
||||
}
|
||||
const walletsToProcess = [];
|
||||
|
||||
for (const wallet of wallets) {
|
||||
let receiveAddress;
|
||||
if (wallet.chain === Chain.ONCHAIN) {
|
||||
try {
|
||||
receiveAddress = await wallet.getAddressAsync();
|
||||
} catch (_) {}
|
||||
if (!receiveAddress) {
|
||||
// either sleep expired or getAddressAsync threw an exception
|
||||
receiveAddress = wallet._getExternalAddressByIndex(wallet.next_free_address_index);
|
||||
}
|
||||
} else if (wallet.chain === Chain.OFFCHAIN) {
|
||||
try {
|
||||
await wallet.getAddressAsync();
|
||||
receiveAddress = wallet.getAddress();
|
||||
} catch (_) {}
|
||||
if (!receiveAddress) {
|
||||
// either sleep expired or getAddressAsync threw an exception
|
||||
receiveAddress = wallet.getAddress();
|
||||
}
|
||||
}
|
||||
const transactions = wallet.getTransactions(10);
|
||||
const watchTransactions = [];
|
||||
for (const transaction of transactions) {
|
||||
let type = 'pendingConfirmation';
|
||||
let memo = '';
|
||||
let amount = 0;
|
||||
|
||||
if ('confirmations' in transaction && !(transaction.confirmations > 0)) {
|
||||
type = 'pendingConfirmation';
|
||||
} else if (transaction.type === 'user_invoice' || transaction.type === 'payment_request') {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise
|
||||
const invoiceExpiration = transaction.timestamp + transaction.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
type = 'pendingConfirmation';
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (transaction.ispaid) {
|
||||
type = 'received';
|
||||
} else {
|
||||
type = 'sent';
|
||||
}
|
||||
}
|
||||
} else if (transaction.value / 100000000 < 0) {
|
||||
type = 'sent';
|
||||
} else {
|
||||
type = 'received';
|
||||
}
|
||||
if (transaction.type === 'user_invoice' || transaction.type === 'payment_request') {
|
||||
amount = isNaN(transaction.value) ? '0' : amount;
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0; // eslint-disable-line no-bitwise
|
||||
const invoiceExpiration = transaction.timestamp + transaction.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString();
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (transaction.ispaid) {
|
||||
amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString();
|
||||
} else {
|
||||
amount = loc.lnd.expired;
|
||||
}
|
||||
} else {
|
||||
amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString();
|
||||
}
|
||||
} else {
|
||||
amount = formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString();
|
||||
}
|
||||
if (txMetadata[transaction.hash] && txMetadata[transaction.hash].memo) {
|
||||
memo = txMetadata[transaction.hash].memo;
|
||||
} else if (transaction.memo) {
|
||||
memo = transaction.memo;
|
||||
}
|
||||
const watchTX = { type, amount, memo, time: transactionTimeToReadable(transaction.received) };
|
||||
watchTransactions.push(watchTX);
|
||||
}
|
||||
|
||||
const walletInformation = {
|
||||
label: wallet.getLabel(),
|
||||
balance: formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true),
|
||||
type: wallet.type,
|
||||
preferredBalanceUnit: wallet.getPreferredBalanceUnit(),
|
||||
receiveAddress,
|
||||
transactions: watchTransactions,
|
||||
hideBalance: wallet.hideBalance,
|
||||
};
|
||||
if (wallet.chain === Chain.ONCHAIN && wallet.type !== MultisigHDWallet.type) {
|
||||
walletInformation.xpub = wallet.getXpub() ? wallet.getXpub() : wallet.getSecret();
|
||||
}
|
||||
if (wallet.allowBIP47() && wallet.isBIP47Enabled()) {
|
||||
walletInformation.paymentCode = wallet.getBIP47PaymentCode();
|
||||
}
|
||||
walletsToProcess.push(walletInformation);
|
||||
}
|
||||
return { wallets: walletsToProcess, randomID: Math.floor(Math.random() * 11) };
|
||||
};
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default WatchConnectivity;
|
172
components/WatchConnectivity.ios.tsx
Normal file
172
components/WatchConnectivity.ios.tsx
Normal file
@ -0,0 +1,172 @@
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import {
|
||||
sendMessage,
|
||||
sendMessageData,
|
||||
startFileTransfer,
|
||||
useReachability,
|
||||
useInstalled,
|
||||
watchEvents,
|
||||
transferUserInfo,
|
||||
updateApplicationContext,
|
||||
transferCurrentComplicationUserInfo,
|
||||
WatchMessage,
|
||||
} from 'react-native-watch-connectivity';
|
||||
import { useStorage } from '../hooks/context/useStorage';
|
||||
import { useSettings } from '../hooks/context/useSettings';
|
||||
import { LightningCustodianWallet } from '../class';
|
||||
import { LightningTransaction, Transaction, TWallet } from '../class/wallets/types';
|
||||
import { Chain } from '../models/bitcoinUnits';
|
||||
|
||||
const REQUEST_TYPES = {
|
||||
CREATE_INVOICE: 'createInvoice',
|
||||
FETCH_TRANSACTIONS: 'fetchTransactions',
|
||||
HIDE_BALANCE: 'hideBalance',
|
||||
};
|
||||
|
||||
interface WalletInfo {
|
||||
label: string;
|
||||
balance: number;
|
||||
type: string;
|
||||
preferredBalanceUnit: string;
|
||||
receiveAddress: string;
|
||||
transactions: Array<{
|
||||
amount: number;
|
||||
type: string;
|
||||
memo: string;
|
||||
time: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface MessageRequest {
|
||||
request: string;
|
||||
walletIndex: number;
|
||||
amount?: number;
|
||||
description?: string;
|
||||
hideBalance?: boolean;
|
||||
}
|
||||
|
||||
export default function WatchConnectivity() {
|
||||
const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk } = useStorage();
|
||||
const { preferredFiatCurrency } = useSettings();
|
||||
const isReachable = useReachability();
|
||||
const isInstalled = useInstalled();
|
||||
|
||||
const messagesListenerActive = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInstalled && isReachable && walletsInitialized) {
|
||||
syncWalletsToWatch();
|
||||
}
|
||||
|
||||
if (!messagesListenerActive.current) {
|
||||
const listener = watchEvents.addListener('message', handleMessages);
|
||||
messagesListenerActive.current = true;
|
||||
return () => {
|
||||
listener.remove();
|
||||
messagesListenerActive.current = false;
|
||||
};
|
||||
}
|
||||
}, [isInstalled, isReachable, walletsInitialized]);
|
||||
|
||||
const syncWalletsToWatch = useCallback(() => {
|
||||
const walletsToProcess: WalletInfo[] = wallets.map((wallet: TWallet | LightningCustodianWallet) => ({
|
||||
label: wallet.getLabel(),
|
||||
balance: wallet.getBalance(),
|
||||
type: wallet.type,
|
||||
preferredBalanceUnit: wallet.getPreferredBalanceUnit(),
|
||||
receiveAddress: wallet.chain === Chain.ONCHAIN? wallet.getAddress() : wallet.getPaymentCode(),
|
||||
transactions: wallet.getTransactions(10).map((transaction: Transaction & LightningTransaction) => ({
|
||||
amount: transaction.value,
|
||||
type: transaction.type,
|
||||
memo: transaction.memo,
|
||||
time: transaction.received,
|
||||
})),
|
||||
}));
|
||||
|
||||
const dataToSend = { wallets: walletsToProcess };
|
||||
|
||||
if (isReachable) {
|
||||
transferUserInfo(dataToSend);
|
||||
} else {
|
||||
try {
|
||||
updateApplicationContext(dataToSend);
|
||||
} catch (error) {
|
||||
console.error('Error updating application context:', error);
|
||||
}
|
||||
}
|
||||
}, [wallets, isReachable]);
|
||||
|
||||
const handleMessages = useCallback(
|
||||
async (message: WatchMessage<MessageRequest>, reply: (response: any) => void) => {
|
||||
if (!isReachable) {
|
||||
reply({});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.request) {
|
||||
case REQUEST_TYPES.CREATE_INVOICE: {
|
||||
const invoice = await createLightningInvoice(message.walletIndex, message.amount || 0, message.description || '');
|
||||
reply({ invoicePaymentRequest: invoice });
|
||||
break;
|
||||
}
|
||||
case REQUEST_TYPES.FETCH_TRANSACTIONS: {
|
||||
await fetchWalletTransactions();
|
||||
await saveToDisk();
|
||||
reply({});
|
||||
break;
|
||||
}
|
||||
case REQUEST_TYPES.HIDE_BALANCE: {
|
||||
handleHideBalance(message.walletIndex, message.hideBalance || false, reply);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
reply({});
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
[wallets, isReachable]
|
||||
);
|
||||
|
||||
const createLightningInvoice = async (walletIndex: number, amount: number, description: string) => {
|
||||
const wallet = wallets[walletIndex] as LightningCustodianWallet;
|
||||
if (!wallet || amount <= 0 || !wallet.allowReceive()) return '';
|
||||
|
||||
try {
|
||||
const invoice = await wallet.addInvoice(amount, description || 'Invoice');
|
||||
// @ts-ignore: fix later
|
||||
if (await Notifications.isNotificationsEnabled()) {
|
||||
const decoded = await wallet.decodeInvoice(invoice);
|
||||
// @ts-ignore: fix later
|
||||
Notifications.majorTomToGroundControl([], [decoded.payment_hash], []);
|
||||
}
|
||||
return invoice;
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleHideBalance = (walletIndex: number, hideBalance: boolean, reply: (response: any) => void) => {
|
||||
const wallet = wallets[walletIndex];
|
||||
if (wallet) {
|
||||
wallet.hideBalance = hideBalance;
|
||||
saveToDisk().then(() => reply({}));
|
||||
} else {
|
||||
reply({});
|
||||
}
|
||||
};
|
||||
|
||||
const syncPreferredFiatCurrency = useCallback(() => {
|
||||
if (preferredFiatCurrency) {
|
||||
transferCurrentComplicationUserInfo({ preferredFiatCurrency: preferredFiatCurrency.endPointKey });
|
||||
}
|
||||
}, [preferredFiatCurrency]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInstalled && preferredFiatCurrency) {
|
||||
syncPreferredFiatCurrency();
|
||||
}
|
||||
}, [isInstalled, preferredFiatCurrency, syncPreferredFiatCurrency]);
|
||||
|
||||
return null;
|
||||
}
|
@ -39,27 +39,36 @@ const allWalletsBalanceAndTransactionTime = async (
|
||||
if (!walletsInitialized || !(await isBalanceDisplayAllowed())) {
|
||||
return { allWalletsBalance: 0, latestTransactionTime: 0 };
|
||||
}
|
||||
|
||||
let balance = 0;
|
||||
let latestTransactionTime: number | string = 0;
|
||||
let latestConfirmedTransactionTime: number = 0;
|
||||
let onlyUnconfirmedTransaction = true;
|
||||
|
||||
for (const wallet of wallets) {
|
||||
if (wallet.hideBalance) continue;
|
||||
|
||||
balance += await wallet.getBalance();
|
||||
|
||||
const transactions: Transaction[] = await wallet.getTransactions();
|
||||
for (const transaction of transactions) {
|
||||
const transactionTime = await wallet.getLatestTransactionTimeEpoch();
|
||||
if (transaction.confirmations > 0 && transactionTime > Number(latestTransactionTime)) {
|
||||
latestTransactionTime = transactionTime;
|
||||
|
||||
if (transaction.confirmations > 0) {
|
||||
// If a confirmed transaction exists, set onlyUnconfirmedTransaction to false
|
||||
onlyUnconfirmedTransaction = false;
|
||||
if (transactionTime > latestConfirmedTransactionTime) {
|
||||
latestConfirmedTransactionTime = transactionTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (latestTransactionTime === 0 && transactions[0]?.confirmations === 0) {
|
||||
latestTransactionTime = WidgetCommunicationKeys.LatestTransactionIsUnconfirmed;
|
||||
// If no confirmed transactions exist and there is exactly one unconfirmed transaction
|
||||
if (onlyUnconfirmedTransaction && transactions.length === 1 && transactions[0]?.confirmations === 0) {
|
||||
return { allWalletsBalance: balance, latestTransactionTime: WidgetCommunicationKeys.LatestTransactionIsUnconfirmed };
|
||||
}
|
||||
}
|
||||
|
||||
return { allWalletsBalance: balance, latestTransactionTime };
|
||||
return { allWalletsBalance: balance, latestTransactionTime: latestConfirmedTransactionTime || 0 };
|
||||
};
|
||||
|
||||
const WidgetCommunication: React.FC = () => {
|
||||
|
@ -120,9 +120,11 @@
|
||||
B45010A62C1507DE00619044 /* CustomSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B45010A52C1507DE00619044 /* CustomSegmentedControlManager.m */; };
|
||||
B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */ = {isa = PBXBuildFile; fileRef = B4549F352B82B10D002E3153 /* ci_post_clone.sh */; };
|
||||
B461B852299599F800E431AA /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = B461B851299599F800E431AA /* AppDelegate.mm */; };
|
||||
B48A6A292C1DF01000030AB9 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B48A6A282C1DF01000030AB9 /* KeychainSwift */; };
|
||||
B4AB225D2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; };
|
||||
B4AB225E2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; };
|
||||
B4AB64512CC9F50200D55A9D /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB64502CC9F50200D55A9D /* KeychainManager.swift */; };
|
||||
B4AB64522CC9F50300D55A9D /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB64502CC9F50200D55A9D /* KeychainManager.swift */; };
|
||||
B4AB64532CC9F50300D55A9D /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB64502CC9F50200D55A9D /* KeychainManager.swift */; };
|
||||
B4B1A4622BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; };
|
||||
B4B1A4642BFA73110072E3BB /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */; };
|
||||
B4D0B2622C1DEA11006B6B1B /* ReceivePageInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2612C1DEA11006B6B1B /* ReceivePageInterfaceController.swift */; };
|
||||
@ -131,7 +133,7 @@
|
||||
B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */; };
|
||||
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
|
||||
B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EFF73A2C3F6C5E0095D655 /* MockData.swift */; };
|
||||
C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
|
||||
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -350,6 +352,7 @@
|
||||
B47B21EB2B2128B8001F6690 /* BlueWalletUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueWalletUITests.swift; sourceTree = "<group>"; };
|
||||
B49038D82B8FBAD300A8164A /* BlueWalletUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueWalletUITest.swift; sourceTree = "<group>"; };
|
||||
B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLParserDelegate.swift; sourceTree = "<group>"; };
|
||||
B4AB64502CC9F50200D55A9D /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = "<group>"; };
|
||||
B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetHelper.swift; sourceTree = "<group>"; };
|
||||
B4B31A352C77BBA000663334 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Interface.strings; sourceTree = "<group>"; };
|
||||
B4D0B2612C1DEA11006B6B1B /* ReceivePageInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceivePageInterfaceController.swift; sourceTree = "<group>"; };
|
||||
@ -381,7 +384,7 @@
|
||||
files = (
|
||||
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */,
|
||||
764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */,
|
||||
C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */,
|
||||
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */,
|
||||
17CDA0718F42DB2CE856C872 /* libPods-BlueWallet.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -415,7 +418,6 @@
|
||||
files = (
|
||||
B41B76852B66B2FF002C48D5 /* Bugsnag in Frameworks */,
|
||||
B41B76872B66B2FF002C48D5 /* BugsnagNetworkRequestPlugin in Frameworks */,
|
||||
B48A6A292C1DF01000030AB9 /* KeychainSwift in Frameworks */,
|
||||
6DFC807024EA0B6C007B8700 /* EFQRCode in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -732,6 +734,7 @@
|
||||
B450109A2C0FCD7E00619044 /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B4AB64502CC9F50200D55A9D /* KeychainManager.swift */,
|
||||
B450109B2C0FCD8A00619044 /* Utilities.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
@ -881,7 +884,6 @@
|
||||
6DFC806F24EA0B6C007B8700 /* EFQRCode */,
|
||||
B41B76842B66B2FF002C48D5 /* Bugsnag */,
|
||||
B41B76862B66B2FF002C48D5 /* BugsnagNetworkRequestPlugin */,
|
||||
B48A6A282C1DF01000030AB9 /* KeychainSwift */,
|
||||
);
|
||||
productName = "BlueWalletWatch Extension";
|
||||
productReference = B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */;
|
||||
@ -962,7 +964,6 @@
|
||||
packageReferences = (
|
||||
6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */,
|
||||
B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */,
|
||||
B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
||||
);
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
@ -1177,6 +1178,7 @@
|
||||
B461B852299599F800E431AA /* AppDelegate.mm in Sources */,
|
||||
B44033F42BCC377F00162242 /* WidgetData.swift in Sources */,
|
||||
B44033C42BCC332400162242 /* Balance.swift in Sources */,
|
||||
B4AB64512CC9F50200D55A9D /* KeychainManager.swift in Sources */,
|
||||
B44034072BCC38A000162242 /* FiatUnit.swift in Sources */,
|
||||
B44034002BCC37F800162242 /* Bundle+decode.swift in Sources */,
|
||||
B44033E22BCC36CB00162242 /* Placeholders.swift in Sources */,
|
||||
@ -1219,6 +1221,7 @@
|
||||
6DD410BA266CAF5C0087DE03 /* FiatUnit.swift in Sources */,
|
||||
B44033FB2BCC379200162242 /* WidgetDataStore.swift in Sources */,
|
||||
B44033EB2BCC371A00162242 /* MarketData.swift in Sources */,
|
||||
B4AB64522CC9F50300D55A9D /* KeychainManager.swift in Sources */,
|
||||
6DD410AF266CAF5C0087DE03 /* WalletInformationAndMarketWidget.swift in Sources */,
|
||||
B44033C62BCC332400162242 /* Balance.swift in Sources */,
|
||||
B44033E62BCC36FF00162242 /* WalletData.swift in Sources */,
|
||||
@ -1264,6 +1267,7 @@
|
||||
B40D4E632258425500428FCC /* ReceiveInterfaceController.swift in Sources */,
|
||||
B43D0378225847C500FBAA95 /* WalletGradient.swift in Sources */,
|
||||
B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */,
|
||||
B4AB64532CC9F50300D55A9D /* KeychainManager.swift in Sources */,
|
||||
B44033C02BCC32F800162242 /* BitcoinUnit.swift in Sources */,
|
||||
B44033E52BCC36FF00162242 /* WalletData.swift in Sources */,
|
||||
B44033EF2BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
|
||||
@ -2052,14 +2056,6 @@
|
||||
version = 6.28.1;
|
||||
};
|
||||
};
|
||||
B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/evgenyneu/keychain-swift.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 24.0.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
@ -2078,11 +2074,6 @@
|
||||
package = B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */;
|
||||
productName = BugsnagNetworkRequestPlugin;
|
||||
};
|
||||
B48A6A282C1DF01000030AB9 /* KeychainSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||
productName = KeychainSwift;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "52530e6b1e3a85c8854952ef703a6d1bbe1acd82713be2b3166476b9b277db23",
|
||||
"originHash" : "89509f555bc90a15b96ca0a326a69850770bdaac04a46f9cf482d81533702e3c",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "bugsnag-cocoa",
|
||||
@ -19,15 +19,6 @@
|
||||
"version" : "6.2.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "keychain-swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/evgenyneu/keychain-swift.git",
|
||||
"state" : {
|
||||
"revision" : "5e1b02b6a9dac2a759a1d5dbc175c86bd192a608",
|
||||
"version" : "24.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift_qrcodejs",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
@ -1,17 +1,16 @@
|
||||
//
|
||||
// ComplicationController.swift
|
||||
// T WatchKit Extension
|
||||
// BlueWalletWatch Extension
|
||||
//
|
||||
// Created by Marcos Rodriguez on 8/24/19.
|
||||
// Copyright © 2019 Marcos Rodriguez. All rights reserved.
|
||||
//
|
||||
|
||||
import ClockKit
|
||||
|
||||
|
||||
class ComplicationController: NSObject, CLKComplicationDataSource {
|
||||
|
||||
private let groupUserDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue)
|
||||
|
||||
// MARK: - Timeline Configuration
|
||||
|
||||
func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
|
||||
@ -22,12 +21,12 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
|
||||
handler(nil)
|
||||
}
|
||||
|
||||
@available(watchOSApplicationExtension 7.0, *)
|
||||
func complicationDescriptors() async -> [CLKComplicationDescriptor] {
|
||||
return [CLKComplicationDescriptor(
|
||||
identifier: "io.bluewallet.bluewallet",
|
||||
displayName: "Market Price",
|
||||
supportedFamilies: CLKComplicationFamily.allCases)]
|
||||
supportedFamilies: CLKComplicationFamily.allCases
|
||||
)]
|
||||
}
|
||||
|
||||
func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
|
||||
@ -42,269 +41,107 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
|
||||
|
||||
func getCurrentTimelineEntry(
|
||||
for complication: CLKComplication,
|
||||
withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void)
|
||||
{
|
||||
let marketData: WidgetDataStore? = groupUserDefaults?.codable(forKey: MarketData.string)
|
||||
withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void
|
||||
) {
|
||||
// Load market data from user defaults
|
||||
guard let marketData: WidgetDataStore = groupUserDefaults?.codable(forKey: MarketData.string) else {
|
||||
handler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
let date = marketData.date ?? Date()
|
||||
let valueLabel = marketData.formattedRateForComplication ?? "--"
|
||||
let valueSmallLabel = marketData.formattedRateForSmallComplication ?? "--"
|
||||
let currencySymbol = groupUserDefaults?.string(forKey: "preferredCurrency")
|
||||
.flatMap { fiatUnit(currency: $0)?.symbol } ?? fiatUnit(currency: "USD")!.symbol
|
||||
let timeLabel = marketData.formattedDate ?? "--"
|
||||
|
||||
let entry: CLKComplicationTimelineEntry
|
||||
let date: Date
|
||||
let valueLabel: String
|
||||
let valueSmallLabel: String
|
||||
let currencySymbol: String
|
||||
let timeLabel: String
|
||||
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 = groupUserDefaults?.string(forKey: "preferredCurrency"), let preferredFiatUnit = fiatUnit(currency: preferredFiatCurrency) {
|
||||
currencySymbol = preferredFiatUnit.symbol
|
||||
} else {
|
||||
currencySymbol = fiatUnit(currency: "USD")!.symbol
|
||||
}
|
||||
} else {
|
||||
valueLabel = "--"
|
||||
timeLabel = "--"
|
||||
valueSmallLabel = "--"
|
||||
currencySymbol = fiatUnit(currency: "USD")!.symbol
|
||||
date = Date()
|
||||
}
|
||||
|
||||
let line2Text = CLKSimpleTextProvider(text:currencySymbol)
|
||||
let line1SmallText = CLKSimpleTextProvider(text: valueSmallLabel)
|
||||
|
||||
// Handle different complication families
|
||||
switch complication.family {
|
||||
case .circularSmall:
|
||||
let template = CLKComplicationTemplateCircularSmallStackText()
|
||||
template.line1TextProvider = line1SmallText
|
||||
template.line2TextProvider = line2Text
|
||||
let template = CLKComplicationTemplateCircularSmallStackText(
|
||||
line1TextProvider: CLKSimpleTextProvider(text: valueSmallLabel),
|
||||
line2TextProvider: CLKSimpleTextProvider(text: currencySymbol)
|
||||
)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
|
||||
case .utilitarianSmallFlat:
|
||||
let template = CLKComplicationTemplateUtilitarianSmallFlat()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueSmallLabel)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
let template = CLKComplicationTemplateUtilitarianSmallFlat(
|
||||
textProvider: CLKTextProvider(format: "%@%@", currencySymbol, valueSmallLabel)
|
||||
)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
|
||||
case .utilitarianSmall:
|
||||
let template = CLKComplicationTemplateUtilitarianSmallRingImage()
|
||||
template.imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Utilitarian")!)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
case .graphicCircular:
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
let template = CLKComplicationTemplateGraphicCircularStackText()
|
||||
template.line1TextProvider = line1SmallText
|
||||
template.line2TextProvider = line2Text
|
||||
let template = CLKComplicationTemplateGraphicCircularStackText(
|
||||
line1TextProvider: CLKSimpleTextProvider(text: valueSmallLabel),
|
||||
line2TextProvider: CLKSimpleTextProvider(text: currencySymbol)
|
||||
)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
|
||||
case .modularSmall:
|
||||
let template = CLKComplicationTemplateModularSmallStackText()
|
||||
template.line1TextProvider = line1SmallText
|
||||
template.line2TextProvider = line2Text
|
||||
let template = CLKComplicationTemplateModularSmallStackText(
|
||||
line1TextProvider: CLKSimpleTextProvider(text: valueSmallLabel),
|
||||
line2TextProvider: CLKSimpleTextProvider(text: currencySymbol)
|
||||
)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
|
||||
case .graphicCorner:
|
||||
let template = CLKComplicationTemplateGraphicCornerStackText()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.outerTextProvider = CLKTextProvider(format: "%@", valueSmallLabel)
|
||||
template.innerTextProvider = CLKTextProvider(format: "%@", currencySymbol)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
let template = CLKComplicationTemplateGraphicCornerStackText(
|
||||
innerTextProvider: CLKSimpleTextProvider(text: valueSmallLabel), // Inner text first
|
||||
outerTextProvider: CLKSimpleTextProvider(text: currencySymbol) // Outer text second
|
||||
)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
case .graphicBezel:
|
||||
let template = CLKComplicationTemplateGraphicBezelCircularText()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueSmallLabel)
|
||||
let imageProvider = CLKFullColorImageProvider(fullColorImage: UIImage(named: "Complication/Graphic Bezel")!)
|
||||
let circularTemplate = CLKComplicationTemplateGraphicCircularImage()
|
||||
circularTemplate.imageProvider = imageProvider
|
||||
template.circularTemplate = circularTemplate
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
} else {
|
||||
|
||||
default:
|
||||
handler(nil)
|
||||
}
|
||||
case .utilitarianLarge:
|
||||
if #available(watchOSApplicationExtension 7.0, *) {
|
||||
let textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
|
||||
let template = CLKComplicationTemplateUtilitarianLargeFlat(textProvider: textProvider)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
case .modularLarge:
|
||||
let template = CLKComplicationTemplateModularLargeStandardBody()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.headerTextProvider = CLKTextProvider(format: "Bitcoin Price")
|
||||
template.body1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
|
||||
template.body2TextProvider = CLKTextProvider(format: "at %@", timeLabel)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
case .extraLarge:
|
||||
let template = CLKComplicationTemplateExtraLargeStackText()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.line1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
|
||||
template.line2TextProvider = CLKTextProvider(format: "at %@", timeLabel)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
case .graphicRectangular:
|
||||
let template = CLKComplicationTemplateGraphicRectangularStandardBody()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.headerTextProvider = CLKTextProvider(format: "Bitcoin Price")
|
||||
template.body1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
|
||||
template.body2TextProvider = CLKTextProvider(format: "at %@", timeLabel)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
case .graphicExtraLarge:
|
||||
if #available(watchOSApplicationExtension 7.0, *) {
|
||||
let template = CLKComplicationTemplateGraphicExtraLargeCircularStackText()
|
||||
template.line1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel)
|
||||
template.line1TextProvider = CLKTextProvider(format: "at %@", timeLabel)
|
||||
entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
|
||||
handler(entry)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
handler(entry)
|
||||
}
|
||||
|
||||
// MARK: - Timeline Entries
|
||||
|
||||
func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
|
||||
// Call the handler with the timeline entries prior to the given date
|
||||
handler(nil)
|
||||
}
|
||||
|
||||
func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
|
||||
// Call the handler with the timeline entries after to the given date
|
||||
handler(nil)
|
||||
}
|
||||
|
||||
// MARK: - Placeholder Templates
|
||||
|
||||
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
|
||||
// This method will be called once per supported complication, and the results will be cached
|
||||
let line1Text = CLKSimpleTextProvider(text: "46 K")
|
||||
let line2Text = CLKSimpleTextProvider(text: "$")
|
||||
let lineTimeText = CLKSimpleTextProvider(text:"3:40 PM")
|
||||
|
||||
// Provide sample template for different complication families
|
||||
switch complication.family {
|
||||
case .circularSmall:
|
||||
let template = CLKComplicationTemplateCircularSmallStackText()
|
||||
template.line1TextProvider = line1Text
|
||||
template.line2TextProvider = line2Text
|
||||
let template = CLKComplicationTemplateCircularSmallStackText(
|
||||
line1TextProvider: line1Text,
|
||||
line2TextProvider: line2Text
|
||||
)
|
||||
handler(template)
|
||||
|
||||
case .utilitarianSmallFlat:
|
||||
let template = CLKComplicationTemplateUtilitarianSmallFlat()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.textProvider = CLKTextProvider(format: "%@", "$46,134")
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
handler(template)
|
||||
case .utilitarianSmall:
|
||||
let template = CLKComplicationTemplateUtilitarianSmallRingImage()
|
||||
template.imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Utilitarian")!)
|
||||
handler(template)
|
||||
case .graphicCircular:
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
let template = CLKComplicationTemplateGraphicCircularStackText()
|
||||
template.line1TextProvider = line1Text
|
||||
template.line2TextProvider = line2Text
|
||||
handler(template)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
case .graphicCorner:
|
||||
let template = CLKComplicationTemplateGraphicCornerStackText()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.outerTextProvider = CLKTextProvider(format: "46,134")
|
||||
template.innerTextProvider = CLKTextProvider(format: "$")
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
let template = CLKComplicationTemplateUtilitarianSmallFlat(
|
||||
textProvider: CLKTextProvider(format: "$46,134")
|
||||
)
|
||||
handler(template)
|
||||
|
||||
case .modularSmall:
|
||||
let template = CLKComplicationTemplateModularSmallStackText()
|
||||
template.line1TextProvider = line1Text
|
||||
template.line2TextProvider = line2Text
|
||||
let template = CLKComplicationTemplateModularSmallStackText(
|
||||
line1TextProvider: line1Text,
|
||||
line2TextProvider: line2Text
|
||||
)
|
||||
handler(template)
|
||||
case .utilitarianLarge:
|
||||
if #available(watchOSApplicationExtension 7.0, *) {
|
||||
let textProvider = CLKTextProvider(format: "%@%@", "$", "46,000")
|
||||
let template = CLKComplicationTemplateUtilitarianLargeFlat(textProvider: textProvider)
|
||||
handler(template)
|
||||
} else {
|
||||
|
||||
default:
|
||||
handler(nil)
|
||||
}
|
||||
case .graphicBezel:
|
||||
let template = CLKComplicationTemplateGraphicBezelCircularText()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.textProvider = CLKTextProvider(format: "%@%@", "$S", "46,000")
|
||||
let imageProvider = CLKFullColorImageProvider(fullColorImage: UIImage(named: "Complication/Graphic Bezel")!)
|
||||
let circularTemplate = CLKComplicationTemplateGraphicCircularImage()
|
||||
circularTemplate.imageProvider = imageProvider
|
||||
template.circularTemplate = circularTemplate
|
||||
handler(template)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
case .modularLarge:
|
||||
let template = CLKComplicationTemplateModularLargeStandardBody()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.headerTextProvider = CLKTextProvider(format: "Bitcoin Price")
|
||||
template.body1TextProvider = CLKTextProvider(format: "%@%@", "$S", "46,000")
|
||||
template.body2TextProvider = lineTimeText
|
||||
handler(template)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
case .extraLarge:
|
||||
let template = CLKComplicationTemplateExtraLargeStackText()
|
||||
template.line1TextProvider = line1Text
|
||||
template.line2TextProvider = lineTimeText
|
||||
handler(template)
|
||||
case .graphicRectangular:
|
||||
let template = CLKComplicationTemplateGraphicRectangularStandardBody()
|
||||
if #available(watchOSApplicationExtension 6.0, *) {
|
||||
template.headerTextProvider = CLKTextProvider(format: "Bitcoin Price")
|
||||
template.body1TextProvider = CLKTextProvider(format: "%@%@", "$S", "46,000")
|
||||
template.body2TextProvider = CLKTextProvider(format: "%@", Date().description)
|
||||
handler(template)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
case .graphicExtraLarge:
|
||||
if #available(watchOSApplicationExtension 7.0, *) {
|
||||
let template = CLKComplicationTemplateGraphicExtraLargeCircularStackText()
|
||||
template.line1TextProvider = line1Text
|
||||
template.line2TextProvider = line2Text
|
||||
handler(template)
|
||||
} else {
|
||||
handler(nil)
|
||||
}
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
// BlueWalletWatch Extension
|
||||
//
|
||||
// Created by Marcos Rodriguez on 3/6/19.
|
||||
|
||||
//
|
||||
|
||||
import WatchKit
|
||||
@ -17,6 +16,8 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate {
|
||||
func applicationDidFinishLaunching() {
|
||||
scheduleNextReload()
|
||||
updatePreferredFiatCurrency()
|
||||
|
||||
// Initialize Bugsnag based on user preference
|
||||
if let isDoNotTrackEnabled = groupUserDefaults?.bool(forKey: "donottrack"), !isDoNotTrackEnabled {
|
||||
Bugsnag.start()
|
||||
}
|
||||
@ -28,7 +29,9 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate {
|
||||
}
|
||||
|
||||
private func fetchPreferredFiatUnit() -> FiatUnit? {
|
||||
if let preferredFiatCurrency = groupUserDefaults?.string(forKey: "preferredCurrency"), let preferredFiatUnit = fiatUnit(currency: preferredFiatCurrency) {
|
||||
// Fetch the preferred fiat currency unit from user defaults, default to USD
|
||||
if let preferredFiatCurrency = groupUserDefaults?.string(forKey: "preferredCurrency"),
|
||||
let preferredFiatUnit = fiatUnit(currency: preferredFiatCurrency) {
|
||||
return preferredFiatUnit
|
||||
} else {
|
||||
return fiatUnit(currency: "USD")
|
||||
@ -37,10 +40,17 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate {
|
||||
|
||||
private func updateMarketData(for fiatUnit: FiatUnit) {
|
||||
MarketAPI.fetchPrice(currency: fiatUnit.endPointKey) { (data, error) in
|
||||
guard let data = data, let encodedData = try? PropertyListEncoder().encode(data) else { return }
|
||||
guard let data = data, let encodedData = try? PropertyListEncoder().encode(data) else {
|
||||
print("Failed to fetch market data or encode it: \(String(describing: error))")
|
||||
return
|
||||
}
|
||||
|
||||
// Store the market data in user defaults
|
||||
let groupUserDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue)
|
||||
groupUserDefaults?.set(encodedData, forKey: MarketData.string)
|
||||
groupUserDefaults?.synchronize()
|
||||
|
||||
// Reload complications to reflect the updated market data
|
||||
ExtensionDelegate.reloadActiveComplications()
|
||||
}
|
||||
}
|
||||
@ -53,6 +63,7 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate {
|
||||
}
|
||||
|
||||
func nextReloadTime(after date: Date) -> Date {
|
||||
// Calculate the next reload time (every 10 minutes)
|
||||
let calendar = Calendar(identifier: .gregorian)
|
||||
return calendar.date(byAdding: .minute, value: 10, to: date)!
|
||||
}
|
||||
@ -86,5 +97,4 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate {
|
||||
updateMarketData(for: fiatUnitUserDefaults)
|
||||
backgroundTask.setTaskCompletedWithSnapshot(false)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,11 +30,6 @@ class InterfaceController: WKInterfaceController, WCSessionDelegate {
|
||||
WCSession.default.activate()
|
||||
}
|
||||
|
||||
private func processContextData(_ context: Any?) {
|
||||
guard let contextUnwrapped = context as? [String: Any] else { return }
|
||||
WatchDataSource.shared.processData(data: contextUnwrapped)
|
||||
}
|
||||
|
||||
@objc private func updateUI() {
|
||||
let wallets = WatchDataSource.shared.wallets
|
||||
let isEmpty = wallets.isEmpty
|
||||
@ -59,23 +54,26 @@ class InterfaceController: WKInterfaceController, WCSessionDelegate {
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
|
||||
print("Received application context data:", applicationContext)
|
||||
WatchDataSource.shared.processData(data: applicationContext)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
|
||||
print("Received user info:", userInfo)
|
||||
WatchDataSource.shared.processData(data: userInfo)
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
||||
if activationState == .activated {
|
||||
WatchDataSource.shared.loadKeychainData()
|
||||
print("Watch session activated successfully.")
|
||||
WatchDataSource.shared.loadWalletsData()
|
||||
} else if let error = error {
|
||||
print("Watch session activation failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
|
||||
print("Received message:", message)
|
||||
WatchDataSource.shared.processData(data: message)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,22 +1,17 @@
|
||||
//
|
||||
// Wallet.swift
|
||||
// Transaction.swift
|
||||
// BlueWalletWatch Extension
|
||||
//
|
||||
// Created by Marcos Rodriguez on 3/13/19.
|
||||
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Transaction: NSObject, NSSecureCoding {
|
||||
static var supportsSecureCoding: Bool = true
|
||||
|
||||
static let identifier: String = "Transaction"
|
||||
|
||||
struct Transaction: Codable {
|
||||
let time: String
|
||||
let memo: String
|
||||
let amount: String
|
||||
let type: String
|
||||
let amount: String
|
||||
|
||||
init(time: String, memo: String, type: String, amount: String) {
|
||||
self.time = time
|
||||
@ -24,18 +19,4 @@ class Transaction: NSObject, NSSecureCoding {
|
||||
self.type = type
|
||||
self.amount = amount
|
||||
}
|
||||
|
||||
func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(time, forKey: "time")
|
||||
aCoder.encode(memo, forKey: "memo")
|
||||
aCoder.encode(type, forKey: "type")
|
||||
aCoder.encode(amount, forKey: "amount")
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
time = aDecoder.decodeObject(forKey: "time") as! String
|
||||
memo = aDecoder.decodeObject(forKey: "memo") as! String
|
||||
amount = aDecoder.decodeObject(forKey: "amount") as! String
|
||||
type = aDecoder.decodeObject(forKey: "type") as! String
|
||||
}
|
||||
}
|
||||
|
@ -3,18 +3,12 @@
|
||||
// BlueWalletWatch Extension
|
||||
//
|
||||
// Created by Marcos Rodriguez on 3/13/19.
|
||||
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Wallet: NSObject, NSSecureCoding {
|
||||
|
||||
static var supportsSecureCoding: Bool = true
|
||||
|
||||
static let identifier: String = "Wallet"
|
||||
|
||||
var identifier: Int?
|
||||
struct Wallet: Codable {
|
||||
let identifier: String
|
||||
let label: String
|
||||
let balance: String
|
||||
let type: String
|
||||
@ -25,7 +19,7 @@ class Wallet: NSObject, NSSecureCoding {
|
||||
let hideBalance: Bool
|
||||
let paymentCode: String?
|
||||
|
||||
init(label: String, balance: String, type: String, preferredBalanceUnit: String, receiveAddress: String, transactions: [Transaction], identifier: Int, xpub: String?, hideBalance: Bool = false, paymentCode: String?) {
|
||||
init(label: String, balance: String, type: String, preferredBalanceUnit: String, receiveAddress: String, transactions: [Transaction], identifier: String, xpub: String?, hideBalance: Bool = false, paymentCode: String?) {
|
||||
self.label = label
|
||||
self.balance = balance
|
||||
self.type = type
|
||||
@ -37,30 +31,4 @@ class Wallet: NSObject, NSSecureCoding {
|
||||
self.hideBalance = hideBalance
|
||||
self.paymentCode = paymentCode
|
||||
}
|
||||
|
||||
func encode(with aCoder: NSCoder) {
|
||||
aCoder.encode(label, forKey: "label")
|
||||
aCoder.encode(balance, forKey: "balance")
|
||||
aCoder.encode(type, forKey: "type")
|
||||
aCoder.encode(receiveAddress, forKey: "receiveAddress")
|
||||
aCoder.encode(preferredBalanceUnit, forKey: "preferredBalanceUnit")
|
||||
aCoder.encode(transactions, forKey: "transactions")
|
||||
aCoder.encode(identifier, forKey: "identifier")
|
||||
aCoder.encode(xpub, forKey: "xpub")
|
||||
aCoder.encode(hideBalance, forKey: "hideBalance")
|
||||
aCoder.encode(paymentCode, forKey: "paymentCode")
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
label = aDecoder.decodeObject(forKey: "label") as! String
|
||||
balance = aDecoder.decodeObject(forKey: "balance") as! String
|
||||
type = aDecoder.decodeObject(forKey: "type") as! String
|
||||
preferredBalanceUnit = aDecoder.decodeObject(forKey: "preferredBalanceUnit") as! String
|
||||
receiveAddress = aDecoder.decodeObject(forKey: "receiveAddress") as! String
|
||||
transactions = aDecoder.decodeObject(forKey: "transactions") as? [Transaction] ?? [Transaction]()
|
||||
xpub = aDecoder.decodeObject(forKey: "xpub") as? String
|
||||
hideBalance = aDecoder.decodeObject(forKey: "hideBalance") as? Bool ?? false
|
||||
paymentCode = aDecoder.decodeObject(forKey: "paymentCode") as? String
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,67 +3,106 @@
|
||||
// BlueWalletWatch Extension
|
||||
//
|
||||
// Created by Marcos Rodriguez on 3/20/19.
|
||||
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import WatchConnectivity
|
||||
import KeychainSwift
|
||||
|
||||
class WatchDataSource: NSObject {
|
||||
|
||||
struct NotificationName {
|
||||
static let dataUpdated = Notification.Name(rawValue: "Notification.WalletDataSource.Updated")
|
||||
}
|
||||
|
||||
struct Notifications {
|
||||
static let dataUpdated = Notification(name: NotificationName.dataUpdated)
|
||||
}
|
||||
|
||||
static let shared = WatchDataSource()
|
||||
var wallets: [Wallet] = [Wallet]()
|
||||
var companionWalletsInitialized = false
|
||||
private let keychain = KeychainSwift()
|
||||
let groupUserDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue)
|
||||
static let groupUserDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue)
|
||||
|
||||
// Use a constant for the keychain identifier
|
||||
private static let walletKeychainIdentifier = "WalletKeychainData"
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
loadKeychainData()
|
||||
loadWalletsData()
|
||||
}
|
||||
|
||||
func loadKeychainData() {
|
||||
if let existingData = keychain.getData(Wallet.identifier), let walletData = try? NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClass: Wallet.self, from: existingData) {
|
||||
guard walletData != self.wallets else { return }
|
||||
// Load wallet data from Keychain or other secure storage using Codable
|
||||
func loadWalletsData() {
|
||||
if let existingData = KeychainManager.shared.getData(forKey: WatchDataSource.walletKeychainIdentifier) { // Use a static key
|
||||
do {
|
||||
let walletData = try JSONDecoder().decode([Wallet].self, from: existingData)
|
||||
wallets = walletData
|
||||
WatchDataSource.postDataUpdatedNotification()
|
||||
} catch {
|
||||
print("Failed to decode wallets from Keychain: \(error)")
|
||||
}
|
||||
} else {
|
||||
print("No data found in Keychain")
|
||||
}
|
||||
}
|
||||
|
||||
// Process wallet data received from the iPhone app via WatchConnectivity
|
||||
func processWalletsData(walletsInfo: [String: Any]) {
|
||||
if let walletsToProcess = walletsInfo["wallets"] as? [[String: Any]] {
|
||||
wallets.removeAll();
|
||||
wallets.removeAll()
|
||||
|
||||
for (index, entry) in walletsToProcess.enumerated() {
|
||||
guard let label = entry["label"] as? String, let balance = entry["balance"] as? String, let type = entry["type"] as? String, let preferredBalanceUnit = entry["preferredBalanceUnit"] as? String, let transactions = entry["transactions"] as? [[String: Any]], let paymentCode = entry["paymentCode"] as? String else {
|
||||
guard let label = entry["label"] as? String,
|
||||
let balance = entry["balance"] as? String,
|
||||
let type = entry["type"] as? String,
|
||||
let preferredBalanceUnit = entry["preferredBalanceUnit"] as? String,
|
||||
let transactions = entry["transactions"] as? [[String: Any]] else {
|
||||
print("Invalid wallet data")
|
||||
continue
|
||||
}
|
||||
|
||||
var transactionsProcessed = [Transaction]()
|
||||
for transactionEntry in transactions {
|
||||
guard let time = transactionEntry["time"] as? String, let memo = transactionEntry["memo"] as? String, let amount = transactionEntry["amount"] as? String, let type = transactionEntry["type"] as? String else { continue }
|
||||
guard let time = transactionEntry["time"] as? String,
|
||||
let memo = transactionEntry["memo"] as? String,
|
||||
let amount = transactionEntry["amount"] as? String,
|
||||
let type = transactionEntry["type"] as? String else {
|
||||
print("Invalid transaction data")
|
||||
continue
|
||||
}
|
||||
let transaction = Transaction(time: time, memo: memo, type: type, amount: amount)
|
||||
transactionsProcessed.append(transaction)
|
||||
}
|
||||
|
||||
let receiveAddress = entry["receiveAddress"] as? String ?? ""
|
||||
let xpub = entry["xpub"] as? String ?? ""
|
||||
let hideBalance = entry["hideBalance"] as? Bool ?? false
|
||||
let wallet = Wallet(label: label, balance: balance, type: type, preferredBalanceUnit: preferredBalanceUnit, receiveAddress: receiveAddress, transactions: transactionsProcessed, identifier: index, xpub: xpub, hideBalance: hideBalance, paymentCode: paymentCode)
|
||||
let paymentCode = entry["paymentCode"] as? String ?? ""
|
||||
|
||||
let wallet = Wallet(
|
||||
label: label,
|
||||
balance: balance,
|
||||
type: type,
|
||||
preferredBalanceUnit: preferredBalanceUnit,
|
||||
receiveAddress: receiveAddress,
|
||||
transactions: transactionsProcessed,
|
||||
identifier: String(index), // Use index as the identifier here
|
||||
xpub: xpub,
|
||||
hideBalance: hideBalance,
|
||||
paymentCode: paymentCode
|
||||
)
|
||||
wallets.append(wallet)
|
||||
}
|
||||
|
||||
if let walletsArchived = try? NSKeyedArchiver.archivedData(withRootObject: wallets, requiringSecureCoding: false) {
|
||||
keychain.set(walletsArchived, forKey: Wallet.identifier)
|
||||
}
|
||||
// Save the updated wallets back to Keychain using Codable
|
||||
do {
|
||||
let encodedWallets = try JSONEncoder().encode(wallets)
|
||||
KeychainManager.shared.set(encodedWallets, forKey: WatchDataSource.walletKeychainIdentifier) // Use the static key
|
||||
WatchDataSource.postDataUpdatedNotification()
|
||||
} catch {
|
||||
print("Failed to encode and save wallets: \(error)")
|
||||
}
|
||||
} else {
|
||||
print("Invalid wallets data received")
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,55 +110,66 @@ class WatchDataSource: NSObject {
|
||||
NotificationCenter.default.post(Notifications.dataUpdated)
|
||||
}
|
||||
|
||||
// Request a Lightning invoice from the iPhone companion app
|
||||
static func requestLightningInvoice(walletIdentifier: Int, amount: Double, description: String?, responseHandler: @escaping (_ invoice: String) -> Void) {
|
||||
guard WatchDataSource.shared.wallets.count > walletIdentifier else {
|
||||
responseHandler("")
|
||||
return
|
||||
}
|
||||
WCSession.default.sendMessage(["request": "createInvoice", "walletIndex": walletIdentifier, "amount": amount, "description": description ?? ""], replyHandler: { (reply: [String : Any]) in
|
||||
print("Requesting Lightning invoice from companion app for wallet index \(walletIdentifier) with amount \(amount)")
|
||||
|
||||
WCSession.default.sendMessage(
|
||||
["request": "createInvoice", "walletIndex": String(walletIdentifier), "amount": amount, "description": description ?? ""], // Convert walletIdentifier to String
|
||||
replyHandler: { (reply: [String: Any]) in
|
||||
if let invoicePaymentRequest = reply["invoicePaymentRequest"] as? String, !invoicePaymentRequest.isEmpty {
|
||||
print("Received Lightning invoice: \(invoicePaymentRequest)")
|
||||
responseHandler(invoicePaymentRequest)
|
||||
} else {
|
||||
print("Invalid invoice received or empty response")
|
||||
responseHandler("")
|
||||
}
|
||||
}) { (error) in
|
||||
print(error)
|
||||
},
|
||||
errorHandler: { (error) in
|
||||
print("Error requesting Lightning invoice: \(error)")
|
||||
responseHandler("")
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
static func toggleWalletHideBalance(walletIdentifier: Int, hideBalance: Bool, responseHandler: @escaping (_ invoice: String) -> Void) {
|
||||
guard WatchDataSource.shared.wallets.count > walletIdentifier else {
|
||||
// Toggle the wallet hide balance option and send a message to the iPhone companion app
|
||||
static func toggleWalletHideBalance(walletIdentifier: String, hideBalance: Bool, responseHandler: @escaping (_ result: String) -> Void) {
|
||||
guard WatchDataSource.shared.wallets.count > Int(walletIdentifier)! else {
|
||||
responseHandler("")
|
||||
return
|
||||
}
|
||||
WCSession.default.sendMessage(["message": "hideBalance", "walletIndex": walletIdentifier, "hideBalance": hideBalance], replyHandler: { (reply: [String : Any]) in
|
||||
responseHandler("")
|
||||
}) { (error) in
|
||||
print(error)
|
||||
responseHandler("")
|
||||
print("Toggling hide balance for wallet index \(walletIdentifier) to \(hideBalance)")
|
||||
|
||||
WCSession.default.sendMessage(
|
||||
["message": "hideBalance", "walletIndex": String(walletIdentifier), "hideBalance": hideBalance], // Convert walletIdentifier to String
|
||||
replyHandler: { _ in
|
||||
print("Successfully toggled hide balance")
|
||||
responseHandler("")
|
||||
},
|
||||
errorHandler: { (error) in
|
||||
print("Error toggling hide balance: \(error)")
|
||||
responseHandler("")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Process the data received from the iPhone companion app, including fiat currency and wallet data
|
||||
func processData(data: [String: Any]) {
|
||||
if let preferredFiatCurrency = data["preferredFiatCurrency"] as? String,
|
||||
let preferredFiatCurrencyUnit = fiatUnit(currency: preferredFiatCurrency) {
|
||||
WatchDataSource.groupUserDefaults?.set(preferredFiatCurrencyUnit.endPointKey, forKey: "preferredCurrency")
|
||||
WatchDataSource.groupUserDefaults?.synchronize()
|
||||
|
||||
if let preferredFiatCurrency = data["preferredFiatCurrency"] as? String, let preferredFiatCurrencyUnit = fiatUnit(currency: preferredFiatCurrency) {
|
||||
groupUserDefaults?.set(preferredFiatCurrencyUnit.endPointKey, forKey: "preferredCurrency")
|
||||
groupUserDefaults?.synchronize()
|
||||
|
||||
// Create an instance of ExtensionDelegate and call updatePreferredFiatCurrency()
|
||||
let extensionDelegate = ExtensionDelegate()
|
||||
extensionDelegate.updatePreferredFiatCurrency()
|
||||
|
||||
} else if let isWalletsInitialized = data["isWalletsInitialized"] as? Bool {
|
||||
companionWalletsInitialized = isWalletsInitialized
|
||||
NotificationCenter.default.post(Notifications.dataUpdated)
|
||||
print("Updated preferred fiat currency to \(preferredFiatCurrency)")
|
||||
} else {
|
||||
WatchDataSource.shared.processWalletsData(walletsInfo: data)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -89,14 +89,8 @@ class SpecifyInterfaceController: WKInterfaceController {
|
||||
}
|
||||
|
||||
@IBAction func createButtonTapped() {
|
||||
if WatchDataSource.shared.companionWalletsInitialized {
|
||||
NotificationCenter.default.post(name: NotificationName.createQRCode, object: specifiedQRContent)
|
||||
dismiss()
|
||||
} else {
|
||||
presentAlert(withTitle: "Error", message: "Unable to create invoice. Please open BlueWallet on your iPhone and unlock your wallets.", preferredStyle: .alert, actions: [WKAlertAction(title: "OK", style: .default, handler: { [weak self] in
|
||||
self?.dismiss()
|
||||
})])
|
||||
}
|
||||
}
|
||||
|
||||
override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
|
||||
|
@ -21,15 +21,15 @@ class WalletDetailsInterfaceController: WKInterfaceController {
|
||||
|
||||
override func awake(withContext context: Any?) {
|
||||
super.awake(withContext: context)
|
||||
guard let identifier = context as? Int else {
|
||||
guard let identifier = context as? String else {
|
||||
pop()
|
||||
return
|
||||
}
|
||||
loadWalletDetails(identifier: identifier)
|
||||
}
|
||||
|
||||
private func loadWalletDetails(identifier: Int) {
|
||||
let wallet = WatchDataSource.shared.wallets[identifier]
|
||||
private func loadWalletDetails(identifier: String) {
|
||||
let wallet = WatchDataSource.shared.wallets[Int(identifier)!]
|
||||
self.wallet = wallet
|
||||
updateWalletUI(wallet: wallet)
|
||||
updateTransactionsTable(forWallet: wallet)
|
||||
@ -78,7 +78,7 @@ class WalletDetailsInterfaceController: WKInterfaceController {
|
||||
|
||||
@objc func showBalanceMenuItemTapped() {
|
||||
guard let identifier = wallet?.identifier else { return }
|
||||
WatchDataSource.toggleWalletHideBalance(walletIdentifier: identifier, hideBalance: false) { [weak self] _ in
|
||||
WatchDataSource.toggleWalletHideBalance(walletIdentifier: String(identifier), hideBalance: false) { [weak self] _ in
|
||||
DispatchQueue.main.async {
|
||||
WatchDataSource.postDataUpdatedNotification()
|
||||
self?.loadWalletDetails(identifier: identifier)
|
||||
@ -88,7 +88,7 @@ class WalletDetailsInterfaceController: WKInterfaceController {
|
||||
|
||||
@objc func hideBalanceMenuItemTapped() {
|
||||
guard let identifier = wallet?.identifier else { return }
|
||||
WatchDataSource.toggleWalletHideBalance(walletIdentifier: identifier, hideBalance: true) { [weak self] _ in
|
||||
WatchDataSource.toggleWalletHideBalance(walletIdentifier: String(identifier), hideBalance: true) { [weak self] _ in
|
||||
DispatchQueue.main.async {
|
||||
WatchDataSource.postDataUpdatedNotification()
|
||||
self?.loadWalletDetails(identifier: identifier)
|
||||
@ -115,15 +115,8 @@ class WalletDetailsInterfaceController: WKInterfaceController {
|
||||
}
|
||||
|
||||
@IBAction func createInvoiceTapped() {
|
||||
if WatchDataSource.shared.companionWalletsInitialized {
|
||||
guard let wallet = wallet else { return }
|
||||
pushController(withName: ReceiveInterfaceController.identifier, context: (wallet.identifier, ReceiveMethod.CreateInvoice))
|
||||
} else {
|
||||
WKInterfaceDevice.current().play(.failure)
|
||||
presentAlert(withTitle: "Error", message: "Unable to create invoice. Please open BlueWallet on your iPhone and unlock your wallets.", preferredStyle: .alert, actions: [WKAlertAction(title: "OK", style: .default, handler: { [weak self] in
|
||||
self?.dismiss()
|
||||
})])
|
||||
}
|
||||
}
|
||||
|
||||
override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
|
||||
|
77
ios/Shared/Utilities/KeychainManager.swift
Normal file
77
ios/Shared/Utilities/KeychainManager.swift
Normal file
@ -0,0 +1,77 @@
|
||||
//
|
||||
// KeychainManager.swift
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 10/23/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
class KeychainManager {
|
||||
|
||||
static let shared = KeychainManager()
|
||||
|
||||
private init() {}
|
||||
|
||||
// MARK: - Save Data to Keychain
|
||||
func set(_ data: Data, forKey key: String) -> Bool {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key,
|
||||
kSecValueData as String: data
|
||||
]
|
||||
|
||||
// Remove any existing item before adding new one
|
||||
SecItemDelete(query as CFDictionary)
|
||||
|
||||
let status = SecItemAdd(query as CFDictionary, nil)
|
||||
return status == errSecSuccess
|
||||
}
|
||||
|
||||
// MARK: - Get Data from Keychain
|
||||
func getData(forKey key: String) -> Data? {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key,
|
||||
kSecReturnData as String: true,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne
|
||||
]
|
||||
|
||||
var result: AnyObject?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
||||
|
||||
if status == errSecSuccess, let data = result as? Data {
|
||||
return data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Remove Data from Keychain
|
||||
func delete(forKey key: String) -> Bool {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key
|
||||
]
|
||||
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
return status == errSecSuccess
|
||||
}
|
||||
|
||||
// MARK: - Update Data in Keychain
|
||||
func update(_ data: Data, forKey key: String) -> Bool {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key
|
||||
]
|
||||
|
||||
let attributesToUpdate: [String: Any] = [
|
||||
kSecValueData as String: data
|
||||
]
|
||||
|
||||
let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
|
||||
return status == errSecSuccess
|
||||
}
|
||||
}
|
@ -21,14 +21,15 @@ struct WalletInformationView: View {
|
||||
let amount = numberFormatter.string(from: NSNumber(value: ((allWalletsBalance.balance / 100000000) * marketData.rate))) ?? ""
|
||||
return amount
|
||||
}
|
||||
var formattedLatestTransactionTime: String {
|
||||
|
||||
var formattedLatestTransactionTime: String? {
|
||||
if allWalletsBalance.latestTransactionTime.isUnconfirmed == true {
|
||||
return "Pending..."
|
||||
} else if allWalletsBalance.latestTransactionTime.epochValue == 0 {
|
||||
return "Never"
|
||||
return nil
|
||||
}
|
||||
guard let epochValue = allWalletsBalance.latestTransactionTime.epochValue else {
|
||||
return "Never"
|
||||
return nil
|
||||
}
|
||||
let forDate = Date(timeIntervalSince1970: TimeInterval(epochValue / 1000))
|
||||
let dateFormatter = RelativeDateTimeFormatter()
|
||||
@ -41,14 +42,17 @@ struct WalletInformationView: View {
|
||||
VStack(alignment: .leading, spacing: nil, content: {
|
||||
Text(allWalletsBalance.formattedBalanceBTC).font(Font.system(size: 15, weight: .medium, design: .default)).foregroundColor(.textColorLightGray).lineLimit(1).minimumScaleFactor(0.01)
|
||||
|
||||
|
||||
Text(formattedBalance).lineLimit(1).foregroundColor(.textColor).font(Font.system(size: 28, weight: .bold, design: .default)).minimumScaleFactor(0.01)
|
||||
|
||||
Spacer()
|
||||
|
||||
// Conditionally render latest transaction time if it's valid
|
||||
if let latestTransaction = formattedLatestTransactionTime {
|
||||
Text("Latest transaction").font(Font.system(size: 11, weight: .regular, design: .default)).foregroundColor(.textColorLightGray)
|
||||
Text(formattedLatestTransactionTime).lineLimit(1).foregroundColor(.textColor).font(Font.system(size:13, weight: .regular, design: .default)).minimumScaleFactor(0.01)
|
||||
|
||||
}).frame(minWidth: 0,
|
||||
Text(latestTransaction).lineLimit(1).foregroundColor(.textColor).font(Font.system(size: 13, weight: .regular, design: .default)).minimumScaleFactor(0.01)
|
||||
}
|
||||
})
|
||||
.frame(minWidth: 0,
|
||||
maxWidth: .infinity,
|
||||
minHeight: 0,
|
||||
maxHeight: .infinity,
|
||||
|
Loading…
Reference in New Issue
Block a user