fix(keyboard): restrict input lift to target android devices

This commit is contained in:
2026-07-03 11:44:09 +08:00
parent f5b3c9fb2b
commit 654b51b45e
7 changed files with 86 additions and 21 deletions
@@ -2,14 +2,19 @@
/* 键盘适配: /* 键盘适配:
* --chat-input-lift 是小米 Android + Facebook IAB 的专用抬升值。 * --chat-input-lift 是小米 Android + Facebook IAB 的专用抬升值。
* --chat-input-lift-effective 只有目标设备标记存在时才读取抬升值。
* --app-safe-bottom 处理小米全面屏手势条 / iPhone home indicator。 * --app-safe-bottom 处理小米全面屏手势条 / iPhone home indicator。
* 正常浏览器中 --chat-input-lift 为 0,因此无视觉副作用。 */ * 正常浏览器中 --chat-input-lift 为 0,因此无视觉副作用。 */
:global(html[data-keyboard-adapt="android-facebook-target"]) {
--chat-input-lift-effective: var(--chat-input-lift, 0px);
}
.bar { .bar {
flex: 0 0 auto; flex: 0 0 auto;
padding: 0 padding: 0
calc(var(--chat-inline-padding, 16px) + var(--app-safe-right, 0px)) calc(var(--chat-inline-padding, 16px) + var(--app-safe-right, 0px))
calc( calc(
var(--spacing-lg, 16px) + var(--chat-input-lift, 0px) + var(--spacing-lg, 16px) + var(--chat-input-lift-effective, 0px) +
var(--app-safe-bottom, 0px) var(--app-safe-bottom, 0px)
) )
calc(var(--chat-inline-padding, 16px) + var(--app-safe-left, 0px)); calc(var(--chat-inline-padding, 16px) + var(--app-safe-left, 0px));
+1 -5
View File
@@ -20,7 +20,6 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) {
const dispatch = useChatDispatch(); const dispatch = useChatDispatch();
const [input, setInput] = useState(""); const [input, setInput] = useState("");
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
const barRef = useRef<HTMLDivElement>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const lastPointerSendAtRef = useRef(0); const lastPointerSendAtRef = useRef(0);
@@ -69,10 +68,7 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) {
}; };
return ( return (
<div <div className={`${styles.bar} ${isFocused ? styles.barFocused : ""}`}>
ref={barRef}
className={`${styles.bar} ${isFocused ? styles.barFocused : ""}`}
>
<div className={`${styles.row} ${isFocused ? styles.rowFocused : ""}`}> <div className={`${styles.row} ${isFocused ? styles.rowFocused : ""}`}>
<ChatInputTextField <ChatInputTextField
ref={textareaRef} ref={textareaRef}
@@ -1,6 +1,9 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { shouldEnableKeyboardAdaptation } from "../environment"; import {
getKeyboardAdaptEnvironment,
shouldEnableKeyboardAdaptation,
} from "../environment";
import type { KeyboardAdaptEnvironment } from "../types"; import type { KeyboardAdaptEnvironment } from "../types";
function makeEnvironment( function makeEnvironment(
@@ -8,12 +11,12 @@ function makeEnvironment(
): KeyboardAdaptEnvironment { ): KeyboardAdaptEnvironment {
return { return {
isAndroid: true, isAndroid: true,
isIOS: false,
inAppBrowserName: "facebook", inAppBrowserName: "facebook",
isXiaomiFamilyDevice: true, isXiaomiFamilyDevice: true,
isOppoFamilyDevice: false, isOppoFamilyDevice: false,
isFacebookInAppBrowser: true, isFacebookInAppBrowser: true,
hasVisualViewport: true, isTargetAndroidFacebookDevice: true,
usesAndroidFacebookFallback: true,
...overrides, ...overrides,
}; };
} }
@@ -29,7 +32,7 @@ describe("shouldEnableKeyboardAdaptation", () => {
makeEnvironment({ makeEnvironment({
isXiaomiFamilyDevice: false, isXiaomiFamilyDevice: false,
isOppoFamilyDevice: true, isOppoFamilyDevice: true,
usesAndroidFacebookFallback: true, isTargetAndroidFacebookDevice: true,
}), }),
), ),
).toBe(true); ).toBe(true);
@@ -40,9 +43,10 @@ describe("shouldEnableKeyboardAdaptation", () => {
shouldEnableKeyboardAdaptation( shouldEnableKeyboardAdaptation(
makeEnvironment({ makeEnvironment({
isAndroid: false, isAndroid: false,
isIOS: true,
isXiaomiFamilyDevice: false, isXiaomiFamilyDevice: false,
isOppoFamilyDevice: false, isOppoFamilyDevice: false,
usesAndroidFacebookFallback: false, isTargetAndroidFacebookDevice: false,
}), }),
), ),
).toBe(false); ).toBe(false);
@@ -52,7 +56,7 @@ describe("shouldEnableKeyboardAdaptation", () => {
makeEnvironment({ makeEnvironment({
inAppBrowserName: "instagram", inAppBrowserName: "instagram",
isFacebookInAppBrowser: false, isFacebookInAppBrowser: false,
usesAndroidFacebookFallback: false, isTargetAndroidFacebookDevice: false,
}), }),
), ),
).toBe(false); ).toBe(false);
@@ -62,9 +66,55 @@ describe("shouldEnableKeyboardAdaptation", () => {
makeEnvironment({ makeEnvironment({
isXiaomiFamilyDevice: false, isXiaomiFamilyDevice: false,
isOppoFamilyDevice: false, isOppoFamilyDevice: false,
usesAndroidFacebookFallback: false, isTargetAndroidFacebookDevice: false,
}), }),
), ),
).toBe(false); ).toBe(false);
}); });
it("disables for iOS Facebook even when Facebook IAB is detected", () => {
expect(
shouldEnableKeyboardAdaptation(
makeEnvironment({
isAndroid: false,
isIOS: true,
inAppBrowserName: "facebook",
isXiaomiFamilyDevice: false,
isOppoFamilyDevice: false,
isFacebookInAppBrowser: true,
isTargetAndroidFacebookDevice: false,
}),
),
).toBe(false);
});
it("does not trust target flag without the Android target device fields", () => {
expect(
shouldEnableKeyboardAdaptation(
makeEnvironment({
isAndroid: false,
isIOS: true,
isFacebookInAppBrowser: true,
isXiaomiFamilyDevice: true,
isTargetAndroidFacebookDevice: true,
}),
),
).toBe(false);
});
it("detects iPhone Facebook in-app browser as non-target environment", () => {
const environment = getKeyboardAdaptEnvironment(
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) " +
"AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 " +
"[FBAN/FBIOS;FBAV/466.0.0.49.109;FBBV/605000000;FBDV/iPhone15,2]",
);
expect(environment).toMatchObject({
isAndroid: false,
isIOS: true,
isFacebookInAppBrowser: true,
isTargetAndroidFacebookDevice: false,
});
expect(shouldEnableKeyboardAdaptation(environment)).toBe(false);
});
}); });
@@ -1,4 +1,6 @@
export const INPUT_LIFT_VAR = "--chat-input-lift"; export const INPUT_LIFT_VAR = "--chat-input-lift";
export const KEYBOARD_ADAPT_DATA_ATTR = "keyboardAdapt";
export const KEYBOARD_ADAPT_TARGET_VALUE = "android-facebook-target";
export const FOCUS_GAP_PX = 14; export const FOCUS_GAP_PX = 14;
export const FALLBACK_INPUT_LIFT_PX = 320; export const FALLBACK_INPUT_LIFT_PX = 320;
+8 -1
View File
@@ -1,9 +1,16 @@
import { INPUT_LIFT_VAR } from "./constants"; import {
INPUT_LIFT_VAR,
KEYBOARD_ADAPT_DATA_ATTR,
KEYBOARD_ADAPT_TARGET_VALUE,
} from "./constants";
export function setInputLiftVar(lift: number): void { export function setInputLiftVar(lift: number): void {
document.documentElement.style.setProperty(INPUT_LIFT_VAR, `${lift}px`); document.documentElement.style.setProperty(INPUT_LIFT_VAR, `${lift}px`);
document.documentElement.dataset[KEYBOARD_ADAPT_DATA_ATTR] =
KEYBOARD_ADAPT_TARGET_VALUE;
} }
export function resetInputLiftVar(): void { export function resetInputLiftVar(): void {
document.documentElement.style.setProperty(INPUT_LIFT_VAR, "0px"); document.documentElement.style.setProperty(INPUT_LIFT_VAR, "0px");
delete document.documentElement.dataset[KEYBOARD_ADAPT_DATA_ATTR];
} }
+10 -5
View File
@@ -10,25 +10,30 @@ export function getKeyboardAdaptEnvironment(
const isXiaomiFamilyDevice = PlatformDetector.isXiaomiFamilyDevice(ua); const isXiaomiFamilyDevice = PlatformDetector.isXiaomiFamilyDevice(ua);
const isOppoFamilyDevice = PlatformDetector.isOppoFamilyDevice(ua); const isOppoFamilyDevice = PlatformDetector.isOppoFamilyDevice(ua);
const isFacebookInAppBrowser = BrowserDetector.isFacebookInAppBrowser(ua); const isFacebookInAppBrowser = BrowserDetector.isFacebookInAppBrowser(ua);
const usesAndroidFacebookFallback = const isTargetAndroidFacebookDevice =
platform.isAndroid && platform.isAndroid &&
!platform.isIOS &&
(isXiaomiFamilyDevice || isOppoFamilyDevice) && (isXiaomiFamilyDevice || isOppoFamilyDevice) &&
isFacebookInAppBrowser; isFacebookInAppBrowser;
return { return {
isAndroid: platform.isAndroid, isAndroid: platform.isAndroid,
isIOS: platform.isIOS,
inAppBrowserName: browser.inAppBrowserName, inAppBrowserName: browser.inAppBrowserName,
isXiaomiFamilyDevice, isXiaomiFamilyDevice,
isOppoFamilyDevice, isOppoFamilyDevice,
isFacebookInAppBrowser, isFacebookInAppBrowser,
hasVisualViewport: isTargetAndroidFacebookDevice,
typeof window !== "undefined" && window.visualViewport != null,
usesAndroidFacebookFallback,
}; };
} }
export function shouldEnableKeyboardAdaptation( export function shouldEnableKeyboardAdaptation(
environment: KeyboardAdaptEnvironment, environment: KeyboardAdaptEnvironment,
): boolean { ): boolean {
return environment.usesAndroidFacebookFallback; return (
environment.isAndroid &&
!environment.isIOS &&
environment.isFacebookInAppBrowser &&
(environment.isXiaomiFamilyDevice || environment.isOppoFamilyDevice)
);
} }
+2 -2
View File
@@ -7,10 +7,10 @@ export interface UseKeyboardHeightOptions {
export interface KeyboardAdaptEnvironment { export interface KeyboardAdaptEnvironment {
isAndroid: boolean; isAndroid: boolean;
isIOS: boolean;
inAppBrowserName: string | null; inAppBrowserName: string | null;
isXiaomiFamilyDevice: boolean; isXiaomiFamilyDevice: boolean;
isOppoFamilyDevice: boolean; isOppoFamilyDevice: boolean;
isFacebookInAppBrowser: boolean; isFacebookInAppBrowser: boolean;
hasVisualViewport: boolean; isTargetAndroidFacebookDevice: boolean;
usesAndroidFacebookFallback: boolean;
} }