fix(keyboard): restrict input lift to target android devices
This commit is contained in:
@@ -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));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user