Android build v1.1.2

This commit is contained in:
Warren H
2026-06-29 22:35:49 -04:00
parent cbe42b6164
commit c6251167a5
52 changed files with 5554 additions and 3117 deletions
+45 -6
View File
@@ -9,6 +9,7 @@ import {
useState,
type ReactNode,
} from 'react'
import { Capacitor } from '@capacitor/core'
export type InputDevice = 'pc' | 'controller'
export type ControllerIconStyle = 'xbox' | 'playstation' | 'nintendo'
@@ -126,6 +127,9 @@ const PREFERENCES_STORAGE_KEY = 'ashen-halls-input-preferences-v1'
const GAME_ACTION_EVENT = 'ashen-halls-game-action'
const NATIVE_CONTROLLER_EVENT = 'ashen-halls-native-controller'
const FOCUSABLE_SELECTOR = 'button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])'
const GAMEPAD_COMBAT_POLL_MS = 1000 / 60
const GAMEPAD_MENU_POLL_MS = 1000 / 30
const GAMEPAD_BROWSER_DISCONNECTED_POLL_MS = 250
let lastControllerFocus: HTMLElement | null = null
@@ -400,6 +404,14 @@ function gamepadTokens(gamepad: Gamepad) {
return tokens
}
function isCombatActive() {
return Boolean(document.querySelector('[data-combat-active="true"]'))
}
function firstConnectedGamepad() {
return Array.from(navigator.getGamepads?.() ?? []).find(Boolean) ?? null
}
function setInputValue(input: HTMLInputElement | HTMLTextAreaElement, nextValue: string) {
const prototype = input instanceof HTMLTextAreaElement
? HTMLTextAreaElement.prototype
@@ -422,6 +434,7 @@ export function InputProvider({ children }: { children: ReactNode }) {
const keyboardInputRef = useRef(keyboardInput)
const previousTokensRef = useRef(new Set<string>())
const repeatRef = useRef<Record<string, number>>({})
const gamepadConnectedRef = useRef(Capacitor.isNativePlatform())
useEffect(() => {
bindingsRef.current = bindings
@@ -664,9 +677,21 @@ export function InputProvider({ children }: { children: ReactNode }) {
}, [])
useEffect(() => {
let frame = 0
const poll = (time: number) => {
const gamepad = Array.from(navigator.getGamepads?.() ?? []).find(Boolean)
let timer = 0
const nextDelay = () => {
if (!Capacitor.isNativePlatform() && !gamepadConnectedRef.current) {
return GAMEPAD_BROWSER_DISCONNECTED_POLL_MS
}
return isCombatActive() ? GAMEPAD_COMBAT_POLL_MS : GAMEPAD_MENU_POLL_MS
}
const clearControllerState = () => {
previousTokensRef.current = new Set()
repeatRef.current = {}
}
const poll = () => {
const time = performance.now()
const gamepad = firstConnectedGamepad()
gamepadConnectedRef.current = Capacitor.isNativePlatform() || Boolean(gamepad)
const currentTokens = gamepad ? gamepadTokens(gamepad) : new Set<string>()
const previousTokens = previousTokensRef.current
@@ -692,10 +717,24 @@ export function InputProvider({ children }: { children: ReactNode }) {
if (!currentTokens.has(token)) delete repeatRef.current[token]
})
previousTokensRef.current = currentTokens
frame = window.requestAnimationFrame(poll)
if (!gamepad) clearControllerState()
timer = window.setTimeout(poll, nextDelay())
}
const onGamepadConnected = () => {
gamepadConnectedRef.current = true
}
const onGamepadDisconnected = () => {
gamepadConnectedRef.current = Boolean(firstConnectedGamepad())
if (!gamepadConnectedRef.current) clearControllerState()
}
window.addEventListener('gamepadconnected', onGamepadConnected)
window.addEventListener('gamepaddisconnected', onGamepadDisconnected)
timer = window.setTimeout(poll, 0)
return () => {
window.clearTimeout(timer)
window.removeEventListener('gamepadconnected', onGamepadConnected)
window.removeEventListener('gamepaddisconnected', onGamepadDisconnected)
}
frame = window.requestAnimationFrame(poll)
return () => window.cancelAnimationFrame(frame)
}, [assignBinding, dispatchControllerToken])
const contextValue = useMemo<InputContextValue>(() => ({