Skip to content

Commit

Permalink
feat(Keyboard): Enhance keyboard handling for mobile devices to impro…
Browse files Browse the repository at this point in the history
…ve user experience
  • Loading branch information
lgmarchi committed Dec 12, 2024
1 parent 1c4b50b commit 5137d38
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 79 deletions.
28 changes: 25 additions & 3 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 @@ -84,7 +87,7 @@
editor.codemirror.dispatch({
selection: { head: line.to, anchor: line.to },
})
if (autoFocus) editor.codemirror.focus()
if ((autoFocus && !isAndroidOriOS()) || isKeyboardOpened) editor.codemirror.focus()
// @ts-ignore
editor.updatePlaceholder(input.placeholder)
editor.registerListener("input", ({ value: val }: { value: string }) => {
Expand Down Expand Up @@ -134,6 +137,25 @@
input.addEventListener("mouseup", mouseupListener)
})
}
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
})
})
onDestroy(async () => {
if (mobileKeyboardListener01) await mobileKeyboardListener01.remove()
if (mobileKeyboardListener02) await mobileKeyboardListener02.remove()
})
</script>

{#key hook}
Expand Down Expand Up @@ -161,7 +183,7 @@
on:keydown={onKeyDown}
on:input={onInput}
on:blur={onBlur}
autofocus={autoFocus} />
autofocus={isAndroidOriOS() ? isKeyboardOpened : autoFocus} />
</div>
</div>
{#if errorMessage}
Expand Down
31 changes: 19 additions & 12 deletions src/lib/layouts/BottomNavBarMobile.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +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 @@ -68,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 @@ -78,18 +93,10 @@
unsubscribeStore()
unsubscribeUIStore()
Keyboard.removeAllListeners()
await mobileKeyboardListener01?.remove()
await mobileKeyboardListener02?.remove()
})
$: settings = SettingsStore.state
$: isKeyboardOpened = false
Keyboard.addListener("keyboardWillShow", _ => {
isKeyboardOpened = true
})
Keyboard.addListener("keyboardWillHide", () => {
isKeyboardOpened = false
})
</script>

{#if !isKeyboardOpened}
Expand Down
53 changes: 45 additions & 8 deletions src/lib/layouts/Chatbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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 @@ -159,27 +160,63 @@
return result
}
let mobileAutoFocus: boolean
Keyboard.addListener("keyboardWillShow", () => {
mobileAutoFocus = true
})
let mobileKeyboardListener01: PluginListenerHandle | undefined
let mobileKeyboardListener02: PluginListenerHandle | undefined
$: isMobileKeyboardOpened = false
onMount(async () => {
mobileAutoFocus = false
hackVariableToRefocusChatBar.set(Math.random().toString())
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())
}
})
if (isAndroidOriOS()) {
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={`chatbat-container-${activeChat.id}`}>
<div class="chatbar" data-cy="chatbar" id={`chatbar-container-${activeChat.id}`}>
<Controls>
<slot name="pre-controls"></slot>
</Controls>
<Input
hook={`${activeChat.id}-${$hackVariableToRefocusChatBar}`}
alt
placeholder={$_("generic.placeholder")}
autoFocus={isAndroidOriOS() ? mobileAutoFocus : true}
autoFocus={true}
bind:value={$message}
rounded
rich={markdown}
Expand Down
38 changes: 1 addition & 37 deletions src/routes/chat/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -83,27 +68,6 @@
$: statusMessage = $activeChat.kind === ChatType.DirectMessage ? $users[$activeChat.users[1]]?.profile?.status_message : $activeChat.motd
$: pinned = getPinned($conversation)
UIStore.state.sidebarOpen.subscribe(_ => {
if (isiOSMobile()) {
let chat = get(Store.state.activeChat)
Keyboard.removeAllListeners().then(() => {
Keyboard.addListener("keyboardWillShow", info => {
const chatbar = document.getElementById(`chatbat-container-${chat.id}`)
if (chatbar) {
chatbar.style.marginBottom = `${info.keyboardHeight - 30}px`
}
})
Keyboard.addListener("keyboardWillHide", () => {
const chatbar = document.getElementById(`chatbat-container-${chat.id}`)
if (chatbar) {
chatbar.style.marginBottom = `0px`
}
})
})
}
})
function toggleSidebar() {
UIStore.toggleSidebar()
}
Expand Down
39 changes: 20 additions & 19 deletions src/routes/settings/profile/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,7 @@
import PinInput from "$lib/components/PinInput.svelte"
import { isiOSMobile } from "$lib/utils/Mobile"
import { Keyboard } from "@capacitor/keyboard"
if (isiOSMobile()) {
Keyboard.removeAllListeners().then(() => {
Keyboard.addListener("keyboardWillShow", _ => {
const focusedElement = document.activeElement
if (focusedElement && (focusedElement.tagName === "INPUT" || focusedElement.tagName === "TEXTAREA")) {
setTimeout(() => {
focusedElement.scrollIntoView({
behavior: "smooth",
block: "center",
})
}, 100)
}
})
})
}
import type { PluginListenerHandle } from "@capacitor/core"
enum SeedState {
Hidden,
Expand Down Expand Up @@ -191,15 +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)
Keyboard.removeAllListeners()
await mobileKeyboardListener?.remove()
})
$: user = Store.state.user
Expand Down

0 comments on commit 5137d38

Please sign in to comment.