Skip to content

Commit

Permalink
fix(Keyboard): Fix keyboard on mobile (#922)
Browse files Browse the repository at this point in the history
  • Loading branch information
lgmarchi authored Dec 12, 2024
1 parent cebffc6 commit e2435ec
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 67 deletions.
42 changes: 30 additions & 12 deletions src/lib/components/ui/ContextMenu.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts" context="module">
import { Keyboard } from "@capacitor/keyboard"
// A close handler referencing the current open context menu
let close_context: any
</script>
Expand All @@ -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]
Expand All @@ -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
Expand All @@ -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]
}
Expand All @@ -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()
Expand All @@ -74,13 +98,7 @@

<slot name="content" open={openContext} />
{#if visible}
<div
id="context-menu"
data-cy={hook}
bind:this={context}
use:clickoutside
on:clickoutside={onClose}
style={`position: fixed; left: ${coords[0]}px; top: ${coords[1]}px;`}>
<div id="context-menu" data-cy={hook} bind:this={context} use:clickoutside on:clickoutside={onClose} style={`position: fixed; left: ${coords[0]}px; top: ${coords[1]}px;`}>
<slot name="items" close={onClose}></slot>
{#each items as item}
<Button
Expand Down
31 changes: 27 additions & 4 deletions src/lib/elements/Input/Input.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
<script lang="ts">
import { isAndroidOriOS } from "$lib/utils/Mobile"
import { InputRules } from "./inputRules"
import { Appearance } from "../../enums/index"
import { MarkdownEditor } from "markdown-editor"
import "./markdown.scss"
import { createEventDispatcher, onMount } from "svelte"
import { createEventDispatcher, onDestroy, onMount } from "svelte"
import { EditorView } from "@codemirror/view"
import { writable } from "svelte/store"
import Text from "../Text.svelte"
import { debounce } from "$lib/utils/Functions"
import type { PluginListenerHandle } from "@capacitor/core"
import { Keyboard } from "@capacitor/keyboard"
export let placeholder: string = ""
export let hook: string = ""
Expand Down Expand Up @@ -67,7 +70,7 @@
let onsend: any[] = []
let editor: MarkdownEditor
let mobileKeyboardWasAlreadyOpened = false
$: if (rich && $input && (!editor || !Array.from($input.parentNode!.children).some(el => el === editor.codemirror.dom))) {
if (editor) {
editor.codemirror.destroy()
Expand All @@ -84,7 +87,7 @@
editor.codemirror.dispatch({
selection: { head: line.to, anchor: line.to },
})
if (autoFocus) editor.codemirror.focus()
if ((autoFocus && !isAndroidOriOS()) || (autoFocus && mobileKeyboardWasAlreadyOpened) || isKeyboardOpened) editor.codemirror.focus()
// @ts-ignore
editor.updatePlaceholder(input.placeholder)
editor.registerListener("input", ({ value: val }: { value: string }) => {
Expand Down Expand Up @@ -134,6 +137,26 @@
input.addEventListener("mouseup", mouseupListener)
})
}
let mobileKeyboardListener01: PluginListenerHandle | undefined
let mobileKeyboardListener02: PluginListenerHandle | undefined
$: isKeyboardOpened = false
onMount(async () => {
mobileKeyboardListener01 = await Keyboard.addListener("keyboardWillShow", () => {
mobileKeyboardWasAlreadyOpened = true
isKeyboardOpened = true
})
mobileKeyboardListener02 = await Keyboard.addListener("keyboardWillHide", () => {
isKeyboardOpened = false
})
})
onDestroy(async () => {
if (mobileKeyboardListener01) await mobileKeyboardListener01.remove()
if (mobileKeyboardListener02) await mobileKeyboardListener02.remove()
})
</script>

{#key hook}
Expand Down Expand Up @@ -161,7 +184,7 @@
on:keydown={onKeyDown}
on:input={onInput}
on:blur={onBlur}
autofocus={autoFocus} />
autofocus={isAndroidOriOS() ? isKeyboardOpened : autoFocus} />
</div>
</div>
{#if errorMessage}
Expand Down
68 changes: 44 additions & 24 deletions src/lib/layouts/BottomNavBarMobile.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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!)!)
Expand All @@ -77,32 +93,36 @@
unsubscribeStore()
unsubscribeUIStore()
await mobileKeyboardListener01?.remove()
await mobileKeyboardListener02?.remove()
})
$: settings = SettingsStore.state
</script>

<div class="content"></div>

<div class="navigation {vertical ? 'vertical' : 'horizontal'} {icons ? 'icons' : ''}">
{#each routes as route}
<div class="navigation-control {!icons ? 'fill' : ''}">
<Button
hook="button-{route.name}"
badge={badgeCounts[route.to]}
fill={!icons}
tooltip={route.name}
icon={icons}
outline={activeRoute !== route.to && !icons}
appearance={activeRoute === route.to ? Appearance.Primary : Appearance.Alt}
on:click={() => handleNavigate(route)}>
<Icon alt={activeRoute === route.to} icon={route.icon} />
{#if !icons}
<Text appearance={activeRoute !== route.to ? Appearance.Default : Appearance.Alt}>{route.name}</Text>
{/if}
</Button>
</div>
{/each}
</div>
{#if !isKeyboardOpened}
<div class="content"></div>

<div class="navigation {vertical ? 'vertical' : 'horizontal'} {icons ? 'icons' : ''}">
{#each routes as route}
<div class="navigation-control {!icons ? 'fill' : ''}">
<Button
hook="button-{route.name}"
badge={badgeCounts[route.to]}
fill={!icons}
tooltip={route.name}
icon={icons}
outline={activeRoute !== route.to && !icons}
appearance={activeRoute === route.to ? Appearance.Primary : Appearance.Alt}
on:click={() => handleNavigate(route)}>
<Icon alt={activeRoute === route.to} icon={route.icon} />
{#if !icons}
<Text appearance={activeRoute !== route.to ? Appearance.Default : Appearance.Alt}>{route.name}</Text>
{/if}
</Button>
</div>
{/each}
</div>
{/if}

<style lang="scss">
.content {
Expand Down
54 changes: 50 additions & 4 deletions src/lib/layouts/Chatbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@
import { get, writable } from "svelte/store"
import { SettingsStore } from "$lib/state"
import { RaygunStoreInstance, type FileAttachment } from "$lib/wasm/RaygunStore"
import { createEventDispatcher, onMount } from "svelte"
import { createEventDispatcher, onDestroy, onMount } from "svelte"
import { ConversationStore } from "$lib/state/conversation"
import type { Chat, FileInfo, GiphyGif } from "$lib/types"
import { Message, PopupButton } from "$lib/components"
import { type Message as MessageType } from "$lib/types"
import { ProfilePicture } from "$lib/components"
import CombinedSelector from "$lib/components/messaging/CombinedSelector.svelte"
import { checkMobile } from "$lib/utils/Mobile"
import { checkMobile, isAndroidOriOS, isiOSMobile } from "$lib/utils/Mobile"
import { UIStore } from "$lib/state/ui"
import { emojiList, emojiRegexMap } from "$lib/components/messaging/emoji/EmojiList"
import { tempCDN } from "$lib/utils/CommonVariables"
import { MessageEvent } from "warp-wasm"
import StoreResolver from "$lib/components/utils/StoreResolver.svelte"
import MessageText from "$lib/components/messaging/message/MessageText.svelte"
import { Keyboard } from "@capacitor/keyboard"
import type { PluginListenerHandle } from "@capacitor/core"
export let replyTo: MessageType | undefined = undefined
export let emojiClickHook: (emoji: string) => boolean
Expand Down Expand Up @@ -54,6 +56,7 @@
}
async function sendMessage(text: string, isStickerOrGif: boolean = false) {
hackVariableToRefocusChatBar.set(Math.random().toString())
message.set("")
let filesSelected = get(Store.state.chatAttachmentsToSend)[activeChat.id]?.localFiles
let filesSelectedFromStorage: FileInfo[] = get(Store.state.chatAttachmentsToSend)[activeChat.id]?.storageFiles
Expand Down Expand Up @@ -157,12 +160,55 @@
return result
}
onMount(() => {
let mobileKeyboardListener01: PluginListenerHandle | undefined
let mobileKeyboardListener02: PluginListenerHandle | undefined
$: isMobileKeyboardOpened = false
onMount(async () => {
hackVariableToRefocusChatBar.set(Math.random().toString())
if (isAndroidOriOS()) {
let chat = get(Store.state.activeChat)
const chatbar = document.getElementById(`chatbar-container-${chat.id}`)
chatbar?.addEventListener("click", _ => {
if (isMobileKeyboardOpened && !$emojiSelectorOpen) {
hackVariableToRefocusChatBar.set(Math.random().toString())
}
})
mobileKeyboardListener01 = await Keyboard.addListener("keyboardWillShow", info => {
if (isiOSMobile()) {
isMobileKeyboardOpened = true
let chat = get(Store.state.activeChat)
const chatbar = document.getElementById(`chatbar-container-${chat.id}`)
if (chatbar) {
chatbar.style.marginBottom = `${info.keyboardHeight - 30}px`
}
}
})
mobileKeyboardListener02 = await Keyboard.addListener("keyboardWillHide", () => {
if (isiOSMobile()) {
isMobileKeyboardOpened = false
let chat = get(Store.state.activeChat)
const chatbar = document.getElementById(`chatbar-container-${chat.id}`)
if (chatbar) {
chatbar.style.marginBottom = `0px`
}
}
})
}
})
onDestroy(() => {
if (isAndroidOriOS()) {
mobileKeyboardListener01?.remove()
mobileKeyboardListener02?.remove()
}
})
</script>

<div class="chatbar" data-cy="chatbar" id={activeChat.id}>
<div class="chatbar" data-cy="chatbar" id={`chatbar-container-${activeChat.id}`}>
<Controls>
<slot name="pre-controls"></slot>
</Controls>
Expand Down
21 changes: 20 additions & 1 deletion src/lib/layouts/login/NewAccount.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import { CommonInputRules } from "$lib/utils/CommonInputRules"
import { LoginPage } from "$lib/layouts/login"
import FileUploadButton from "$lib/components/ui/FileUploadButton.svelte"
import { get } from "svelte/store"
import { Keyboard } from "@capacitor/keyboard"
import { isiOSMobile } from "$lib/utils/Mobile"
import { onDestroy } from "svelte"
export let page: LoginPage
export let username = ""
Expand All @@ -23,6 +25,23 @@
async function updateProfilePicture(picture: string) {
profilePicture = picture
}
Keyboard.addListener("keyboardWillShow", info => {
const pageId = document.getElementById("auth-recover")
if (pageId && isiOSMobile()) {
pageId.style.marginBottom = `${info.keyboardHeight}px`
}
})
Keyboard.addListener("keyboardWillHide", () => {
const pageId = document.getElementById("auth-recover")
if (pageId && isiOSMobile()) {
pageId.style.marginBottom = `0px`
}
})
onDestroy(() => {
Keyboard.removeAllListeners()
})
</script>

<div id="auth-recover">
Expand Down
7 changes: 7 additions & 0 deletions src/lib/utils/Mobile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export function isAndroidOriOS(): boolean {
return platform === "ios" || platform === "android"
}

export function isiOSMobile(): boolean {
if (platform === null) {
log.warn("Platform info not yet loaded. Assuming 'false'.")
return false
}
return platform === "ios"
}
export function isAndroid(): boolean {
if (platform === null) {
log.warn("Platform info not yet loaded. Assuming 'false'.")
Expand Down
Loading

0 comments on commit e2435ec

Please sign in to comment.