diff --git a/.gitignore b/.gitignore index 6181bf68..b2919114 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /.pnp .pnp.js .pnpm-store +.omc # testing /coverage diff --git a/package.json b/package.json index d1481fbc..52096d9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vocechat-web", - "version": "0.9.78", + "version": "0.9.79", "homepage": "https://voce.chat", "dependencies": { "@metamask/onboarding": "^1.0.1", diff --git a/src/hooks/useVirtualKeyboard.ts b/src/hooks/useVirtualKeyboard.ts new file mode 100644 index 00000000..24586a38 --- /dev/null +++ b/src/hooks/useVirtualKeyboard.ts @@ -0,0 +1,49 @@ +import { useEffect } from "react"; + +const isIOS = () => + /iPad|iPhone|iPod/.test(navigator.userAgent) || + (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1); + +/** + * Listens to the VisualViewport API and writes the visible height as a CSS + * custom property (`--vh-visible`) on `document.documentElement`. + * + * On Android mobile browsers the virtual keyboard shrinks the visual viewport + * while `100vh` still refers to the full layout viewport, which causes + * bottom-pinned elements (like the chat input) to be hidden behind the + * keyboard or the browser chrome. + * + * iOS Safari handles this natively via scroll-into-view behaviour and + * `-webkit-fill-available`, so we skip it there to avoid the container + * being over-shrunk when the keyboard opens. + */ +export default function useVirtualKeyboard() { + useEffect(() => { + if (isIOS()) return; + + const vv = window.visualViewport; + + function update() { + const height = vv ? vv.height : window.innerHeight; + document.documentElement.style.setProperty("--vh-visible", `${height}px`); + } + + update(); + + if (vv) { + vv.addEventListener("resize", update); + vv.addEventListener("scroll", update); + } else { + window.addEventListener("resize", update); + } + + return () => { + if (vv) { + vv.removeEventListener("resize", update); + vv.removeEventListener("scroll", update); + } else { + window.removeEventListener("resize", update); + } + }; + }, []); +} diff --git a/src/routes/chat/index.tsx b/src/routes/chat/index.tsx index 92d9edab..2b09270e 100644 --- a/src/routes/chat/index.tsx +++ b/src/routes/chat/index.tsx @@ -8,6 +8,8 @@ import ChannelModal from "@/components/ChannelModal"; import ErrorCatcher from "@/components/ErrorCatcher"; import Server from "@/components/Server"; import UsersModal from "@/components/UsersModal"; +import useVirtualKeyboard from "@/hooks/useVirtualKeyboard"; +import { isIOS } from "@/utils"; import ChannelChat from "./ChannelChat"; import DMChat from "./DMChat"; import GuestBlankPlaceholder from "./GuestBlankPlaceholder"; @@ -19,6 +21,7 @@ import VoiceFullscreen from "./VoiceFullscreen"; import { shallowEqual } from "react-redux"; function ChatPage() { + useVirtualKeyboard(); const isHomePath = useMatch(`/`); const isChatHomePath = useMatch(`/chat`); const [sessionListVisible, setSessionListVisible] = useState(false); @@ -65,6 +68,8 @@ function ChatPage() { const contextId = (+channel_id || callingTo) ?? 0; console.log("fffff", channel_id, user_id, aside, channelChatVisible); + const mobileHeight = isIOS() ? "h-screen" : "h-[var(--vh-visible,100vh)]"; + return ( {channelModalVisible && ( @@ -73,7 +78,7 @@ function ChatPage() { {usersModalVisible && }
@@ -85,7 +90,7 @@ function ChatPage() { )}
diff --git a/src/routes/home/index.tsx b/src/routes/home/index.tsx index 4b4f7419..8a77dc0a 100644 --- a/src/routes/home/index.tsx +++ b/src/routes/home/index.tsx @@ -13,6 +13,8 @@ import Tooltip from "@/components/Tooltip"; import UnreadTabTip from "@/components/UnreadTabTip"; import Voice from "@/components/Voice"; import usePreload from "@/hooks/usePreload"; +import useVirtualKeyboard from "@/hooks/useVirtualKeyboard"; +import { isIOS } from "@/utils"; import FavIcon from "@/assets/icons/bookmark.svg"; import ChatIcon from "@/assets/icons/chat.svg"; import FolderIcon from "@/assets/icons/folder.svg"; @@ -23,6 +25,7 @@ import User from "./User"; import StreamStatus from "@/components/StreamStatus"; function HomePage() { + useVirtualKeyboard(); const { t } = useTranslation(); const dispatch = useDispatch(); const isHomePath = useMatch(`/`); @@ -66,7 +69,9 @@ function HomePage() { {!guest && }
{!guest && (
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); +export const isIOS = () => + /iPad|iPhone|iPod/.test(navigator.userAgent) || + (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1); + export const getLocalAuthData = () => { return { token: localStorage.getItem(KEY_TOKEN) || "",