Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Keyboard): Fix keyboard on mobile #922

Merged
merged 20 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0d6572f
feat(Mobile-Navigation): Hide bottomNavBar when keyboard is opened
lgmarchi Dec 5, 2024
726d493
feat(Mobile): Enhance keyboard handling in chatbar and bottom navigat…
lgmarchi Dec 5, 2024
d74f91e
feat(Login): Adjust layout for iOS keyboard visibility in NewAccount …
lgmarchi Dec 5, 2024
9879dab
feat(Chatbar): Improve keyboard handling for iOS in Chatbar and NewAc…
lgmarchi Dec 5, 2024
7a38075
feat(Chat): Update keyboard listener subscription to use sidebar stat…
lgmarchi Dec 5, 2024
b12f4f3
feat(Keyboard): Adjust keyboard resize mode for iOS and improve chatb…
lgmarchi Dec 5, 2024
a35aa06
feat(Profile): Implement keyboard handling for iOS to ensure focused …
lgmarchi Dec 5, 2024
f8351af
feat(Keyboard): Conditionally hide keyboard on sidebar swipe for Andr…
lgmarchi Dec 5, 2024
eb9e259
Merge branch 'dev' into fix-keyboard-on-mobile
stavares843 Dec 5, 2024
241ac23
Merge branch 'dev' into fix-keyboard-on-mobile
phillsatellite Dec 6, 2024
af622de
Merge remote-tracking branch 'origin/dev' into fix-keyboard-on-mobile
lgmarchi Dec 6, 2024
def18cc
Merge branch 'dev' into fix-keyboard-on-mobile
lgmarchi Dec 6, 2024
72890f2
Merge branch 'dev' into fix-keyboard-on-mobile
phillsatellite Dec 9, 2024
ffd3d40
Merge branch 'dev' into fix-keyboard-on-mobile
phillsatellite Dec 9, 2024
8256f78
feat(Keyboard): Keep keyboard opened when after send a message
lgmarchi Dec 10, 2024
dc07324
fix(Chatbar): adjust autoFocus behavior for Android and iOS devices, …
lgmarchi Dec 10, 2024
9302396
fix(Chatbar): update autoFocus behavior for mobile devices to improve…
lgmarchi Dec 10, 2024
1c4b50b
Merge branch 'dev' into fix-keyboard-on-mobile
phillsatellite Dec 10, 2024
5137d38
feat(Keyboard): Enhance keyboard handling for mobile devices to impro…
lgmarchi Dec 12, 2024
d83f201
feat(ContextMenu, Input, Chatbar): Enhance keyboard handling for mobi…
lgmarchi Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading