From 654b51b45e79a4a797cc7db915b75a1aed02d5f7 Mon Sep 17 00:00:00 2001 From: chenhang Date: Fri, 3 Jul 2026 11:44:09 +0800 Subject: [PATCH] fix(keyboard): restrict input lift to target android devices --- .../chat/components/chat-input-bar.module.css | 7 +- src/app/chat/components/chat-input-bar.tsx | 6 +- .../__tests__/environment.test.ts | 64 +++++++++++++++++-- src/hooks/use-keyboard-height/constants.ts | 2 + src/hooks/use-keyboard-height/css-vars.ts | 9 ++- src/hooks/use-keyboard-height/environment.ts | 15 +++-- src/hooks/use-keyboard-height/types.ts | 4 +- 7 files changed, 86 insertions(+), 21 deletions(-) diff --git a/src/app/chat/components/chat-input-bar.module.css b/src/app/chat/components/chat-input-bar.module.css index 9dc12f0d..494a00bb 100644 --- a/src/app/chat/components/chat-input-bar.module.css +++ b/src/app/chat/components/chat-input-bar.module.css @@ -2,14 +2,19 @@ /* 键盘适配: * --chat-input-lift 是小米 Android + Facebook IAB 的专用抬升值。 + * --chat-input-lift-effective 只有目标设备标记存在时才读取抬升值。 * --app-safe-bottom 处理小米全面屏手势条 / iPhone home indicator。 * 正常浏览器中 --chat-input-lift 为 0,因此无视觉副作用。 */ +:global(html[data-keyboard-adapt="android-facebook-target"]) { + --chat-input-lift-effective: var(--chat-input-lift, 0px); +} + .bar { flex: 0 0 auto; padding: 0 calc(var(--chat-inline-padding, 16px) + var(--app-safe-right, 0px)) 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) ) calc(var(--chat-inline-padding, 16px) + var(--app-safe-left, 0px)); diff --git a/src/app/chat/components/chat-input-bar.tsx b/src/app/chat/components/chat-input-bar.tsx index 2ac242a0..b197f94a 100644 --- a/src/app/chat/components/chat-input-bar.tsx +++ b/src/app/chat/components/chat-input-bar.tsx @@ -20,7 +20,6 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) { const dispatch = useChatDispatch(); const [input, setInput] = useState(""); const [isFocused, setIsFocused] = useState(false); - const barRef = useRef(null); const textareaRef = useRef(null); const lastPointerSendAtRef = useRef(0); @@ -69,10 +68,7 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) { }; return ( -
+
{ makeEnvironment({ isXiaomiFamilyDevice: false, isOppoFamilyDevice: true, - usesAndroidFacebookFallback: true, + isTargetAndroidFacebookDevice: true, }), ), ).toBe(true); @@ -40,9 +43,10 @@ describe("shouldEnableKeyboardAdaptation", () => { shouldEnableKeyboardAdaptation( makeEnvironment({ isAndroid: false, + isIOS: true, isXiaomiFamilyDevice: false, isOppoFamilyDevice: false, - usesAndroidFacebookFallback: false, + isTargetAndroidFacebookDevice: false, }), ), ).toBe(false); @@ -52,7 +56,7 @@ describe("shouldEnableKeyboardAdaptation", () => { makeEnvironment({ inAppBrowserName: "instagram", isFacebookInAppBrowser: false, - usesAndroidFacebookFallback: false, + isTargetAndroidFacebookDevice: false, }), ), ).toBe(false); @@ -62,9 +66,55 @@ describe("shouldEnableKeyboardAdaptation", () => { makeEnvironment({ isXiaomiFamilyDevice: false, isOppoFamilyDevice: false, - usesAndroidFacebookFallback: false, + isTargetAndroidFacebookDevice: 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); + }); }); diff --git a/src/hooks/use-keyboard-height/constants.ts b/src/hooks/use-keyboard-height/constants.ts index 80d07752..a0804f60 100644 --- a/src/hooks/use-keyboard-height/constants.ts +++ b/src/hooks/use-keyboard-height/constants.ts @@ -1,4 +1,6 @@ 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 FALLBACK_INPUT_LIFT_PX = 320; diff --git a/src/hooks/use-keyboard-height/css-vars.ts b/src/hooks/use-keyboard-height/css-vars.ts index 95b9c64e..5ec07b8e 100644 --- a/src/hooks/use-keyboard-height/css-vars.ts +++ b/src/hooks/use-keyboard-height/css-vars.ts @@ -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 { document.documentElement.style.setProperty(INPUT_LIFT_VAR, `${lift}px`); + document.documentElement.dataset[KEYBOARD_ADAPT_DATA_ATTR] = + KEYBOARD_ADAPT_TARGET_VALUE; } export function resetInputLiftVar(): void { document.documentElement.style.setProperty(INPUT_LIFT_VAR, "0px"); + delete document.documentElement.dataset[KEYBOARD_ADAPT_DATA_ATTR]; } diff --git a/src/hooks/use-keyboard-height/environment.ts b/src/hooks/use-keyboard-height/environment.ts index 3c3bcd3f..8e67bee7 100644 --- a/src/hooks/use-keyboard-height/environment.ts +++ b/src/hooks/use-keyboard-height/environment.ts @@ -10,25 +10,30 @@ export function getKeyboardAdaptEnvironment( const isXiaomiFamilyDevice = PlatformDetector.isXiaomiFamilyDevice(ua); const isOppoFamilyDevice = PlatformDetector.isOppoFamilyDevice(ua); const isFacebookInAppBrowser = BrowserDetector.isFacebookInAppBrowser(ua); - const usesAndroidFacebookFallback = + const isTargetAndroidFacebookDevice = platform.isAndroid && + !platform.isIOS && (isXiaomiFamilyDevice || isOppoFamilyDevice) && isFacebookInAppBrowser; return { isAndroid: platform.isAndroid, + isIOS: platform.isIOS, inAppBrowserName: browser.inAppBrowserName, isXiaomiFamilyDevice, isOppoFamilyDevice, isFacebookInAppBrowser, - hasVisualViewport: - typeof window !== "undefined" && window.visualViewport != null, - usesAndroidFacebookFallback, + isTargetAndroidFacebookDevice, }; } export function shouldEnableKeyboardAdaptation( environment: KeyboardAdaptEnvironment, ): boolean { - return environment.usesAndroidFacebookFallback; + return ( + environment.isAndroid && + !environment.isIOS && + environment.isFacebookInAppBrowser && + (environment.isXiaomiFamilyDevice || environment.isOppoFamilyDevice) + ); } diff --git a/src/hooks/use-keyboard-height/types.ts b/src/hooks/use-keyboard-height/types.ts index 6abbf1ec..3cf764da 100644 --- a/src/hooks/use-keyboard-height/types.ts +++ b/src/hooks/use-keyboard-height/types.ts @@ -7,10 +7,10 @@ export interface UseKeyboardHeightOptions { export interface KeyboardAdaptEnvironment { isAndroid: boolean; + isIOS: boolean; inAppBrowserName: string | null; isXiaomiFamilyDevice: boolean; isOppoFamilyDevice: boolean; isFacebookInAppBrowser: boolean; - hasVisualViewport: boolean; - usesAndroidFacebookFallback: boolean; + isTargetAndroidFacebookDevice: boolean; }