diff --git a/src/lib/components/ui/ContextMenu.svelte b/src/lib/components/ui/ContextMenu.svelte index e0c591f84..42b09ac61 100644 --- a/src/lib/components/ui/ContextMenu.svelte +++ b/src/lib/components/ui/ContextMenu.svelte @@ -1,4 +1,5 @@ @@ -9,8 +10,10 @@ import { clickoutside } from "@svelte-put/clickoutside" import { Appearance } from "$lib/enums" import type { ContextItem } from "$lib/types" - import { createEventDispatcher, tick } from "svelte" + import { createEventDispatcher, onMount, tick } from "svelte" import { log } from "$lib/utils/Logger" + import type { PluginListenerHandle } from "@capacitor/core" + import { isAndroidOriOS } from "$lib/utils/Mobile" let visible: boolean = false let coords: [number, number] = [0, 0] @@ -31,7 +34,7 @@ const { width, height } = context.getBoundingClientRect() const offsetX = evt.pageX - const offsetY = evt.pageY + const offsetY = evt.pageY - keyboardHeight / 2.5 const screenWidth = evt.view!.innerWidth const screenHeight = evt.view!.innerHeight @@ -42,9 +45,7 @@ // Calculate Y position, prioritizing space above the cursor if not enough below const adjustedY = offsetY - height - const topY = screenHeight - offsetY < height + 30 - ? Math.max(5, adjustedY) - : Math.max(5, overFlowY ? offsetY - height : offsetY) + const topY = screenHeight - offsetY < height + 30 ? Math.max(5, adjustedY) : Math.max(5, overFlowY ? offsetY - height : offsetY) return [topX, topY] } @@ -60,6 +61,29 @@ await tick() coords = calculatePos(evt) } + let keyboardHeight = 0 + onMount(() => { + let mobileKeyboardListener01: PluginListenerHandle | undefined + let mobileKeyboardListener02: PluginListenerHandle | undefined + + async function setupListeners() { + mobileKeyboardListener01 = await Keyboard.addListener("keyboardWillShow", info => { + keyboardHeight = info.keyboardHeight + }) + + mobileKeyboardListener02 = await Keyboard.addListener("keyboardWillHide", () => { + keyboardHeight = 0 + }) + } + if (isAndroidOriOS()) { + setupListeners() + } + + return () => { + if (mobileKeyboardListener01) mobileKeyboardListener01.remove() + if (mobileKeyboardListener02) mobileKeyboardListener02.remove() + } + }) function handleItemClick(e: MouseEvent, item: ContextItem) { e.stopPropagation() @@ -74,13 +98,7 @@ {#if visible} -
+
{#each items as item}
{#if errorMessage} diff --git a/src/lib/layouts/BottomNavBarMobile.svelte b/src/lib/layouts/BottomNavBarMobile.svelte index b74b60e9b..5ae627f75 100644 --- a/src/lib/layouts/BottomNavBarMobile.svelte +++ b/src/lib/layouts/BottomNavBarMobile.svelte @@ -7,8 +7,10 @@ import { UIStore } from "$lib/state/ui" import type { FriendRequest, NavRoute } from "$lib/types" import { checkMobile, isAndroidOriOS } from "$lib/utils/Mobile" - import { createEventDispatcher, onDestroy } from "svelte" + import { createEventDispatcher, onDestroy, onMount } from "svelte" import { get } from "svelte/store" + import { Keyboard } from "@capacitor/keyboard" + import type { PluginListenerHandle } from "@capacitor/core" export let routes: NavRoute[] = [] export let activeRoute: Route | SettingsRoute | CommunitySettingsRoute = Route.Home @@ -67,8 +69,22 @@ if (route.to === Route.Settings) return true } + let mobileKeyboardListener01: PluginListenerHandle | undefined + let mobileKeyboardListener02: PluginListenerHandle | undefined + $: isKeyboardOpened = false + + onMount(async () => { + mobileKeyboardListener01 = await Keyboard.addListener("keyboardWillShow", () => { + isKeyboardOpened = true + }) + + mobileKeyboardListener02 = await Keyboard.addListener("keyboardWillHide", () => { + isKeyboardOpened = false + }) + }) + // Clean up subscriptions when component is destroyed - onDestroy(() => { + onDestroy(async () => { setTimeout(() => { if (get(Store.state.activeCall)) { Store.setActiveCall(Store.getCallingChat(VoiceRTCInstance.channel!)!) @@ -77,32 +93,36 @@ unsubscribeStore() unsubscribeUIStore() + await mobileKeyboardListener01?.remove() + await mobileKeyboardListener02?.remove() }) $: settings = SettingsStore.state -
- - +{#if !isKeyboardOpened} +
+ + +{/if} `} diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte index cccf2da9f..f31be6537 100644 --- a/src/routes/chat/+page.svelte +++ b/src/routes/chat/+page.svelte @@ -5,22 +5,7 @@ import { animationDuration } from "$lib/globals/animations" import { slide } from "svelte/transition" import { Chatbar, Sidebar, Topbar, Profile } from "$lib/layouts" - import { - ImageEmbed, - ChatPreview, - Conversation, - Message as MessageComponent, - MessageGroup, - MessageReactions, - MessageReplyContainer, - ProfilePicture, - Modal, - ProfilePictureMany, - ChatFilter, - ContextMenu, - EmojiGroup, - ChatIcon, - } from "$lib/components" + import { ImageEmbed, ChatPreview, Conversation, Message as MessageComponent, MessageGroup, MessageReactions, MessageReplyContainer, Modal, ChatFilter, ContextMenu, EmojiGroup, ChatIcon } from "$lib/components" import CreateTransaction from "$lib/components/wallet/CreateTransaction.svelte" import { Button, FileInput, Icon, Label, Text } from "$lib/elements" import CallScreen from "$lib/components/calling/CallScreen.svelte" @@ -49,7 +34,7 @@ import { debounce, getTimeAgo } from "$lib/utils/Functions" import Controls from "$lib/layouts/Controls.svelte" import { tempCDN } from "$lib/utils/CommonVariables" - import { checkMobile, isAndroidOriOS } from "$lib/utils/Mobile" + import { checkMobile, isAndroidOriOS, isiOSMobile } from "$lib/utils/Mobile" import BrowseFiles from "../files/BrowseFiles.svelte" import AttachmentRenderer from "$lib/components/messaging/AttachmentRenderer.svelte" import ShareFile from "$lib/components/files/ShareFile.svelte" @@ -57,6 +42,7 @@ import AddMembers from "$lib/components/group/AddMembers.svelte" import { routes } from "$lib/defaults/routes" import BottomNavBarMobile from "$lib/layouts/BottomNavBarMobile.svelte" + import { Keyboard } from "@capacitor/keyboard" enum Permission { UNDEFINED, diff --git a/src/routes/settings/profile/+page.svelte b/src/routes/settings/profile/+page.svelte index 55dffe412..e305c7983 100644 --- a/src/routes/settings/profile/+page.svelte +++ b/src/routes/settings/profile/+page.svelte @@ -23,6 +23,9 @@ import { log } from "$lib/utils/Logger" import Modal from "$lib/components/ui/Modal.svelte" import PinInput from "$lib/components/PinInput.svelte" + import { isiOSMobile } from "$lib/utils/Mobile" + import { Keyboard } from "@capacitor/keyboard" + import type { PluginListenerHandle } from "@capacitor/core" enum SeedState { Hidden, @@ -173,14 +176,31 @@ let statusMessage: string = { ...get(Store.state.user) }.profile.status_message let seedWarning = false - onMount(() => { + let mobileKeyboardListener: PluginListenerHandle | undefined + + onMount(async () => { userReference = { ...get(Store.state.user) } statusMessage = { ...get(Store.state.user) }.profile.status_message + + if (isiOSMobile()) { + mobileKeyboardListener = await Keyboard.addListener("keyboardWillShow", _ => { + const focusedElement = document.activeElement + if (focusedElement && (focusedElement.tagName === "INPUT" || focusedElement.tagName === "TEXTAREA")) { + setTimeout(() => { + focusedElement.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + }, 100) + } + }) + } }) - onDestroy(() => { + onDestroy(async () => { Store.setUsername(userReference.name) Store.setStatusMessage(userReference.profile.status_message) + await mobileKeyboardListener?.remove() }) $: user = Store.state.user @@ -352,7 +372,7 @@ {/if} -
+