diff --git a/locales/rn/de.json b/locales/rn/de.json
index 3e9674e2..488b521f 100644
--- a/locales/rn/de.json
+++ b/locales/rn/de.json
@@ -1129,6 +1129,11 @@
"remove_account_message": "Von {account} auf diesem Gerät abmelden. Ihre E-Mails bleiben auf dem Server.",
"remove_account_title": "Konto entfernen?",
"show_unread": "Ungelesene anzeigen",
+ "sign_out_all_confirm_message": "Alle Konten werden von diesem Gerät entfernt. Sie können sich jederzeit wieder anmelden.",
+ "sign_out_all_confirm_title": "Von allen Konten abmelden?",
+ "sign_out_confirm_message": "Das Konto wird von diesem Gerät entfernt. Sie können sich jederzeit wieder anmelden.",
+ "sign_out_confirm_title": "Abmelden?",
+ "sign_out_confirm_title_account": "Von {account} abmelden?",
"tab_unavailable": "{name} (nicht verfügbar)"
},
"unified_mailbox": {
diff --git a/locales/rn/en.json b/locales/rn/en.json
index 00f6f632..abb546ec 100644
--- a/locales/rn/en.json
+++ b/locales/rn/en.json
@@ -1148,6 +1148,11 @@
"remove_account_message": "Sign out of {account} on this device. Your mail stays on the server.",
"remove_account_title": "Remove account?",
"show_unread": "Show unread",
+ "sign_out_all_confirm_message": "Every account is removed from this device. You can sign in again at any time.",
+ "sign_out_all_confirm_title": "Sign out of all accounts?",
+ "sign_out_confirm_message": "The account is removed from this device. You can sign in again at any time.",
+ "sign_out_confirm_title": "Sign out?",
+ "sign_out_confirm_title_account": "Sign out of {account}?",
"tab_unavailable": "{name} (unavailable)"
},
"unified_mailbox": {
diff --git a/src/components/SidebarDrawer.tsx b/src/components/SidebarDrawer.tsx
index 8595670e..8cb5ddbb 100644
--- a/src/components/SidebarDrawer.tsx
+++ b/src/components/SidebarDrawer.tsx
@@ -38,6 +38,7 @@ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/types';
import type { Mailbox } from '../api/types';
import { MODAL_ORIENTATIONS } from '../lib/modal-orientations';
+import { confirmSignOut } from '../lib/sign-out-confirm';
const CHEVRON_SLOT = 20;
const INDENT_STEP = 12;
@@ -919,7 +920,10 @@ export default function SidebarDrawer({ visible, onClose, docked = false }: Side
styles.accountMenuAction,
pressed && styles.accountMenuActionPressed,
]}
- onPress={() => { setAccountMenuOpen(false); onClose(); void logout(); }}
+ onPress={() => {
+ setAccountMenuOpen(false);
+ confirmSignOut(t, { account: accountEmailFull || undefined }, () => { onClose(); void logout(); });
+ }}
>
@@ -932,7 +936,10 @@ export default function SidebarDrawer({ visible, onClose, docked = false }: Side
styles.accountMenuAction,
pressed && styles.accountMenuActionPressed,
]}
- onPress={() => { setAccountMenuOpen(false); onClose(); void logoutAll(); }}
+ onPress={() => {
+ setAccountMenuOpen(false);
+ confirmSignOut(t, { all: true }, () => { onClose(); void logoutAll(); });
+ }}
>
diff --git a/src/lib/__tests__/sign-out-confirm.test.ts b/src/lib/__tests__/sign-out-confirm.test.ts
new file mode 100644
index 00000000..6ff29e04
--- /dev/null
+++ b/src/lib/__tests__/sign-out-confirm.test.ts
@@ -0,0 +1,23 @@
+import { describe, it, expect, vi } from 'vitest';
+
+const alert = vi.fn();
+vi.mock('react-native', () => ({ Alert: { alert: (...a: unknown[]) => alert(...a) } }));
+
+import { confirmSignOut } from '../sign-out-confirm';
+
+const t = (_key: string, fallback: string, params?: Record) =>
+ fallback.replace(/\{(\w+)\}/g, (_, k) => String(params?.[k] ?? ''));
+
+describe('confirmSignOut', () => {
+ it('asks first and signs out only from the destructive button', () => {
+ const onConfirm = vi.fn();
+ confirmSignOut(t, { account: 'usera@example.org' }, onConfirm);
+ expect(onConfirm).not.toHaveBeenCalled();
+ const [title, , buttons] = alert.mock.calls[0] as [string, string, Array<{ style?: string; onPress?: () => void }>];
+ expect(title).toBe('Sign out of usera@example.org?');
+ buttons.find((b) => b.style === 'cancel')?.onPress?.();
+ expect(onConfirm).not.toHaveBeenCalled();
+ buttons.find((b) => b.style === 'destructive')!.onPress!();
+ expect(onConfirm).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/lib/sign-out-confirm.ts b/src/lib/sign-out-confirm.ts
new file mode 100644
index 00000000..d428cf65
--- /dev/null
+++ b/src/lib/sign-out-confirm.ts
@@ -0,0 +1,31 @@
+import { Alert } from 'react-native';
+
+type Translate = (key: string, fallback: string, params?: Record) => string;
+
+/**
+ * Asks before signing out: it drops the account's cached mail and push
+ * registration on this device, and a stray tap in the account menu or at the
+ * end of Settings did it straight away.
+ */
+export function confirmSignOut(
+ t: Translate,
+ { account, all = false }: { account?: string; all?: boolean },
+ onConfirm: () => void,
+): void {
+ const title = all
+ ? t('sidebar.sign_out_all_confirm_title', 'Sign out of all accounts?')
+ : account
+ ? t('sidebar.sign_out_confirm_title_account', 'Sign out of {account}?', { account })
+ : t('sidebar.sign_out_confirm_title', 'Sign out?');
+ const message = all
+ ? t('sidebar.sign_out_all_confirm_message', 'Every account is removed from this device. You can sign in again at any time.')
+ : t('sidebar.sign_out_confirm_message', 'The account is removed from this device. You can sign in again at any time.');
+ Alert.alert(title, message, [
+ { text: t('common.cancel', 'Cancel'), style: 'cancel' },
+ {
+ text: all ? t('sidebar.sign_out_all', 'Sign out of all accounts') : t('sidebar.sign_out', 'Sign out'),
+ style: 'destructive',
+ onPress: onConfirm,
+ },
+ ]);
+}
diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx
index c5cd2a7e..b6bf0822 100644
--- a/src/screens/SettingsScreen.tsx
+++ b/src/screens/SettingsScreen.tsx
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ScrollView, Pressable, TextInput, Keyboard } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useLayout } from '../lib/use-layout';
+import { confirmSignOut } from '../lib/sign-out-confirm';
import {
ArrowLeft, LogOut, Settings, ChevronRight, Search, X,
Palette, User, Shield, UserPen, Palmtree, Calendar,
@@ -506,7 +507,8 @@ export default function SettingsScreen({ onLogout, onBack, onTabSelect }: Settin
{/* Logout - matches webmail: border-t, destructive text, icon + label */}
{ if (onLogout) confirmSignOut(t, {}, onLogout); }}
+ accessibilityRole="button"
style={({ pressed }) => [styles.logoutBtn, pressed && styles.logoutBtnPressed]}
>