Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ReactotronConfig.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Reactotron from 'reactotron-react-native';
import { reactotronRedux } from 'reactotron-redux';
import mmkvPlugin from 'reactotron-react-native-mmkv';
import { storage } from './src/store/mmkv-storage';
import { storage } from './src/storage';

const reactotron = Reactotron.configure()
.use(reactotronRedux())
Expand Down
2 changes: 1 addition & 1 deletion src/components/SlashtagsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React, { ReactElement, useEffect, useState } from 'react';
import { createContext } from 'react';

import { useAppSelector } from '../hooks/redux';
import { WebRelayCache } from '../store/mmkv-storage';
import { WebRelayCache } from '../storage';
import { webRelaySelector } from '../store/reselect/settings';
import { seedHashSelector } from '../store/reselect/wallet';
import i18n from '../utils/i18n';
Expand Down
29 changes: 21 additions & 8 deletions src/hooks/useBlocksWidget.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useEffect, useState } from 'react';
import { __E2E__ } from '../constants/env';
import { widgetsCache } from '../storage/widgets-cache';
import { i18nTime } from '../utils/i18n';

type TBlocksWidgetData = {
type TWidgetData = {
height: string;
time: string;
date: string;
Expand Down Expand Up @@ -32,15 +33,24 @@ type ErrorState = {

type ReadyState = {
status: EWidgetStatus.Ready;
data: TBlocksWidgetData;
data: TWidgetData;
};

type TWidgetState = LoadingState | ErrorState | ReadyState;

const BASE_URL = 'https://mempool.space/api';
const REFRESH_INTERVAL = 1000 * 60 * 2; // 2 minutes
const CACHE_KEY = 'blocks';

const formatBlockInfo = (blockInfo): TBlocksWidgetData => {
const cacheData = (data: TWidgetData) => {
widgetsCache.set(CACHE_KEY, data);
};

const getCachedData = (): TWidgetData | null => {
return widgetsCache.get<TWidgetData>(CACHE_KEY);
};

const formatBlockInfo = (blockInfo): TWidgetData => {
const { format } = new Intl.NumberFormat('en-US');

const difficulty = (blockInfo.difficulty / 1000000000000).toFixed(2);
Expand Down Expand Up @@ -88,9 +98,11 @@ const formatBlockInfo = (blockInfo): TBlocksWidgetData => {
};

const useBlocksWidget = (): TWidgetState => {
const [state, setState] = useState<TWidgetState>({
status: EWidgetStatus.Loading,
data: null,
const [state, setState] = useState<TWidgetState>(() => {
const cached = getCachedData();
return cached
? { status: EWidgetStatus.Ready, data: cached }
: { status: EWidgetStatus.Loading, data: null };
});

useEffect(() => {
Expand Down Expand Up @@ -122,9 +134,10 @@ const useBlocksWidget = (): TWidgetState => {
try {
const hash = await fetchTipHash();
const blockInfo = await fetchBlockInfo(hash);
const formatted = formatBlockInfo(blockInfo);
const data = formatBlockInfo(blockInfo);

setState({ status: EWidgetStatus.Ready, data: formatted });
cacheData(data);
setState({ status: EWidgetStatus.Ready, data });
} catch (error) {
console.error('Failed to fetch block data:', error);
setState({ status: EWidgetStatus.Error, data: null });
Expand Down
24 changes: 18 additions & 6 deletions src/hooks/useNewsWidget.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { __E2E__ } from '../constants/env';
import { widgetsCache } from '../storage/widgets-cache';
import { timeAgo } from '../utils/helpers';

type TArticle = {
Expand All @@ -18,7 +19,7 @@ type TArticle = {
};
};

type TWidgetArticle = {
type TWidgetData = {
title: string;
timeAgo: string;
link: string;
Expand All @@ -43,18 +44,29 @@ type ErrorState = {

type ReadyState = {
status: EWidgetStatus.Ready;
data: TWidgetArticle;
data: TWidgetData;
};

type TWidgetState = LoadingState | ErrorState | ReadyState;

const BASE_URL = 'https://feeds.synonym.to/news-feed/api';
const REFRESH_INTERVAL = 1000 * 60 * 2; // 2 minutes
const CACHE_KEY = 'news';

const cacheData = (data: TWidgetData) => {
widgetsCache.set(CACHE_KEY, data);
};

const getCachedData = (): TWidgetData | null => {
return widgetsCache.get<TWidgetData>(CACHE_KEY);
};

const useNewsWidget = (): TWidgetState => {
const [state, setState] = useState<TWidgetState>({
status: EWidgetStatus.Loading,
data: null,
const [state, setState] = useState<TWidgetState>(() => {
const cached = getCachedData();
return cached
? { status: EWidgetStatus.Ready, data: cached }
: { status: EWidgetStatus.Loading, data: null };
});

useEffect(() => {
Expand All @@ -71,7 +83,6 @@ const useNewsWidget = (): TWidgetState => {
};

const fetchData = async (): Promise<void> => {
setState({ status: EWidgetStatus.Loading, data: null });
try {
const articles = await fetchArticles();

Expand All @@ -87,6 +98,7 @@ const useNewsWidget = (): TWidgetState => {
publisher: article.publisher.title,
};

cacheData(data);
setState({ status: EWidgetStatus.Ready, data });
} catch (error) {
console.error('Failed to fetch news data:', error);
Expand Down
48 changes: 43 additions & 5 deletions src/hooks/usePriceWidget.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { __E2E__ } from '../constants/env';
import { tradingPairs } from '../constants/widgets';
import { widgetsCache } from '../storage/widgets-cache';
import { TGraphPeriod } from '../store/types/widgets';
import { IThemeColors } from '../styles/themes';

Expand Down Expand Up @@ -94,13 +95,42 @@ export const formatPrice = (pair: TradingPair, price: number): string => {
}
};

const cacheData = (
pairName: string,
period: TGraphPeriod,
data: TWidgetData,
) => {
const cacheKey = `${pairName}_${period}`;
widgetsCache.set(cacheKey, data);
};

const getCachedData = (
pairs: string[],
period: TGraphPeriod,
): TWidgetData[] | null => {
const data = pairs.map((pairName) => {
const cacheKey = `${pairName}_${period}`;
const cached = widgetsCache.get<TWidgetData>(cacheKey);
return cached;
});

const allCached = data.every((d) => d !== null);
if (allCached) {
return data;
}

return null;
};

const usePriceWidget = (
pairs: string[],
period: TGraphPeriod,
): TWidgetState => {
const [state, setState] = useState<TWidgetState>({
status: EWidgetStatus.Loading,
data: null,
const [state, setState] = useState<TWidgetState>(() => {
const cached = getCachedData(pairs, period);
return cached
? { status: EWidgetStatus.Ready, data: cached }
: { status: EWidgetStatus.Loading, data: null };
});

// biome-ignore lint/correctness/useExhaustiveDependencies: pairs is an array so deep check it
Expand Down Expand Up @@ -146,12 +176,16 @@ const usePriceWidget = (
const change = getChange(updatedPastValues);
const price = formatPrice(pair, latestPrice);

return {
const data = {
name: pairName,
price,
change,
pastValues: updatedPastValues,
};

cacheData(pairName, period, data);

return data;
});
const data = await Promise.all(promises);
setState({ status: EWidgetStatus.Ready, data });
Expand Down Expand Up @@ -179,12 +213,16 @@ const usePriceWidget = (
const change = getChange(newPastValues);
const price = formatPrice(pair, latestPrice);

return {
const data = {
...pairData,
price,
change,
pastValues: newPastValues,
};

cacheData(pairData.name, period, data);

return data;
}),
);
setState({ status: EWidgetStatus.Ready, data: updatedData });
Expand Down
19 changes: 16 additions & 3 deletions src/hooks/useWeatherWidget.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { __E2E__ } from '../constants/env';
import { widgetsCache } from '../storage/widgets-cache';
import { refreshOnchainFeeEstimates } from '../store/utils/fees';
import { getDisplayValues, getFiatDisplayValues } from '../utils/displayValues';

Expand Down Expand Up @@ -56,6 +57,15 @@ const VBYTES_SIZE = 140; // average native segwit transaction size
const USD_GOOD_THRESHOLD = 1; // $1 USD threshold for good condition
const PERCENTILE_LOW = 0.33;
const PERCENTILE_HIGH = 0.66;
const CACHE_KEY = 'weather';

const cacheData = (data: TWidgetData) => {
widgetsCache.set(CACHE_KEY, data);
};

const getCachedData = (): TWidgetData | null => {
return widgetsCache.get<TWidgetData>(CACHE_KEY);
};

const calculateCondition = (
currentFeeRate: number,
Expand Down Expand Up @@ -97,9 +107,11 @@ const calculateCondition = (
};

const useWeatherWidget = (): TWidgetState => {
const [state, setState] = useState<TWidgetState>({
status: EWidgetStatus.Loading,
data: null,
const [state, setState] = useState<TWidgetState>(() => {
const cached = getCachedData();
return cached
? { status: EWidgetStatus.Ready, data: cached }
: { status: EWidgetStatus.Loading, data: null };
});

useEffect(() => {
Expand Down Expand Up @@ -137,6 +149,7 @@ const useWeatherWidget = (): TWidgetState => {
const currentFee = `${dv.fiatSymbol} ${dv.fiatFormatted}`;
const data = { condition, currentFee, nextBlockFee: fees.fast };

cacheData(data);
setState({ status: EWidgetStatus.Ready, data });
} catch (error) {
console.error('Failed to fetch fee data:', error);
Expand Down
8 changes: 7 additions & 1 deletion src/screens/Settings/DevSettings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import { EItemType, IListData } from '../../../components/List';
import { __E2E__ } from '../../../constants/env';
import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
import type { SettingsScreenProps } from '../../../navigation/types';
import { widgetsCache } from '../../../storage';
import { storage } from '../../../storage';
import actions from '../../../store/actions/actions';
import {
clearUtxos,
injectFakeTransaction,
} from '../../../store/actions/wallet';
import { getStore, getWalletStore } from '../../../store/helpers';
import { storage } from '../../../store/mmkv-storage';
import { warningsSelector } from '../../../store/reselect/checks';
import { settingsSelector } from '../../../store/reselect/settings';
import {
Expand Down Expand Up @@ -171,6 +172,11 @@ const DevSettings = ({
type: EItemType.button,
onPress: clearWebRelayCache,
},
{
title: 'Clear Widgets Cache',
type: EItemType.button,
onPress: widgetsCache.clear,
},
{
title: "Clear UTXO's",
type: EItemType.button,
Expand Down
9 changes: 9 additions & 0 deletions src/storage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MMKV } from 'react-native-mmkv';
import { receivedTxIds } from './received-tx-cache';
import { reduxStorage } from './redux-storage';
import { WebRelayCache } from './webrelay-cache';
import { widgetsCache } from './widgets-cache';

export const storage = new MMKV();

export { reduxStorage, receivedTxIds, WebRelayCache, widgetsCache };
32 changes: 32 additions & 0 deletions src/storage/received-tx-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Used to prevent duplicate notifications for the same txId that seems to occur when:
// - when Bitkit is brought from background to foreground

import { storage } from '.';

// - connection to electrum server is lost and then re-established
export const receivedTxIds = {
STORAGE_KEY: 'receivedTxIds',

// Get stored txIds
get: (): string[] => {
return JSON.parse(storage.getString(receivedTxIds.STORAGE_KEY) || '[]');
},

// Save txIds to storage
save: (txIds: string[]): void => {
storage.set(receivedTxIds.STORAGE_KEY, JSON.stringify(txIds));
},

// Add a new txId
add: (txId: string): void => {
const txIds = receivedTxIds.get();
txIds.push(txId);
receivedTxIds.save(txIds);
},

// Check if txId exists
has: (txId: string): boolean => {
const txIds = receivedTxIds.get();
return txIds.includes(txId);
},
};
17 changes: 17 additions & 0 deletions src/storage/redux-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Storage } from 'redux-persist';
import { storage } from '.';

export const reduxStorage: Storage = {
setItem: (key, value): Promise<boolean> => {
storage.set(key, value);
return Promise.resolve(true);
},
getItem: (key): Promise<string | undefined> => {
const value = storage.getString(key);
return Promise.resolve(value);
},
removeItem: (key): Promise<void> => {
storage.delete(key);
return Promise.resolve();
},
};
Loading
Loading