fix: tell two accounts with the same name apart in settings

This commit is contained in:
Linus Rath
2026-10-31 04:21:37 +01:00
parent b205c13a6e
commit 505fbc59ad
3 changed files with 63 additions and 5 deletions
+6 -5
View File
@@ -15,7 +15,7 @@ import { setPendingSettingsTab } from '../../navigation/pending-settings-tab';
import { sharedAccountSettingsTabs } from '../../lib/capabilities';
import { jmapClient } from '../../api/jmap-client';
import { fetchMailQuota, type MailQuota } from '../../api/quota';
import { MAX_ACCOUNTS } from '../../lib/account-utils';
import { MAX_ACCOUNTS, accountRowText } from '../../lib/account-utils';
import type { RootStackParamList } from '../../navigation/types';
function formatFileSize(bytes: number): string {
@@ -168,7 +168,8 @@ export function AccountSettings() {
{accounts.map((a, index) => {
const isActive = a.id === activeAccountId;
const isDefault = a.id === defaultAccountId;
const label = a.email || a.username;
// The address leads when another account has the same name.
const rowText = accountRowText(a, accounts);
return (
<View key={a.id} style={[styles.row, index > 0 && styles.rowBorder]}>
<View style={[styles.avatar, { backgroundColor: a.avatarColor }]}>
@@ -183,10 +184,10 @@ export function AccountSettings() {
: t('settings.account.accounts.switch_to', 'Switch to this account')}
>
<View style={styles.rowTitle}>
<Text style={styles.rowName} numberOfLines={1}>{a.displayName || a.username}</Text>
<Text style={styles.rowName} numberOfLines={1}>{rowText.title}</Text>
{isActive && <Check size={14} color={c.success} />}
</View>
<Text style={styles.rowSub} numberOfLines={1}>{label}</Text>
{rowText.subtitle ? <Text style={styles.rowSub} numberOfLines={1}>{rowText.subtitle}</Text> : null}
{isDefault && (
<Text style={styles.defaultBadge}>
{t('settings.account.accounts.default_badge', 'Default account')}
@@ -226,7 +227,7 @@ export function AccountSettings() {
</Pressable>
<Pressable
style={styles.iconBtn}
onPress={() => confirmRemove(a.id, label)}
onPress={() => confirmRemove(a.id, a.email || a.username)}
disabled={busyId !== null}
accessibilityRole="button"
accessibilityLabel={t('common.remove', 'Remove')}
@@ -0,0 +1,27 @@
import { describe, it, expect } from 'vitest';
import { accountRowText } from '../account-utils';
// One person signed in to two mailboxes has the same name on both, so the
// rows in the switcher and in Settings > Account looked alike; only the
// small address line told them apart (webmail 3bb9f7df8).
const ada = { id: 'a', displayName: 'Ada Lovelace', email: 'ada@example.com', username: 'ada@example.com' };
const adaWork = { id: 'b', displayName: 'Ada Lovelace', email: 'ada@work.example', username: 'ada@work.example' };
const bob = { id: 'c', displayName: 'Bob', email: 'bob@example.com', username: 'bob@example.com' };
describe('accountRowText', () => {
it('shows the name over the address', () => {
expect(accountRowText(ada, [ada, bob])).toEqual({ title: 'Ada Lovelace', subtitle: 'ada@example.com' });
});
it('leads with the address when another account has the same name', () => {
expect(accountRowText(ada, [ada, adaWork, bob])).toEqual({ title: 'ada@example.com', subtitle: null });
expect(accountRowText(adaWork, [ada, adaWork, bob])).toEqual({ title: 'ada@work.example', subtitle: null });
expect(accountRowText(bob, [ada, adaWork, bob]).title).toBe('Bob');
});
it('does not repeat an address that is also the name', () => {
const plain = { id: 'd', displayName: 'carol@example.com', email: 'carol@example.com', username: 'carol@example.com' };
expect(accountRowText(plain, [plain])).toEqual({ title: 'carol@example.com', subtitle: null });
});
});
+30
View File
@@ -22,3 +22,33 @@ export function generateAccountId(username: string, serverUrl: string): string {
}
return `${username}@${host}`;
}
/** What naming an account in a list needs (structural: no AccountEntry import). */
export interface NamedAccount {
id: string;
displayName?: string;
email?: string;
username: string;
}
/**
* The bold title and the line under it for an account in a list (webmail
* lib/account-utils.ts accountRowText, 3bb9f7df8). Usually the name over the
* address. One person signed in to two mailboxes has the same name, and
* often the same picture, on both, so the rows only differed in the small
* address line: when another account shares the name, the address becomes
* the title and the name is left out. A name that is just the address again
* is left out too.
*/
export function accountRowText(
account: NamedAccount,
accounts: readonly NamedAccount[],
): { title: string; subtitle: string | null } {
const key = (value: string) => value.trim().toLowerCase();
const nameOf = (a: NamedAccount) => a.displayName || a.username;
const name = nameOf(account);
const address = account.email || account.username;
const shared = accounts.some((a) => a.id !== account.id && key(nameOf(a)) === key(name));
if (!name || shared || key(name) === key(address)) return { title: address, subtitle: null };
return { title: name, subtitle: address };
}