mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 08:07:11 +00:00
fix: open the keyboard in the body when replying on Android
This commit is contained in:
@@ -137,6 +137,19 @@ const RichTextEditor = React.forwardRef<RichTextEditorHandle, Props>(function Ri
|
|||||||
// posts its first message, then replay in order.
|
// posts its first message, then replay in order.
|
||||||
const readyRef = React.useRef(false);
|
const readyRef = React.useRef(false);
|
||||||
const queuedRef = React.useRef<string[]>([]);
|
const queuedRef = React.useRef<string[]>([]);
|
||||||
|
// Focusing the editor in the page only moves the caret there. Android
|
||||||
|
// opens the keyboard, and takes key focus off the composer's first button
|
||||||
|
// (Close: a hardware Space shut the composer), only for a WebView that holds
|
||||||
|
// native focus. iOS has keyboardDisplayRequiresUserAction for this.
|
||||||
|
const wantsNativeFocusRef = React.useRef(false);
|
||||||
|
const requestNativeFocus = React.useCallback(() => {
|
||||||
|
if (Platform.OS !== 'android') return;
|
||||||
|
if (!readyRef.current) {
|
||||||
|
wantsNativeFocusRef.current = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
webViewRef.current?.requestFocus();
|
||||||
|
}, []);
|
||||||
const call = React.useCallback((script: string) => {
|
const call = React.useCallback((script: string) => {
|
||||||
if (!readyRef.current) {
|
if (!readyRef.current) {
|
||||||
queuedRef.current.push(script);
|
queuedRef.current.push(script);
|
||||||
@@ -203,15 +216,17 @@ const RichTextEditor = React.forwardRef<RichTextEditorHandle, Props>(function Ri
|
|||||||
call(`window.__rne && window.__rne.getHtml(${id})`);
|
call(`window.__rne && window.__rne.getHtml(${id})`);
|
||||||
}),
|
}),
|
||||||
focusEnd: () => {
|
focusEnd: () => {
|
||||||
|
requestNativeFocus();
|
||||||
call(`window.__rne && window.__rne.focusEnd()`);
|
call(`window.__rne && window.__rne.focusEnd()`);
|
||||||
},
|
},
|
||||||
focus: () => {
|
focus: () => {
|
||||||
|
requestNativeFocus();
|
||||||
call(`window.__rne && window.__rne.focus()`);
|
call(`window.__rne && window.__rne.focus()`);
|
||||||
},
|
},
|
||||||
blur: () => {
|
blur: () => {
|
||||||
call(`document.activeElement && document.activeElement.blur()`);
|
call(`document.activeElement && document.activeElement.blur()`);
|
||||||
},
|
},
|
||||||
}), [call]);
|
}), [call, requestNativeFocus]);
|
||||||
|
|
||||||
const onMessage = (event: WebViewMessageEvent) => {
|
const onMessage = (event: WebViewMessageEvent) => {
|
||||||
let data: { type: string; payload: unknown };
|
let data: { type: string; payload: unknown };
|
||||||
@@ -225,6 +240,12 @@ const RichTextEditor = React.forwardRef<RichTextEditorHandle, Props>(function Ri
|
|||||||
perfEndAfterPaint('compose-open');
|
perfEndAfterPaint('compose-open');
|
||||||
const queued = queuedRef.current;
|
const queued = queuedRef.current;
|
||||||
queuedRef.current = [];
|
queuedRef.current = [];
|
||||||
|
// A focus asked for before the page was up (a reply opening) gets the
|
||||||
|
// view's native focus first, then the caret from the queued script.
|
||||||
|
if (wantsNativeFocusRef.current) {
|
||||||
|
wantsNativeFocusRef.current = false;
|
||||||
|
webViewRef.current?.requestFocus();
|
||||||
|
}
|
||||||
for (const script of queued) webViewRef.current?.injectJavaScript(`${script}; true;`);
|
for (const script of queued) webViewRef.current?.injectJavaScript(`${script}; true;`);
|
||||||
}
|
}
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
// Reply opened with no keyboard on Android and focus on the composer's
|
||||||
|
// Close button (a hardware Space shut the composer). 8bb7b607 focused the
|
||||||
|
// editor inside its page only; an Android WebView takes the keyboard and
|
||||||
|
// key focus only with native view focus, which nothing gave it. The suite
|
||||||
|
// has no React Native renderer, so this checks the editor's wiring.
|
||||||
|
|
||||||
|
const src = fs.readFileSync(path.resolve(__dirname, '..', '..', 'components/RichTextEditor.tsx'), 'utf8');
|
||||||
|
|
||||||
|
describe('rich text editor focus', () => {
|
||||||
|
it('gives the WebView native focus before focusing the editor in the page', () => {
|
||||||
|
const handle = src.slice(src.indexOf('focusEnd: () =>'), src.indexOf('blur: () =>'));
|
||||||
|
expect(handle).toMatch(/focusEnd: \(\) => \{\s+requestNativeFocus\(\);\s+call\(`window\.__rne && window\.__rne\.focusEnd\(\)`\);/);
|
||||||
|
expect(handle).toMatch(/focus: \(\) => \{\s+requestNativeFocus\(\);\s+call\(`window\.__rne && window\.__rne\.focus\(\)`\);/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('asks for native focus on Android, and holds the request until the page is up', () => {
|
||||||
|
const fn = src.slice(src.indexOf('const requestNativeFocus'), src.indexOf('const call = '));
|
||||||
|
expect(fn).toMatch(/if \(Platform\.OS !== 'android'\) return;/);
|
||||||
|
expect(fn).toMatch(/if \(!readyRef\.current\) \{\s+wantsNativeFocusRef\.current = true;\s+return;\s+\}\s+webViewRef\.current\?\.requestFocus\(\);/);
|
||||||
|
const ready = src.slice(src.indexOf('if (!readyRef.current) {\r\n readyRef.current = true;'.replace(/\r\n/g, src.includes('\r\n') ? '\r\n' : '\n')));
|
||||||
|
expect(ready).toMatch(/if \(wantsNativeFocusRef\.current\) \{\s+wantsNativeFocusRef\.current = false;\s+webViewRef\.current\?\.requestFocus\(\);\s+\}\s+for \(const script of queued\)/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user