mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
Merge pull request #24 from arnauda-gh/fix/swipe-actions-notification
fix(mailboxes): prevent silent swipe failures by notifying user of missing folders
This commit is contained in:
@@ -172,9 +172,9 @@ export function SwipeableRow({
|
||||
return;
|
||||
}
|
||||
// Instant mode: fire on release-past-threshold, no second tap.
|
||||
if (g.dx >= COMMIT_THRESHOLD && rightAction !== 'none') {
|
||||
if ((g.dx >= COMMIT_THRESHOLD || (g.dx > 40 && g.vx >= 0.5)) && rightAction !== 'none') {
|
||||
fly(widthRef.current || EXIT_DISTANCE, rightAction);
|
||||
} else if (g.dx <= -COMMIT_THRESHOLD && leftAction !== 'none') {
|
||||
} else if ((g.dx <= -COMMIT_THRESHOLD || (g.dx < -40 && g.vx <= -0.5)) && leftAction !== 'none') {
|
||||
fly(-(widthRef.current || EXIT_DISTANCE), leftAction);
|
||||
} else {
|
||||
Animated.spring(dx, { toValue: 0, useNativeDriver: true, speed: 24, bounciness: 4 }).start();
|
||||
|
||||
@@ -96,3 +96,36 @@ export function flattenVisible(
|
||||
walk(nodes);
|
||||
return out;
|
||||
}
|
||||
|
||||
export function findTrashMailbox(mailboxes: Mailbox[]): Mailbox | undefined {
|
||||
const roleMatch = mailboxes.find((m) => m.role === 'trash');
|
||||
if (roleMatch) return roleMatch;
|
||||
|
||||
const names = ['trash', 'bin', 'deleted', 'deleted items', 'corbeille', 'messages supprimés', 'éléments supprimés', 'supprimé', 'supprimés'];
|
||||
return mailboxes.find((m) => {
|
||||
const lower = m.name.toLowerCase();
|
||||
return names.includes(lower) || names.some((n) => lower.includes(n));
|
||||
});
|
||||
}
|
||||
|
||||
export function findArchiveMailbox(mailboxes: Mailbox[]): Mailbox | undefined {
|
||||
const roleMatch = mailboxes.find((m) => m.role === 'archive');
|
||||
if (roleMatch) return roleMatch;
|
||||
|
||||
const names = ['archive', 'archives', 'archived'];
|
||||
return mailboxes.find((m) => {
|
||||
const lower = m.name.toLowerCase();
|
||||
return names.includes(lower) || names.some((n) => lower.includes(n));
|
||||
});
|
||||
}
|
||||
|
||||
export function findJunkMailbox(mailboxes: Mailbox[]): Mailbox | undefined {
|
||||
const roleMatch = mailboxes.find((m) => m.role === 'junk' || m.role === 'spam');
|
||||
if (roleMatch) return roleMatch;
|
||||
|
||||
const names = ['junk', 'spam', 'indésirables', 'indésirable', 'courrier indésirable'];
|
||||
return mailboxes.find((m) => {
|
||||
const lower = m.name.toLowerCase();
|
||||
return names.includes(lower) || names.some((n) => lower.includes(n));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ import { useSettingsStore, type SwipeAction } from '../stores/settings-store';
|
||||
import { useKeywordsStore } from '../stores/keywords-store';
|
||||
import { useLocaleStore } from '../stores/locale-store';
|
||||
import { formatListDate } from '../lib/date-format';
|
||||
import { findTrashMailbox, findArchiveMailbox, findJunkMailbox } from '../lib/mailbox-tree';
|
||||
import type { Email } from '../api/types';
|
||||
|
||||
function getSenderName(email: Email): string {
|
||||
return email.from?.[0]?.name || email.from?.[0]?.email || 'Unknown';
|
||||
}
|
||||
@@ -164,6 +164,7 @@ interface EmailListScreenProps {
|
||||
export default function EmailListScreen({ onEmailPress, onComposePress }: EmailListScreenProps) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const { t } = useLocaleStore();
|
||||
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
||||
const [filterMenuOpen, setFilterMenuOpen] = React.useState(false);
|
||||
const emails = useEmailStore((s) => s.emails);
|
||||
@@ -232,15 +233,15 @@ export default function EmailListScreen({ onEmailPress, onComposePress }: EmailL
|
||||
}, [emails, disableThreading]);
|
||||
|
||||
const archiveMailboxId = React.useMemo(
|
||||
() => mailboxes.find((m) => m.role === 'archive')?.id ?? null,
|
||||
() => findArchiveMailbox(mailboxes)?.id ?? null,
|
||||
[mailboxes],
|
||||
);
|
||||
const trashMailboxId = React.useMemo(
|
||||
() => mailboxes.find((m) => m.role === 'trash')?.id ?? null,
|
||||
() => findTrashMailbox(mailboxes)?.id ?? null,
|
||||
[mailboxes],
|
||||
);
|
||||
const junkMailboxId = React.useMemo(
|
||||
() => mailboxes.find((m) => m.role === 'junk' || m.role === 'spam')?.id ?? null,
|
||||
() => findJunkMailbox(mailboxes)?.id ?? null,
|
||||
[mailboxes],
|
||||
);
|
||||
|
||||
@@ -336,16 +337,31 @@ export default function EmailListScreen({ onEmailPress, onComposePress }: EmailL
|
||||
case 'archive':
|
||||
if (archiveMailboxId && currentMailboxId !== archiveMailboxId) {
|
||||
void archiveEmailAction(id);
|
||||
} else if (!archiveMailboxId) {
|
||||
Alert.alert(
|
||||
t('email_list.error', 'Error'),
|
||||
t('email_list.no_archive_folder', 'Could not find an Archive folder on the server.'),
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
if (trashMailboxId) {
|
||||
void deleteEmailAction(id, trashMailboxId, currentMailboxId);
|
||||
} else {
|
||||
Alert.alert(
|
||||
t('email_list.error', 'Error'),
|
||||
t('email_list.no_trash_folder', 'Could not find a Trash folder on the server. Please check your mailbox configuration.'),
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'spam':
|
||||
if (junkMailboxId && currentMailboxId !== junkMailboxId) {
|
||||
void moveToMailboxAction(id, currentMailboxId, junkMailboxId);
|
||||
} else if (!junkMailboxId) {
|
||||
Alert.alert(
|
||||
t('email_list.error', 'Error'),
|
||||
t('email_list.no_junk_folder', 'Could not find a Spam/Junk folder on the server.'),
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'read':
|
||||
@@ -365,7 +381,7 @@ export default function EmailListScreen({ onEmailPress, onComposePress }: EmailL
|
||||
}, [
|
||||
currentMailboxId, archiveMailboxId, trashMailboxId, junkMailboxId,
|
||||
moveToMailboxAction, archiveEmailAction, deleteEmailAction,
|
||||
markRead, markUnread, toggleStar, togglePin,
|
||||
markRead, markUnread, toggleStar, togglePin, t,
|
||||
]);
|
||||
|
||||
const renderEmailRow = React.useCallback(
|
||||
@@ -433,7 +449,7 @@ export default function EmailListScreen({ onEmailPress, onComposePress }: EmailL
|
||||
};
|
||||
|
||||
const handleBulkDelete = async () => {
|
||||
const trash = mailboxes.find((m) => m.role === 'trash');
|
||||
const trash = findTrashMailbox(mailboxes);
|
||||
if (!trash || !currentMailboxId) {
|
||||
clearSelection();
|
||||
return;
|
||||
|
||||
@@ -29,6 +29,8 @@ import { setEmailKeywords } from '../api/email';
|
||||
import { shareEmailEml, shareAttachment, downloadAttachment } from '../lib/email-export';
|
||||
import { useKeywordsStore, keywordToken, type KeywordDef } from '../stores/keywords-store';
|
||||
import { useSheetDrag } from '../lib/use-sheet-drag';
|
||||
import { useLocaleStore } from '../stores/locale-store';
|
||||
import { findTrashMailbox } from '../lib/mailbox-tree';
|
||||
import type { Email, Mailbox } from '../api/types';
|
||||
import type { RootStackParamList } from '../navigation/types';
|
||||
|
||||
@@ -65,6 +67,7 @@ function plainTextBody(email: Email): string {
|
||||
export default function EmailThreadScreen({ route, navigation }: Props) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const { t } = useLocaleStore();
|
||||
const { jmapAccountId } = route.params;
|
||||
// The displayed email is tracked in local state (not a route param) so that
|
||||
// swiping / Prev-Next can switch messages in place without remounting the
|
||||
@@ -299,8 +302,14 @@ export default function EmailThreadScreen({ route, navigation }: Props) {
|
||||
|
||||
const onDelete = () => {
|
||||
if (!email || !currentMailboxId) return;
|
||||
const trash = mailboxes.find((m) => m.role === 'trash');
|
||||
if (!trash) return;
|
||||
const trash = findTrashMailbox(mailboxes);
|
||||
if (!trash) {
|
||||
Alert.alert(
|
||||
t('email_list.error', 'Error'),
|
||||
t('email_list.no_trash_folder', 'Could not find a Trash folder on the server. Please check your mailbox configuration.'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
void deleteEmail(email.id, trash.id, currentMailboxId);
|
||||
navigation.goBack();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user