Merge pull request #7449 from BlueWallet/electrumpref

REF: Make Server history menu less confusing
This commit is contained in:
GLaDOS 2025-01-08 10:35:23 +00:00 committed by GitHub
commit 3532840b5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 124 additions and 151 deletions

View file

@ -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<void> {
export async function presentResetToDefaultsAlert(): Promise<boolean> {
return new Promise(resolve => {
triggerWarningHapticFeedback();
presentAlert({
title: loc.settings.electrum_reset,
message: loc.settings.electrum_reset_to_default,

View file

@ -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",

View file

@ -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.",

View file

@ -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",

View file

@ -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<ElectrumServerItem[]>([]);
const [serverHistory, setServerHistory] = useState<Set<ElectrumServerItem>>(new Set());
const [config, setConfig] = useState<{ connected?: number; host?: string; port?: string }>({});
const [host, setHost] = useState<string>('');
const [port, setPort] = useState<number | undefined>();
@ -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<string>;
isPreferred?: boolean;
isConnectedTo?: boolean;
isSuggested?: boolean;
};
const createServerAction = useCallback(
(value: ElectrumServerItem, seenHosts: Set<string>) => {
({ 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<string>();
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(
() => <HeaderMenuButton actions={generateToolTipActions()} onPressMenuItem={onPressMenuItem} />,

View file

@ -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');

View file

@ -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<string, ToolTipAction> = {
},
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: {