From 07438e010e46c6a05b286c703b2fff0c9f5f6511 Mon Sep 17 00:00:00 2001
From: arnauda-gh <59512940+arnauda-gh@users.noreply.github.com>
Date: Thu, 16 Jul 2026 19:04:01 +0200
Subject: [PATCH] fix(composer): improve contact suggestion layout and prevent
focus loss & duplicate recipients
---
src/screens/ComposeScreen.tsx | 55 +++++++++++++++++++++++++++--------
1 file changed, 43 insertions(+), 12 deletions(-)
diff --git a/src/screens/ComposeScreen.tsx b/src/screens/ComposeScreen.tsx
index 1dfda9ac..80f2a707 100644
--- a/src/screens/ComposeScreen.tsx
+++ b/src/screens/ComposeScreen.tsx
@@ -113,10 +113,11 @@ function RecipientChip({ recipient, onRemove }: { recipient: Recipient; onRemove
}
function SuggestionList({
- suggestions, onPick,
+ suggestions, onPick, onPressIn,
}: {
suggestions: Array<{ contact: ContactCard; name: string; email: string }>;
onPick: (s: { name: string; email: string }) => void;
+ onPressIn?: () => void;
}) {
const c = useColors();
const styles = React.useMemo(() => makeStyles(c), [c]);
@@ -127,6 +128,7 @@ function SuggestionList({
return (
onPick(s)}
style={({ pressed }) => [styles.suggestionRow, pressed && styles.suggestionRowPressed]}
>
@@ -343,6 +345,7 @@ export default function ComposeScreen({ route, navigation }: Props) {
});
const editorRef = React.useRef(null);
+ const isPickingSuggestion = React.useRef(false);
// Track inline-image placeholders that haven't yet been rewritten to cid:
// until send time. Maps cid → blobId/type/name/size.
const inlineRegistryRef = React.useRef
-
+
{t('email_composer.to', 'To')}
{toRecipients.map((r, i) => (
@@ -1011,8 +1015,13 @@ export default function ComposeScreen({ route, navigation }: Props) {
onChangeText={setToInput}
onFocus={() => setActiveField('to')}
onBlur={() => {
- addTyped();
- setTimeout(() => setActiveField((f) => (f === 'to' ? null : f)), 200);
+ setTimeout(() => {
+ if (!isPickingSuggestion.current) {
+ addTyped();
+ }
+ setActiveField((f) => (f === 'to' ? null : f));
+ isPickingSuggestion.current = false;
+ }, 200);
}}
onSubmitEditing={addTyped}
keyboardType="email-address"
@@ -1021,7 +1030,13 @@ export default function ComposeScreen({ route, navigation }: Props) {
/>
{activeField === 'to' && suggestions.length > 0 && (
-
+ {
+ isPickingSuggestion.current = true;
+ }}
+ />
)}
{!ccVisible && (
setCcVisible(true)} style={styles.ccToggle}>
@@ -1031,7 +1046,7 @@ export default function ComposeScreen({ route, navigation }: Props) {
{ccVisible && (
-
+
{t('email_composer.cc', 'Cc')}
{ccRecipients.map((r, i) => (
@@ -1049,8 +1064,13 @@ export default function ComposeScreen({ route, navigation }: Props) {
onChangeText={setCcInput}
onFocus={() => setActiveField('cc')}
onBlur={() => {
- addTyped();
- setTimeout(() => setActiveField((f) => (f === 'cc' ? null : f)), 200);
+ setTimeout(() => {
+ if (!isPickingSuggestion.current) {
+ addTyped();
+ }
+ setActiveField((f) => (f === 'cc' ? null : f));
+ isPickingSuggestion.current = false;
+ }, 200);
}}
onSubmitEditing={addTyped}
keyboardType="email-address"
@@ -1059,7 +1079,13 @@ export default function ComposeScreen({ route, navigation }: Props) {
/>
{activeField === 'cc' && suggestions.length > 0 && (
-
+ {
+ isPickingSuggestion.current = true;
+ }}
+ />
)}
)}
@@ -1332,6 +1358,7 @@ function makeStyles(c: ThemePalette) {
borderBottomWidth: 1,
borderBottomColor: c.borderLight,
gap: spacing.md,
+ position: 'relative',
},
fieldLabel: {
...typography.body,
@@ -1374,12 +1401,16 @@ function makeStyles(c: ThemePalette) {
paddingVertical: 6,
},
suggestionBox: {
- marginTop: spacing.xs,
- marginLeft: 60,
+ position: 'absolute',
+ top: '100%',
+ left: spacing.lg + 56 + spacing.md,
+ right: spacing.lg,
borderWidth: 1,
borderColor: c.borderLight,
borderRadius: radius.sm,
backgroundColor: c.card,
+ zIndex: 20,
+ elevation: 5,
overflow: 'hidden',
},
suggestionRow: {