diff --git a/blue_modules/BlueElectrum.ts b/blue_modules/BlueElectrum.ts index 0c6496681..74a68d107 100644 --- a/blue_modules/BlueElectrum.ts +++ b/blue_modules/BlueElectrum.ts @@ -9,6 +9,7 @@ import presentAlert from '../components/Alert'; import loc from '../loc'; import { GROUP_IO_BLUEWALLET } from './currency'; import { ElectrumServerItem } from '../screen/settings/ElectrumSettings'; +import { triggerWarningHapticFeedback } from './hapticFeedback'; const ElectrumClient = require('electrum-client'); const net = require('net'); @@ -326,6 +327,7 @@ export async function connectMain(): Promise { export async function presentResetToDefaultsAlert(): Promise { return new Promise(resolve => { + triggerWarningHapticFeedback(); presentAlert({ title: loc.settings.electrum_reset, message: loc.settings.electrum_reset_to_default, diff --git a/ios/BlueWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/BlueWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved index 23bedbaa3..dc603f84c 100644 --- a/ios/BlueWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/BlueWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "89509f555bc90a15b96ca0a326a69850770bdaac04a46f9cf482d81533702e3c", + "originHash" : "52530e6b1e3a85c8854952ef703a6d1bbe1acd82713be2b3166476b9b277db23", "pins" : [ { "identity" : "bugsnag-cocoa", @@ -19,6 +19,15 @@ "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", diff --git a/loc/en.json b/loc/en.json index 31fd92a98..bb0163ac3 100644 --- a/loc/en.json +++ b/loc/en.json @@ -29,6 +29,7 @@ "qr_custom_input_button": "Tap 10 times to enter custom input", "unlock": "Unlock", "port": "Port", + "ssl_port": "SSL Port", "suggested": "Suggested" }, "azteco": { @@ -217,7 +218,6 @@ "block_explorer_invalid_custom_url": "The URL provided is invalid. Please enter a valid URL starting with http:// or https://.", "about_selftest_electrum_disabled": "Self-testing is not available with Electrum Offline Mode. Please disable offline mode and try again.", "about_selftest_ok": "All internal tests have passed successfully. The wallet works well.", - "about_sm_github": "GitHub", "about_sm_discord": "Discord Server", "about_sm_telegram": "Telegram channel", @@ -252,15 +252,11 @@ "electrum_settings_server": "Electrum Server", "electrum_status": "Status", "electrum_preferred_server": "Preferred Server", - "electrum_preferred_server_description": "Enter the server you want your wallet to use for all Bitcoin activities. Once set, your wallet will exclusively use this server to check balances, send transactions, and fetch network data. Ensure you trust this server before setting it.", "electrum_clear_alert_title": "Clear history?", - "electrum_clear_alert_message": "Do you want to clear electrum servers history?", - "electrum_clear_alert_cancel": "Cancel", - "only_use_preferred": "Only connect to preferred server", + "electrum_preferred_server_description": "Enter the server you want your wallet to use for all Bitcoin activities. Once set, your wallet will exclusively use this server to check balances, send transactions, and fetch network data. Ensure you trust this server before setting it.", "electrum_unable_to_connect": "Unable to connect to {server}.", "electrum_history": "History", "electrum_reset_to_default": "This will let BlueWallet randomly choose a server from the suggested list and history. Your server history will remain unchanged.", "electrum_reset": "Reset to default", - "electrum_clear": "Clear History", "encrypt_decrypt": "Decrypt Storage", "encrypt_decrypt_q": "Are you sure you want to decrypt your storage? This will allow your wallets to be accessed without a password.", "encrypt_enc_and_pass": "Encrypted and Password Protected", @@ -275,6 +271,7 @@ "encrypt_tstorage": "Storage", "encrypt_use": "Use {type}", "set_as_preferred": "Set as preferred", + "set_as_preferred_electrum": "Setting {host}:{port} as preferred server will disable connecting to a suggested server at random.", "encrypted_feature_disabled": "This feature cannot be used with encrypted storage enabled.", "encrypt_use_expl": "{type} will be used to confirm your identity before making a transaction, unlocking, exporting, or deleting a wallet. {type} will not be used to unlock encrypted storage.", "biometrics_fail": "If {type} is not enabled, or fails to unlock, you can use your device passcode as an alternative.", diff --git a/loc/pcm.json b/loc/pcm.json index 11693af9e..868d272e7 100644 --- a/loc/pcm.json +++ b/loc/pcm.json @@ -49,7 +49,6 @@ "default_info": "Normal info", "default_title": "As you launch", "default_wallets": "See all your wallets", - "electrum_clear_alert_title": "You wan clear history?", "electrum_clear_alert_message": "You wan clear electrum servers history?", "electrum_clear_alert_cancel": "Cancel", "electrum_clear_alert_ok": "Ok", diff --git a/screen/settings/ElectrumSettings.tsx b/screen/settings/ElectrumSettings.tsx index b4a99ac47..260312013 100644 --- a/screen/settings/ElectrumSettings.tsx +++ b/screen/settings/ElectrumSettings.tsx @@ -37,8 +37,6 @@ export interface ElectrumServerItem { } const SET_PREFERRED_PREFIX = 'set_preferred_'; -const DELETE_PREFIX = 'delete_'; -const PREFERRED_SERVER_ROW = 'preferredserverrow'; const ElectrumSettings: React.FC = () => { const { colors } = useTheme(); @@ -46,7 +44,7 @@ const ElectrumSettings: React.FC = () => { const { server } = params; const navigation = useExtendedNavigation(); const [isLoading, setIsLoading] = useState(true); - const [serverHistory, setServerHistory] = useState([]); + const [serverHistory, setServerHistory] = useState>(new Set()); const [config, setConfig] = useState<{ connected?: number; host?: string; port?: string }>({}); const [host, setHost] = useState(''); const [port, setPort] = useState(); @@ -97,12 +95,14 @@ const ElectrumSettings: React.FC = () => { console.log('Server history string:', serverHistoryStr); const parsedServerHistory: ElectrumServerItem[] = serverHistoryStr ? JSON.parse(serverHistoryStr) : []; - const filteredServerHistory = parsedServerHistory.filter( - v => - v.host && - (v.tcp || v.ssl) && - !suggestedServers.some(suggested => suggested.host === v.host && suggested.tcp === v.tcp && suggested.ssl === v.ssl) && - !hardcodedPeers.some(peer => peer.host === v.host), + const filteredServerHistory = new Set( + parsedServerHistory.filter( + v => + v.host && + (v.tcp || v.ssl) && + !suggestedServers.some(suggested => suggested.host === v.host && suggested.tcp === v.tcp && suggested.ssl === v.ssl) && + !hardcodedPeers.some(peer => peer.host === v.host), + ), ); console.log('Filtered server history:', filteredServerHistory); @@ -152,20 +152,6 @@ const ElectrumSettings: React.FC = () => { } }, [server]); - const clearHistory = useCallback(async () => { - setIsLoading(true); - await DefaultPreference.clear(BlueElectrum.ELECTRUM_SERVER_HISTORY); - setServerHistory([]); - setIsLoading(false); - }, []); - - const serverExists = useCallback( - (value: ElectrumServerItem) => { - return serverHistory.some(s => `${s.host}:${s.tcp}:${s.ssl}` === `${value.host}:${value.tcp}:${value.ssl}`); - }, - [serverHistory], - ); - const save = useCallback( async (v?: ElectrumServerItem) => { Keyboard.dismiss(); @@ -182,19 +168,22 @@ const ElectrumSettings: React.FC = () => { await DefaultPreference.set(BlueElectrum.ELECTRUM_TCP_PORT, serverPort); await DefaultPreference.set(BlueElectrum.ELECTRUM_SSL_PORT, serverSslPort); - if ( - !serverExists({ host: serverHost, tcp: Number(serverPort), ssl: Number(serverSslPort) }) && - (serverPort || serverSslPort) && - !hardcodedPeers.some(peer => peer.host === serverHost) - ) { - const newServerHistory = [...serverHistory, { host: serverHost, tcp: Number(serverPort), ssl: Number(serverSslPort) }]; - await DefaultPreference.set(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify(newServerHistory)); + const serverExistsInHistory = Array.from(serverHistory).some( + s => s.host === serverHost && s.tcp === Number(serverPort) && s.ssl === Number(serverSslPort), + ); + + if (!serverExistsInHistory && (serverPort || serverSslPort) && !hardcodedPeers.some(peer => peer.host === serverHost)) { + const newServerHistory = new Set(serverHistory); + newServerHistory.add({ host: serverHost, tcp: Number(serverPort), ssl: Number(serverSslPort) }); + await DefaultPreference.set(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify(Array.from(newServerHistory))); setServerHistory(newServerHistory); } } triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); presentAlert({ message: loc.settings.electrum_saved }); + + fetchData(); } catch (error) { triggerHapticFeedback(HapticFeedbackTypes.NotificationError); presentAlert({ message: (error as Error).message }); @@ -202,30 +191,9 @@ const ElectrumSettings: React.FC = () => { setIsLoading(false); } }, - [host, port, sslPort, serverExists, serverHistory], + [host, port, sslPort, fetchData, serverHistory], ); - const resetToDefault = useCallback(() => { - Alert.alert(loc.settings.electrum_preferred_server, loc.settings.electrum_preferred_server_description, [ - { - text: loc._.cancel, - onPress: () => console.log('Cancel Pressed'), - style: 'cancel', - }, - { - text: loc._.ok, - style: 'destructive', - onPress: async () => { - await BlueElectrum.removePreferredServer(); - setHost(''); - setPort(undefined); - setSslPort(undefined); - presentAlert({ message: loc.settings.electrum_saved }); - }, - }, - ]); - }, []); - const selectServer = useCallback( (value: string) => { const parsedServer = JSON.parse(value) as ElectrumServerItem; @@ -237,43 +205,43 @@ const ElectrumSettings: React.FC = () => { [save], ); - const clearHistoryAlert = useCallback(() => { - triggerHapticFeedback(HapticFeedbackTypes.ImpactHeavy); - Alert.alert(loc.settings.electrum_clear_alert_title, loc.settings.electrum_clear_alert_message, [ - { text: loc.settings.electrum_clear_alert_cancel, onPress: () => console.log('Cancel Pressed'), style: 'cancel' }, - { text: loc._.ok, onPress: () => clearHistory() }, - ]); - }, [clearHistory]); + const presentSelectServerAlert = useCallback( + (value: ElectrumServerItem) => { + triggerHapticFeedback(HapticFeedbackTypes.ImpactHeavy); + Alert.alert( + loc.settings.electrum_preferred_server, + loc.formatString(loc.settings.set_as_preferred_electrum, { host: value.host, port: String(value.ssl ?? value.tcp) }), + [ + { + text: loc._.ok, + onPress: () => { + selectServer(JSON.stringify(value)); + }, + style: 'default', + }, + { text: loc._.cancel, onPress: () => {}, style: 'cancel' }, + ], + { cancelable: false }, + ); + }, + [selectServer], + ); const onPressMenuItem = useCallback( (id: string) => { if (id.startsWith(SET_PREFERRED_PREFIX)) { const rawServer = JSON.parse(id.replace(SET_PREFERRED_PREFIX, '')); - selectServer(JSON.stringify(rawServer)); - } else if (id.startsWith(DELETE_PREFIX)) { - const rawServer = JSON.parse(id.replace(DELETE_PREFIX, '')); - const newServerHistory = serverHistory - .filter(s => !(s.host === rawServer.host && s.tcp === rawServer.tcp && s.ssl === rawServer.ssl)) - .filter( - v => !suggestedServers.some(suggested => suggested.host === v.host && suggested.tcp === v.tcp && suggested.ssl === v.ssl), - ); - setServerHistory(newServerHistory); - DefaultPreference.set(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify(newServerHistory)); - } else if (id === PREFERRED_SERVER_ROW) { - presentResetToDefaultsAlert().then(async result => { - if (result) { - await BlueElectrum.removePreferredServer(); - presentAlert({ message: loc.settings.electrum_saved }); - fetchData(); - } - }); + presentSelectServerAlert(rawServer); } else { switch (id) { case CommonToolTipActions.ResetToDefault.id: - resetToDefault(); - break; - case CommonToolTipActions.ClearHistory.id: - clearHistoryAlert(); + presentResetToDefaultsAlert().then(reset => { + if (reset) { + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); + presentAlert({ message: loc.settings.electrum_saved }); + fetchData(); + } + }); break; default: try { @@ -285,37 +253,43 @@ const ElectrumSettings: React.FC = () => { } } }, - [selectServer, serverHistory, fetchData, resetToDefault, clearHistoryAlert], + [presentSelectServerAlert, fetchData, selectServer], ); + type TCreateServerActionParameters = { + value: ElectrumServerItem; + seenHosts: Set; + isPreferred?: boolean; + isConnectedTo?: boolean; + isSuggested?: boolean; + }; const createServerAction = useCallback( - (value: ElectrumServerItem, seenHosts: Set) => { + ({ value, seenHosts, isPreferred = false, isConnectedTo = false, isSuggested = false }: TCreateServerActionParameters) => { const hostKey = `${value.host}:${value.ssl ?? value.tcp}`; if (seenHosts.has(hostKey)) return null; seenHosts.add(hostKey); return { - id: JSON.stringify(value), + id: `${SET_PREFERRED_PREFIX}${JSON.stringify(value)}`, text: Platform.OS === 'android' ? `${value.host}:${value.ssl ?? value.tcp}` : value.host, - subactions: [ - ...(host === value.host && (port === value.tcp || sslPort === value.ssl) + icon: isPreferred ? { iconValue: Platform.OS === 'ios' ? 'star.fill' : 'star_off' } : undefined, + menuState: isConnectedTo, + disabled: isPreferred, + subtitle: value.ssl ? `${loc._.ssl_port}: ${value.ssl}` : `${loc._.port}: ${value.tcp}`, + subactions: + isSuggested || isPreferred ? [] : [ - { - id: `${SET_PREFERRED_PREFIX}${JSON.stringify(value)}`, - text: loc.settings.set_as_preferred, - subtitle: `${loc._.port}: ${value.ssl ?? value.tcp}`, - }, - ]), - ...(hardcodedPeers.some(peer => peer.host === value.host) - ? [] - : [ - { - id: `${DELETE_PREFIX}${JSON.stringify(value)}`, - text: loc.wallets.details_delete, - }, - ]), - ], + ...(host === value.host && (port === value.tcp || sslPort === value.ssl) + ? [] + : [ + { + id: `${SET_PREFERRED_PREFIX}${JSON.stringify(value)}`, + text: loc.settings.set_as_preferred, + subtitle: value.ssl ? `${loc._.ssl_port}: ${value.ssl}` : `${loc._.port}: ${value.tcp}`, + }, + ]), + ], } as Action; }, [host, port, sslPort], @@ -325,52 +299,55 @@ const ElectrumSettings: React.FC = () => { const actions: Action[] = []; const seenHosts = new Set(); - if (host) { - const preferred = { - id: 'preferred', - hidden: false, - displayInline: true, - text: loc.settings.electrum_preferred_server, - subactions: [ - { - id: PREFERRED_SERVER_ROW, - text: Platform.OS === 'android' ? `${host} (${sslPort ?? port})` : host, - subtitle: `${loc._.port}: ${sslPort ?? port}`, - menuState: true, - }, - ], - }; - actions.push(preferred); - seenHosts.add(`${host}:${sslPort ?? port}`); - } - const suggestedServersAction: Action = { id: 'suggested_servers', text: loc._.suggested, displayInline: true, subtitle: loc.settings.electrum_suggested_description, - subactions: suggestedServers.map(value => createServerAction(value, seenHosts)).filter((action): action is Action => action !== null), + subactions: suggestedServers + .map(value => + createServerAction({ + value, + seenHosts, + isPreferred: host === value.host && (port === value.tcp || sslPort === value.ssl), + isConnectedTo: config?.host === value.host && (config.port === value.tcp || config.port === value.ssl), + isSuggested: true, + }), + ) + .filter((action): action is Action => action !== null), }; actions.push(suggestedServersAction); - console.warn('serverHistory', serverHistory); - if (serverHistory.length > 0) { - const serverSubactions: Action[] = serverHistory - .map(value => createServerAction(value, seenHosts)) + if (serverHistory.size > 0) { + const serverSubactions: Action[] = Array.from(serverHistory) + .map(value => + createServerAction({ + value, + seenHosts, + isPreferred: host === value.host && (port === value.tcp || sslPort === value.ssl), + isConnectedTo: config?.host === value.host && (config.port === value.tcp || config.port === value.ssl), + isSuggested: false, + }), + ) .filter((action): action is Action => action !== null); actions.push({ id: 'server_history', text: loc.settings.electrum_history, - displayInline: serverHistory.length <= 5 && serverHistory.length > 0, - subactions: [CommonToolTipActions.ClearHistory, ...serverSubactions], - hidden: serverHistory.length === 0, + displayInline: serverHistory.size <= 5 && serverHistory.size > 0, + subactions: serverSubactions, + hidden: serverHistory.size === 0, }); } + const resetToDefaults = { ...CommonToolTipActions.ResetToDefault }; + resetToDefaults.hidden = !host; + + actions.push(resetToDefaults); + return actions; - }, [createServerAction, host, port, serverHistory, sslPort]); + }, [config?.host, config.port, createServerAction, host, port, serverHistory, sslPort]); const HeaderRight = useMemo( () => , diff --git a/tests/e2e/bluewallet.spec.js b/tests/e2e/bluewallet.spec.js index 3ef0c8d01..e6f319f36 100644 --- a/tests/e2e/bluewallet.spec.js +++ b/tests/e2e/bluewallet.spec.js @@ -93,8 +93,7 @@ describe('BlueWallet UI Tests - no wallets', () => { await sup('OK'); await element(by.text('OK')).tap(); await element(by.id('HeaderMenuButton')).tap(); - await element(by.text('Preferred Server')).tap(); - await element(by.text('electrum.blockstream.info (50001)')).tap(); + await element(by.text('Reset to default')).tap(); await sup('OK'); await element(by.text('OK')).tap(); await sup('OK'); diff --git a/typings/CommonToolTipActions.ts b/typings/CommonToolTipActions.ts index 426841a52..a5a59ffb8 100644 --- a/typings/CommonToolTipActions.ts +++ b/typings/CommonToolTipActions.ts @@ -22,7 +22,6 @@ const keys = { ClearClipboard: 'clearClipboard', PaymentsCode: 'paymentsCode', ResetToDefault: 'resetToDefault', - ClearHistory: 'clearHistory', ScanQR: 'scan_qr', RemoveAllRecipients: 'RemoveAllRecipients', AddRecipient: 'AddRecipient', @@ -78,9 +77,6 @@ const icons = { CoinControl: { iconValue: 'switch.2' }, CoSignTransaction: { iconValue: 'signature' }, PaymentsCode: { iconValue: 'qrcode.viewfinder' }, - ClearHistory: { - iconValue: 'trash', - }, RemoveAllRecipients: { iconValue: 'person.2.slash' }, AddRecipient: { iconValue: 'person.badge.plus' }, RemoveRecipient: { iconValue: 'person.badge.minus' }, @@ -324,14 +320,8 @@ export const CommonToolTipActions: Record = { }, ResetToDefault: { id: keys.ResetToDefault, - text: loc.settings.only_use_preferred, - hidden: true, - menuState: true, - }, - ClearHistory: { - id: keys.ClearHistory, - text: loc.settings.electrum_clear, - icon: icons.ClearHistory, + text: loc.settings.electrum_reset, + hidden: false, destructive: true, }, PasteFromClipboard: {