mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-21 14:34:55 +01:00
FIX: Allow user to define a custom block explorer URL #7077
This commit is contained in:
parent
92bd542069
commit
15c608a7af
16 changed files with 328 additions and 31 deletions
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
116
components/SettingsBlockExplorerCustomUrlListItem.tsx
Normal file
116
components/SettingsBlockExplorerCustomUrlListItem.tsx
Normal 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;
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
32
models/blockExplorer.ts
Normal 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;
|
||||
}
|
||||
};
|
|
@ -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"
|
||||
|
|
|
@ -51,6 +51,7 @@ export type DetailViewStackParamList = {
|
|||
PlausibleDeniability: undefined;
|
||||
Licensing: undefined;
|
||||
NetworkSettings: undefined;
|
||||
SettingsBlockExplorer: undefined;
|
||||
About: undefined;
|
||||
DefaultView: undefined;
|
||||
ElectrumSettings: undefined;
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
/>
|
90
screen/settings/SettingsBlockExplorer.tsx
Normal file
90
screen/settings/SettingsBlockExplorer.tsx
Normal 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,
|
||||
},
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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}>
|
||||
|
|
Loading…
Add table
Reference in a new issue