Skip to content

Commit a50ca1c

Browse files
fix(mobile): the Android card no longer closes while focus returns from the model sheet
The Android card waited for keyboardWillShow, so after the model sheet restored focus it closed until the keyboard arrived. Close it only when a keyboard that showed since focus hides (Back), so a refocus waiting for its keyboard keeps it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent 9bb5ad2 commit a50ca1c

3 files changed

Lines changed: 13 additions & 13 deletions

File tree

‎apps/mobile/src/features/threads/ThreadComposer.tsx‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ export function useCompactComposerEditor(
151151
const [contentSize, setContentSize] = useState({ width: 0, height: 0 });
152152
const [slot, setSlot] = useState(EMPTY_COMPACT_COMPOSER_SLOT);
153153
const keyboardShowing = useAndroidKeyboardShowing();
154+
// Only a keyboard that showed since focus and then hid closes the card, so a refocus that
155+
// is still waiting for its keyboard (after the model sheet) keeps it open.
156+
const [keyboardShownSinceFocus, setKeyboardShownSinceFocus] = useState(false);
157+
if (keyboardShowing && !keyboardShownSinceFocus) setKeyboardShownSinceFocus(true);
154158
const contentHeight = contentSize.height;
155159
const rowWidth = useSharedValue(0);
156160
const editorX = useSharedValue(0);
@@ -161,8 +165,7 @@ export function useCompactComposerEditor(
161165
const { isExpanded, modelIconOnly, labelShownAtRest } = resolveCompactComposerLayout({
162166
...state,
163167
slot,
164-
isAndroid: Platform.OS === "android",
165-
keyboardShowing,
168+
keyboardDismissed: keyboardShownSinceFocus && !keyboardShowing,
166169
value,
167170
contentSize,
168171
lineHeight: text.lineHeight,
@@ -209,6 +212,7 @@ export function useCompactComposerEditor(
209212
);
210213
}
211214
},
215+
onEditorFocus: () => setKeyboardShownSinceFocus(false),
212216
onModelControlLayout: (event: { nativeEvent: { layout: { width: number } } }) => {
213217
const { width } = event.nativeEvent.layout;
214218
setSlot((current) =>
@@ -677,6 +681,7 @@ export const ThreadComposer = function ThreadComposer(props: ThreadComposerProps
677681
const onEditorFocusChange = props.onEditorFocusChange;
678682
const handleFocus = () => {
679683
setIsFocused(true);
684+
compactEditor.onEditorFocus();
680685
settingsSheetPresentation.onEditorFocusChange(true);
681686
onEditorFocusChange?.(true);
682687
setComposerEditing(true);

‎apps/mobile/src/features/threads/compactComposerLayout.test.ts‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ function composer(
1616
input: {
1717
readonly rowWidth?: number;
1818
readonly isFocused?: boolean;
19-
readonly isAndroid?: boolean;
20-
readonly keyboardVisible?: boolean;
19+
readonly keyboardDismissed?: boolean;
2120
readonly keepsSettingsOpen?: boolean;
2221
readonly value?: string;
2322
readonly contentSize?: { readonly width: number; readonly height: number };
@@ -34,8 +33,7 @@ function composer(
3433
attachmentCount: input.measuredAttachmentCount ?? input.attachmentCount ?? 0,
3534
},
3635
isFocused: input.isFocused ?? false,
37-
isAndroid: input.isAndroid ?? false,
38-
keyboardShowing: input.keyboardVisible ?? false,
36+
keyboardDismissed: input.keyboardDismissed ?? false,
3937
keepsSettingsOpen: input.keepsSettingsOpen ?? false,
4038
value: input.value ?? "",
4139
contentSize: input.contentSize ?? { width: 0, height: LINE_HEIGHT },
@@ -50,10 +48,8 @@ describe("compact composer layout", () => {
5048
it("opens the card with the model label when a narrow composer is focused", () => {
5149
expect(composer()).toEqual({ isExpanded: false, modelIconOnly: true });
5250
expect(composer({ isFocused: true })).toEqual({ isExpanded: true, modelIconOnly: false });
53-
expect(composer({ isFocused: true, isAndroid: true }).isExpanded).toBe(false);
54-
expect(composer({ isFocused: true, isAndroid: true, keyboardVisible: true }).isExpanded).toBe(
55-
true,
56-
);
51+
// Android's Back hides the keyboard but leaves the editor focused.
52+
expect(composer({ isFocused: true, keyboardDismissed: true }).isExpanded).toBe(false);
5753
expect(composer({ keepsSettingsOpen: true })).toEqual({
5854
isExpanded: true,
5955
modelIconOnly: false,

‎apps/mobile/src/features/threads/compactComposerLayout.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,8 @@ export function compactSlotAfterLabelLayout(
5252
export function resolveCompactComposerLayout(input: {
5353
readonly slot: CompactComposerSlot;
5454
readonly isFocused: boolean;
55-
readonly isAndroid: boolean;
5655
/** Android's Back hides the keyboard without blurring the editor. */
57-
readonly keyboardShowing: boolean;
56+
readonly keyboardDismissed: boolean;
5857
readonly keepsSettingsOpen: boolean;
5958
readonly value: string;
6059
readonly contentSize: { readonly width: number; readonly height: number };
@@ -74,7 +73,7 @@ export function resolveCompactComposerLayout(input: {
7473
const opensOnFocus =
7574
rowWidth > 0 &&
7675
!isWide &&
77-
((input.isFocused && (!input.isAndroid || input.keyboardShowing)) || input.keepsSettingsOpen);
76+
((input.isFocused && !input.keyboardDismissed) || input.keepsSettingsOpen);
7877
const wraps = input.contentSize.height > input.lineHeight * 1.5;
7978
// A line that fits the expanded editor may still overflow its compact slot.
8079
// Compare its measured width too, a character early, so widening cannot loop.

0 commit comments

Comments
 (0)