FIX: Allow user to define a custom block explorer URL #7077

This commit is contained in:
Marcos Rodriguez Velez 2024-09-17 22:00:58 -04:00
parent 92bd542069
commit 15c608a7af
16 changed files with 328 additions and 31 deletions

View file

@ -31,8 +31,6 @@ function Notifications(props) {
return false;
};
Notifications.isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android';
/**
* Calls `configure`, which tries to obtain push token, save it, and registers all associated with
* notifications callbacks
@ -131,7 +129,7 @@ function Notifications(props) {
* @returns {Promise<boolean>} TRUE if permissions were obtained, FALSE otherwise
*/
Notifications.tryToObtainPermissions = async function (anchor) {
if (!Notifications.isNotificationsCapable) return false;
if (!isNotificationsCapable) return false;
if (await Notifications.getPushToken()) {
// we already have a token, no sense asking again, just configure pushes to register callbacks and we are done
if (!alreadyConfigured) configureNotifications(); // no await so it executes in background while we return TRUE and use token
@ -441,4 +439,6 @@ function Notifications(props) {
return null;
}
export const isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android';
export default Notifications;

View file

@ -14,6 +14,7 @@ import { useStorage } from '../../hooks/context/useStorage';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { TotalWalletsBalanceKey, TotalWalletsBalancePreferredUnit } from '../TotalWalletsBalance';
import { LayoutAnimation } from 'react-native';
import { BLOCK_EXPLORERS, getBlockExplorer, saveBlockExplorer } from '../../models/blockExplorer';
// DefaultPreference and AsyncStorage get/set
@ -85,6 +86,8 @@ interface SettingsContextType {
setTotalBalancePreferredUnitStorage: (unit: BitcoinUnit) => Promise<void>;
isDrawerShouldHide: boolean;
setIsDrawerShouldHide: (value: boolean) => void;
selectedBlockExplorer: string;
setBlockExplorerStorage: (url: string) => Promise<boolean>;
}
const defaultSettingsContext: SettingsContextType = {
@ -112,6 +115,8 @@ const defaultSettingsContext: SettingsContextType = {
setTotalBalancePreferredUnitStorage: async (unit: BitcoinUnit) => {},
isDrawerShouldHide: false,
setIsDrawerShouldHide: () => {},
selectedBlockExplorer: BLOCK_EXPLORERS.DEFAULT,
setBlockExplorerStorage: async () => false,
};
export const SettingsContext = createContext<SettingsContextType>(defaultSettingsContext);
@ -142,6 +147,8 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
// Toggle Drawer (for screens like Manage Wallets or ScanQRCode)
const [isDrawerShouldHide, setIsDrawerShouldHide] = useState<boolean>(false);
const [selectedBlockExplorer, setSelectedBlockExplorer] = useState<string>(BLOCK_EXPLORERS.DEFAULT);
const languageStorage = useAsyncStorage(STORAGE_KEY);
const { walletsInitialized } = useStorage();
@ -211,6 +218,14 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
setTotalBalancePreferredUnitState(unit);
})
.catch(error => console.error('Error fetching total balance preferred unit:', error));
getBlockExplorer()
.then(url => {
console.debug('SettingsContext blockExplorer:', url);
setSelectedBlockExplorer(url);
})
.catch(error => console.error('Error fetching block explorer settings:', error));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -295,6 +310,14 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
setTotalBalancePreferredUnitState(unit);
}, []);
const setBlockExplorerStorage = useCallback(async (url: string): Promise<boolean> => {
const success = await saveBlockExplorer(url);
if (success) {
setSelectedBlockExplorer(url);
}
return success;
}, []);
const value = useMemo(
() => ({
preferredFiatCurrency,
@ -321,6 +344,8 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
setTotalBalancePreferredUnitStorage,
isDrawerShouldHide,
setIsDrawerShouldHide,
selectedBlockExplorer,
setBlockExplorerStorage,
}),
[
preferredFiatCurrency,
@ -347,6 +372,8 @@ export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ chil
setTotalBalancePreferredUnitStorage,
isDrawerShouldHide,
setIsDrawerShouldHide,
selectedBlockExplorer,
setBlockExplorerStorage,
],
);

View file

@ -0,0 +1,116 @@
import React from 'react';
import { StyleSheet, TextInput, View, TouchableOpacity } from 'react-native';
import { ListItem as RNElementsListItem } from '@rneui/themed';
import { useTheme } from './themes';
import loc from '../loc';
interface SettingsBlockExplorerCustomUrlListItemProps {
title: string;
customUrl?: string;
onCustomUrlChange?: (url: string) => void;
onSubmitCustomUrl?: () => void;
selected: boolean;
onPress: () => void;
checkmark?: boolean;
isLoading?: boolean;
}
const SettingsBlockExplorerCustomUrlListItem: React.FC<SettingsBlockExplorerCustomUrlListItemProps> = ({
title,
customUrl,
onCustomUrlChange,
onSubmitCustomUrl,
selected,
onPress,
checkmark = false,
isLoading = false,
}) => {
const { colors } = useTheme();
const styleHook = StyleSheet.create({
uri: {
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
},
containerStyle: {
backgroundColor: colors.background,
minHeight: checkmark ? 140 : 60,
},
checkmarkContainer: {
justifyContent: 'center',
alignItems: 'center', //
},
checkmarkStyle: {
backgroundColor: 'transparent',
borderWidth: 0,
},
});
return (
<TouchableOpacity onPress={onPress}>
<RNElementsListItem containerStyle={styleHook.containerStyle} bottomDivider>
<RNElementsListItem.Content>
<RNElementsListItem.Title style={[styles.title, { color: colors.text }]}>{title}</RNElementsListItem.Title>
</RNElementsListItem.Content>
{checkmark && (
<View style={styleHook.checkmarkContainer}>
<RNElementsListItem.CheckBox
iconRight
iconType="octaicon"
checkedIcon="check"
checked
containerStyle={styleHook.checkmarkStyle}
/>
</View>
)}
</RNElementsListItem>
{selected && (
<View style={[styles.uri, styleHook.uri]}>
<TextInput
value={customUrl}
placeholder={loc._.enter_url}
onChangeText={onCustomUrlChange}
numberOfLines={1}
style={styles.uriText}
placeholderTextColor="#81868e"
editable={!isLoading}
textContentType="URL"
autoFocus
clearButtonMode="while-editing"
autoCapitalize="none"
autoCorrect={false}
underlineColorAndroid="transparent"
onSubmitEditing={onSubmitCustomUrl}
testID="CustomURIInput"
/>
</View>
)}
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
title: {
fontSize: 16,
fontWeight: '500',
},
uri: {
flexDirection: 'row',
borderWidth: 1,
borderBottomWidth: 0.5,
minHeight: 44,
height: 44,
alignItems: 'center',
borderRadius: 4,
},
uriText: {
flex: 1,
color: '#81868e',
marginHorizontal: 8,
minHeight: 36,
height: 36,
},
});
export default SettingsBlockExplorerCustomUrlListItem;

View file

@ -43,7 +43,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
const { navigate } = useExtendedNavigation<NavigationProps>();
const menuRef = useRef<ToolTipMenuProps>();
const { txMetadata, counterpartyMetadata, wallets } = useStorage();
const { language } = useSettings();
const { language, selectedBlockExplorer } = useSettings();
const containerStyle = useMemo(
() => ({
backgroundColor: 'transparent',
@ -253,16 +253,16 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]);
const handleOnCopyNote = useCallback(() => Clipboard.setString(subtitle ?? ''), [subtitle]);
const handleOnViewOnBlockExplorer = useCallback(() => {
const url = `https://mempool.space/tx/${item.hash}`;
const url = `${selectedBlockExplorer}/${item.hash}`;
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
}
});
}, [item.hash]);
}, [item.hash, selectedBlockExplorer]);
const handleCopyOpenInBlockExplorerPress = useCallback(() => {
Clipboard.setString(`https://mempool.space/tx/${item.hash}`);
}, [item.hash]);
Clipboard.setString(`${selectedBlockExplorer}/${item.hash}`);
}, [item.hash, selectedBlockExplorer]);
const onToolTipPress = useCallback(
(id: any) => {

View file

@ -1596,7 +1596,7 @@ PODS:
- React
- RNCAsyncStorage (1.24.0):
- React-Core
- RNCClipboard (1.14.1):
- RNCClipboard (1.14.2):
- React-Core
- RNCPushNotificationIOS (1.11.0):
- React-Core
@ -1641,7 +1641,7 @@ PODS:
- React
- RNRate (1.2.12):
- React-Core
- RNReactNativeHapticFeedback (2.3.1):
- RNReactNativeHapticFeedback (2.3.2):
- DoubleConversion
- glog
- hermes-engine
@ -1751,7 +1751,7 @@ PODS:
- Yoga
- RNShare (10.2.1):
- React-Core
- RNSVG (15.6.0):
- RNSVG (15.7.1):
- React-Core
- RNVectorIcons (10.2.0):
- DoubleConversion
@ -2202,7 +2202,7 @@ SPEC CHECKSUMS:
ReactNativeCameraKit: 9d46a5d7dd544ca64aa9c03c150d2348faf437eb
RealmJS: 325a7b621587dd9945306d4cbfd6b641bc20e2dd
RNCAsyncStorage: ec53e44dc3e75b44aa2a9f37618a49c3bc080a7a
RNCClipboard: 0a720adef5ec193aa0e3de24c3977222c7e52a37
RNCClipboard: 5e503962f0719ace8f7fdfe9c60282b526305c85
RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
RNDeviceInfo: b899ce37a403a4dea52b7cb85e16e49c04a5b88e
@ -2215,11 +2215,11 @@ SPEC CHECKSUMS:
RNPrivacySnapshot: 71919dde3c6a29dd332115409c2aec564afee8f4
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNRate: ef3bcff84f39bb1d1e41c5593d3eea4aab2bd73a
RNReactNativeHapticFeedback: 2bdbd63bcdbb52c4ae81a7b0c48ab1f00c06980a
RNReactNativeHapticFeedback: 9b35ff960958c399b03581140fa2d1499c09c8b6
RNReanimated: ece067b779e0d6c7887c6bb80d381d0a0efd43c9
RNScreens: 19719a9c326e925498ac3b2d35c4e50fe87afc06
RNShare: 0fad69ae2d71de9d1f7b9a43acf876886a6cb99c
RNSVG: 5da7a24f31968ec74f0b091e3440080f347e279b
RNSVG: 4590aa95758149fa27c5c83e54a6a466349a1688
RNVectorIcons: 6382277afab3c54658e9d555ee0faa7a37827136
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d

View file

@ -10,6 +10,8 @@
"never": "Never",
"of": "{number} of {total}",
"ok": "OK",
"default": "Default",
"enter_url": "Enter URL",
"storage_is_encrypted": "Your storage is encrypted. Password is required to decrypt it.",
"yes": "Yes",
"no": "No",
@ -262,6 +264,9 @@
"encrypt_storage_explanation_description_line1": "Enabling Storage Encryption adds an extra layer of protection to your app by securing the way your data is stored on your device. This makes it harder for anyone to access your information without permission.",
"encrypt_storage_explanation_description_line2": "However, it's important to know that this encryption only protects the access to your wallets stored on the device's keychain. It doesn't put a password or any extra protection on the wallets themselves.",
"i_understand": "I understand",
"block_explorer": "Block Explorer",
"block_explorer_custom": "Custom Block Explorer",
"block_explorer_error_saving_custom": "Error saving custom block explorer",
"encrypt_title": "Security",
"encrypt_tstorage": "Storage",
"encrypt_use": "Use {type}",

32
models/blockExplorer.ts Normal file
View file

@ -0,0 +1,32 @@
import DefaultPreference from 'react-native-default-preference';
import { GROUP_IO_BLUEWALLET } from '../blue_modules/currency';
export const BLOCK_EXPLORER = 'blockExplorer';
export const BLOCK_EXPLORERS = {
DEFAULT: 'https://mempool.space/tx',
BLOCKCHAIR: 'https://blockchair.com',
BLOCKSTREAM: 'https://blockstream.info',
CUSTOM: 'custom',
};
export const getBlockExplorer = async (): Promise<string> => {
try {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
const selectedExplorer = await DefaultPreference.get(BLOCK_EXPLORER);
return selectedExplorer || BLOCK_EXPLORERS.DEFAULT; // Return the selected explorer or default to mempool.space
} catch (error) {
console.error('Error getting block explorer:', error);
return BLOCK_EXPLORERS.DEFAULT;
}
};
export const saveBlockExplorer = async (url: string): Promise<boolean> => {
try {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
await DefaultPreference.set(BLOCK_EXPLORER, url); // Save whatever URL is provided (either predefined or custom)
return true;
} catch (error) {
console.error('Error saving block explorer:', error);
return false;
}
};

View file

@ -31,6 +31,7 @@ import AddWalletStack from './AddWalletStack';
import AztecoRedeemStackRoot from './AztecoRedeemStack';
import {
AboutComponent,
BlockExplorerSettingsComponent,
CurrencyComponent,
DefaultViewComponent,
ElectrumSettingsComponent,
@ -304,6 +305,12 @@ const DetailViewStackScreensStack = () => {
component={NetworkSettingsComponent}
options={navigationStyle({ title: loc.settings.network })(theme)}
/>
<DetailViewStack.Screen
name="SettingsBlockExplorer"
component={BlockExplorerSettingsComponent}
options={navigationStyle({ title: loc.settings.block_explorer })(theme)}
/>
<DetailViewStack.Screen name="About" component={AboutComponent} options={navigationStyle({ title: loc.settings.about })(theme)} />
<DetailViewStack.Screen
name="DefaultView"

View file

@ -51,6 +51,7 @@ export type DetailViewStackParamList = {
PlausibleDeniability: undefined;
Licensing: undefined;
NetworkSettings: undefined;
SettingsBlockExplorer: undefined;
About: undefined;
DefaultView: undefined;
ElectrumSettings: undefined;

View file

@ -3,6 +3,7 @@ import React, { lazy, Suspense } from 'react';
import Currency from '../screen/settings/Currency';
import Language from '../screen/settings/Language';
import { LazyLoadingIndicator } from './LazyLoadingIndicator'; // Assume you have this component for loading indication
import SettingsBlockExplorer from '../screen/settings/SettingsBlockExplorer';
const Settings = lazy(() => import('../screen/settings/Settings'));
const GeneralSettings = lazy(() => import('../screen/settings/GeneralSettings'));
@ -46,6 +47,12 @@ export const NetworkSettingsComponent = () => (
</Suspense>
);
export const BlockExplorerSettingsComponent = () => (
<Suspense fallback={<LazyLoadingIndicator />}>
<SettingsBlockExplorer />
</Suspense>
);
export const AboutComponent = () => (
<Suspense fallback={<LazyLoadingIndicator />}>
<About />

View file

@ -23,6 +23,7 @@ import { useTheme } from '../../components/themes';
import { scanQrHelper } from '../../helpers/scan-qr';
import loc from '../../loc';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import { useSettings } from '../../hooks/context/useSettings';
const BROADCAST_RESULT = Object.freeze({
none: 'Input transaction hex',
@ -39,6 +40,7 @@ const Broadcast: React.FC = () => {
const [txHex, setTxHex] = useState<string | undefined>();
const { colors } = useTheme();
const [broadcastResult, setBroadcastResult] = useState<string>(BROADCAST_RESULT.none);
const { selectedBlockExplorer } = useSettings();
const stylesHooks = StyleSheet.create({
input: {
@ -158,13 +160,13 @@ const Broadcast: React.FC = () => {
<BlueSpacing20 />
</BlueCard>
)}
{BROADCAST_RESULT.success === broadcastResult && tx && <SuccessScreen tx={tx} />}
{BROADCAST_RESULT.success === broadcastResult && tx && <SuccessScreen tx={tx} url={`${selectedBlockExplorer}/${tx}`} />}
</View>
</SafeArea>
);
};
const SuccessScreen: React.FC<{ tx: string }> = ({ tx }) => {
const SuccessScreen: React.FC<{ tx: string; url: string }> = ({ tx, url }) => {
if (!tx) {
return null;
}
@ -177,7 +179,7 @@ const SuccessScreen: React.FC<{ tx: string }> = ({ tx }) => {
<BlueSpacing20 />
<BlueTextCentered>{loc.settings.success_transaction_broadcasted}</BlueTextCentered>
<BlueSpacing10 />
<BlueButtonLink title={loc.settings.open_link_in_explorer} onPress={() => Linking.openURL(`https://mempool.space/tx/${tx}`)} />
<BlueButtonLink title={loc.settings.open_link_in_explorer} onPress={() => Linking.openURL(url)} />
</View>
</BlueCard>
</View>

View file

@ -13,12 +13,14 @@ import loc from '../../loc';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import HandOffComponent from '../../components/HandOffComponent';
import { HandOffActivityType } from '../../components/types';
import { useSettings } from '../../hooks/context/useSettings';
const Success = () => {
const pop = () => {
getParent().pop();
};
const { colors } = useTheme();
const { selectedBlockExplorer } = useSettings();
const { getParent } = useNavigation();
const { amount, fee, amountUnit = BitcoinUnit.BTC, invoiceDescription = '', onDonePressed = pop, txid } = useRoute().params;
const stylesHook = StyleSheet.create({
@ -52,7 +54,7 @@ const Success = () => {
<HandOffComponent
title={loc.transactions.details_title}
type={HandOffActivityType.ViewInBlockExplorer}
url={`https://mempool.space/tx/${txid}`}
url={`${selectedBlockExplorer}/${txid}`}
/>
)}
</SafeArea>

View file

@ -1,30 +1,34 @@
import { useNavigation } from '@react-navigation/native';
import React from 'react';
import { ScrollView } from 'react-native';
import Notifications from '../../blue_modules/notifications';
import { isNotificationsCapable } from '../../blue_modules/notifications';
import ListItem from '../../components/ListItem';
import loc from '../../loc';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
const NetworkSettings = () => {
const { navigate } = useNavigation();
const NetworkSettings: React.FC = () => {
const navigation = useExtendedNavigation();
const navigateToElectrumSettings = () => {
navigate('ElectrumSettings');
navigation.navigate('ElectrumSettings');
};
const navigateToLightningSettings = () => {
navigate('LightningSettings');
navigation.navigate('LightningSettings');
};
const navigateToBlockExplorerSettings = () => {
navigation.navigate('SettingsBlockExplorer');
};
return (
<ScrollView contentInsetAdjustmentBehavior="automatic" automaticallyAdjustContentInsets>
<ListItem title={loc.settings.block_explorer} onPress={navigateToBlockExplorerSettings} testID="BlockExplorerSettings" chevron />
<ListItem title={loc.settings.network_electrum} onPress={navigateToElectrumSettings} testID="ElectrumSettings" chevron />
<ListItem title={loc.settings.lightning_settings} onPress={navigateToLightningSettings} testID="LightningSettings" chevron />
{Notifications.isNotificationsCapable && (
{isNotificationsCapable && (
<ListItem
title={loc.settings.notifications}
onPress={() => navigate('NotificationSettings')}
onPress={() => navigation.navigate('NotificationSettings')}
testID="NotificationSettings"
chevron
/>

View file

@ -0,0 +1,90 @@
import React, { useState } from 'react';
import { FlatList, Alert, StyleSheet } from 'react-native';
import ListItem from '../../components/ListItem';
import SettingsBlockExplorerCustomUrlListItem from '../../components/SettingsBlockExplorerCustomUrlListItem';
import loc from '../../loc';
import { BLOCK_EXPLORERS } from '../../models/blockExplorer';
import { useTheme } from '../../components/themes';
import { useSettings } from '../../hooks/context/useSettings';
const SettingsBlockExplorer: React.FC = () => {
const { selectedBlockExplorer, setBlockExplorerStorage } = useSettings();
const [customUrlInput, setCustomUrlInput] = useState<string>(selectedBlockExplorer || '');
const { colors } = useTheme();
const blockExplorers = [
{ name: `${loc._.default} - Mempool.space`, key: 'default', url: BLOCK_EXPLORERS.DEFAULT },
{ name: 'Blockchair', key: 'blockchair', url: BLOCK_EXPLORERS.BLOCKCHAIR },
{ name: 'Blockstream.info', key: 'blockstream', url: BLOCK_EXPLORERS.BLOCKSTREAM },
{ name: loc.settings.block_explorer_custom, key: 'custom', url: null },
];
const handleExplorerPress = async (key: string, url: string | null) => {
if (key === 'custom') {
setCustomUrlInput(selectedBlockExplorer);
return;
}
const success = await setBlockExplorerStorage(url!);
if (!success) {
Alert.alert(loc.errors.error, loc.settings.block_explorer_error_saving_custom);
}
};
const handleCustomUrlChange = (url: string) => {
setCustomUrlInput(url);
};
const handleSubmitCustomUrl = async () => {
if (!customUrlInput) return;
const success = await setBlockExplorerStorage(customUrlInput);
if (!success) {
Alert.alert(loc.errors.error, loc.settings.block_explorer_error_saving_custom);
}
};
const renderItem = ({ item }: { item: { name: string; key: string; url: string | null } }) => {
if (item.key === 'custom') {
return (
<SettingsBlockExplorerCustomUrlListItem
title={item.name}
selected={selectedBlockExplorer === customUrlInput}
customUrl={customUrlInput}
onCustomUrlChange={handleCustomUrlChange}
onSubmitCustomUrl={handleSubmitCustomUrl}
onPress={() => handleExplorerPress(item.key, item.url)}
checkmark={selectedBlockExplorer === customUrlInput}
/>
);
}
return (
<ListItem
title={item.name}
onPress={() => handleExplorerPress(item.key, item.url)}
checkmark={selectedBlockExplorer === item.url}
disabled={selectedBlockExplorer === item.url}
containerStyle={{ backgroundColor: colors.background }}
/>
);
};
return (
<FlatList
data={blockExplorers}
keyExtractor={item => item.key}
renderItem={renderItem}
contentInsetAdjustmentBehavior="automatic"
automaticallyAdjustContentInsets
style={[styles.container, { backgroundColor: colors.background }]}
/>
);
};
export default SettingsBlockExplorer;
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

View file

@ -19,6 +19,7 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import { useStorage } from '../../hooks/context/useStorage';
import { HandOffActivityType } from '../../components/types';
import { useSettings } from '../../hooks/context/useSettings';
const actionKeys = {
CopyToClipboard: 'copyToClipboard',
@ -63,6 +64,7 @@ const TransactionDetails = () => {
const { setOptions, navigate } = useExtendedNavigation<NavigationProps>();
const { hash, walletID } = useRoute<RouteProps>().params;
const { saveToDisk, txMetadata, counterpartyMetadata, wallets, getTransactions } = useStorage();
const { selectedBlockExplorer } = useSettings();
const [from, setFrom] = useState<string[]>([]);
const [to, setTo] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(true);
@ -159,7 +161,7 @@ const TransactionDetails = () => {
);
const handleOnOpenTransactionOnBlockExplorerTapped = () => {
const url = `https://mempool.space/tx/${tx?.hash}`;
const url = `${selectedBlockExplorer}/${tx?.hash}`;
Linking.canOpenURL(url)
.then(supported => {
if (supported) {
@ -184,7 +186,7 @@ const TransactionDetails = () => {
};
const handleCopyPress = (stringToCopy: string) => {
Clipboard.setString(stringToCopy !== actionKeys.CopyToClipboard ? stringToCopy : `https://mempool.space/tx/${tx?.hash}`);
Clipboard.setString(stringToCopy !== actionKeys.CopyToClipboard ? stringToCopy : `${selectedBlockExplorer}/${tx?.hash}`);
};
if (isLoading || !tx) {
@ -255,7 +257,7 @@ const TransactionDetails = () => {
<HandOffComponent
title={loc.transactions.details_title}
type={HandOffActivityType.ViewInBlockExplorer}
url={`https://mempool.space/tx/${tx.hash}`}
url={`${selectedBlockExplorer}/${tx.hash}`}
/>
<BlueCard>
<View>

View file

@ -21,6 +21,7 @@ import { useStorage } from '../../hooks/context/useStorage';
import { HandOffActivityType } from '../../components/types';
import HeaderRightButton from '../../components/HeaderRightButton';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import { useSettings } from '../../hooks/context/useSettings';
enum ButtonStatus {
Possible,
@ -97,6 +98,7 @@ const TransactionStatus = () => {
const { navigate, setOptions, goBack } = useNavigation<TransactionStatusProps['navigation']>();
const { colors } = useTheme();
const wallet = useRef(wallets.find(w => w.getID() === walletID));
const { selectedBlockExplorer } = useSettings();
const fetchTxInterval = useRef<NodeJS.Timeout>();
const stylesHook = StyleSheet.create({
value: {
@ -481,7 +483,7 @@ const TransactionStatus = () => {
<HandOffComponent
title={loc.transactions.details_title}
type={HandOffActivityType.ViewInBlockExplorer}
url={`https://mempool.space/tx/${tx.hash}`}
url={`${selectedBlockExplorer}/${tx.hash}`}
/>
<View style={styles.container}>