fix: let TalkBack read the unified inbox row as its name and unread count

This commit is contained in:
Linus Rath
2026-10-31 14:31:54 +01:00
parent ce950430ea
commit 172d0f63e8
3 changed files with 51 additions and 8 deletions
+10 -8
View File
@@ -49,6 +49,7 @@ import {
import { serverSupportsEmailPush } from '../lib/push-notifications';
import { mailboxIconKind, type MailboxIconKind } from '../lib/mailbox-icon';
import { useSheetBottomInset } from '../lib/sheet-bottom-inset';
import { unreadRowLabel } from '../lib/unread-row-label';
const CHEVRON_SLOT = 20;
const INDENT_STEP = 12;
@@ -171,9 +172,7 @@ function SidebarRow({
const leftPad = ROW_PX_BASE + depth * INDENT_STEP;
// Which folder is open, and the unread count with the folder's name
// ("Inbox, 3 unread"), for screen readers (webmail 7c282f1f4).
const accessibleName = unread > 0 && !isAccountHeader
? t('shell.name_unread', '{name}, {count} unread', { name: label, count: unread })
: label;
const accessibleName = isAccountHeader ? label : unreadRowLabel(label, unread, t);
return (
<Pressable
onPress={onPress}
@@ -720,6 +719,9 @@ export default function SidebarDrawer({ visible, onClose, docked = false }: Side
// account's (own + shared) plus, cross-account, the tucked-away snapshots
// of the other accounts.
const hasSharedInbox = mailboxes.some((m) => m.isShared && m.role === 'inbox');
const unifiedInboxLabel = unifiedCrossAccount && accounts.length > 1
? t('sidebar.all_accounts', 'All accounts')
: t('sidebar.unified_inbox', 'All inboxes');
const showUnified = showUnifiedSection({
accountCount: accounts.length, unifiedCrossAccount, includeGroupInUnified, hasSharedInbox,
});
@@ -1047,13 +1049,13 @@ export default function SidebarDrawer({ visible, onClose, docked = false }: Side
<Pressable
style={({ pressed }) => [styles.quickRow, pressed && styles.rowPressed]}
onPress={() => { onClose(); navigation.navigate('UnifiedInbox', { role: 'inbox' }); }}
// A name of its own, like a folder row: else TalkBack read its
// texts one by one ("All inboxes, /, 9").
accessibilityRole="button"
accessibilityLabel={unreadRowLabel(unifiedInboxLabel, unifiedCounts.inbox.unread, t)}
>
<Layers size={16} color={c.textSecondary} />
<Text style={styles.quickRowLabel}>
{unifiedCrossAccount && accounts.length > 1
? t('sidebar.all_accounts', 'All accounts')
: t('sidebar.unified_inbox', 'All inboxes')}
</Text>
<Text style={styles.quickRowLabel}>{unifiedInboxLabel}</Text>
<View style={{ flex: 1 }} />
<RowCounts unread={unifiedCounts.inbox.unread} total={unifiedCounts.inbox.total} showTotal={showFolderTotalCount} />
</Pressable>
@@ -0,0 +1,30 @@
import { describe, expect, it, vi } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
vi.mock('expo-localization', () => ({ getLocales: () => [] }));
import { translate } from '../../i18n';
import { unreadRowLabel } from '../unread-row-label';
const tEn = (key: string, fallback?: string, params?: Record<string, string | number>) => translate('en', key, fallback, params);
describe('unreadRowLabel', () => {
it('says the unread count after the name', () => {
expect(unreadRowLabel('All inboxes', 9, tEn)).toBe('All inboxes, 9 unread');
expect(unreadRowLabel('All inboxes', 0, tEn)).toBe('All inboxes');
});
});
describe('the sidebar Unified Inbox row', () => {
// TalkBack read it as "All inboxes, /, 9": no label, so the row's texts
// (the name and RowCounts' unread / total) one by one.
it('is a button named by its label and unread count', () => {
const source = fs.readFileSync(path.resolve(__dirname, '../../components/SidebarDrawer.tsx'), 'utf8');
const start = source.indexOf('{showUnified && (');
const row = source.slice(start, source.indexOf('</Pressable>', start));
expect(row).toContain("navigation.navigate('UnifiedInbox', { role: 'inbox' })");
expect(row).toMatch(/accessibilityRole="button"/);
expect(row).toMatch(/accessibilityLabel=\{unreadRowLabel\(unifiedInboxLabel, unifiedCounts\.inbox\.unread, t\)\}/);
});
});
+11
View File
@@ -0,0 +1,11 @@
type Translate = (key: string, fallback?: string, params?: Record<string, string | number>) => string;
/**
* What a screen reader calls a folder or view row: its name, with the unread
* count when there is unread mail ("Inbox, 3 unread"; webmail 7c282f1f4).
* Without a label of its own a row reads its texts one by one, so the
* Unified Inbox row came out as "All inboxes, /, 9".
*/
export function unreadRowLabel(name: string, unread: number, t: Translate): string {
return unread > 0 ? t('shell.name_unread', '{name}, {count} unread', { name, count: unread }) : name;
}