Add dev live-reload tooling

DevPlugin (native + TS) lets the running app switch its webview to a
Vite dev server via DEV_URL, toggled from a hidden DevMenu (5 taps on
the app title).
This commit is contained in:
2026-08-12 11:54:39 +02:00
parent 0188d5210e
commit 6c9df23659
3 changed files with 105 additions and 0 deletions
@@ -3,10 +3,26 @@ package com.tiptoisync.app
import android.os.Bundle import android.os.Bundle
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import com.getcapacitor.BridgeActivity import com.getcapacitor.BridgeActivity
import com.getcapacitor.CapConfig
class MainActivity : BridgeActivity() { class MainActivity : BridgeActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// Dev-URL aus SharedPreferences lesen, bevor die Bridge initialisiert wird.
// "CapacitorStorage" ist der von @capacitor/preferences genutzte SharedPreferences-Name.
val devUrl = getSharedPreferences("CapacitorStorage", MODE_PRIVATE)
.getString("devServerUrl", null)
if (devUrl != null) {
// CapConfig mit der Dev-Server-URL überschreiben die Bridge lädt dann
// direkt vom Vite Dev-Server und injiziert die Bridge trotzdem korrekt.
config = CapConfig.Builder(this)
.setServerUrl(devUrl)
.setAllowMixedContent(true)
.create()
}
registerPlugin(TiptoiPlugin::class.java) registerPlugin(TiptoiPlugin::class.java)
registerPlugin(DevPlugin::class.java)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
} }
+81
View File
@@ -0,0 +1,81 @@
import { createSignal, onMount, Show } from 'solid-js'
import { Preferences } from '@capacitor/preferences'
import { Dev } from '../native/DevPlugin'
const DEV_URL_KEY = 'devServerUrl'
interface Props {
onClose: () => void
}
export default function DevMenu(props: Props) {
const [url, setUrl] = createSignal('')
const [saved, setSaved] = createSignal<string | null>(null)
onMount(async () => {
const { value } = await Preferences.get({ key: DEV_URL_KEY })
setSaved(value)
setUrl(value ?? '')
})
const handleSave = async () => {
const trimmed = url().trim()
if (!trimmed) return
// Speichern + Activity-Neustart → Bridge initialisiert sich mit Dev-URL
await Dev.activate({ url: trimmed })
}
const handleClear = async () => {
// Entfernen + Activity-Neustart → Bridge initialisiert sich mit gebundelter App
await Dev.deactivate()
}
return (
<div class="dev-menu-backdrop" onClick={props.onClose}>
<div class="dev-menu" onClick={e => e.stopPropagation()}>
<div class="dev-menu__header">
<span class="dev-menu__title"> Dev-Modus</span>
<button class="dev-menu__close" onClick={props.onClose}></button>
</div>
<p class="dev-menu__desc">
Vite Dev-Server URL eintragen. Die App lädt dann JavaScript direkt
vom Dev-Server inkl. Hot Reload. Native Plugins bleiben aktiv.
</p>
<Show when={saved()}>
<div class="dev-menu__active">
<span class="dev-menu__active-dot" />
Aktiv: <code>{saved()}</code>
</div>
</Show>
<div class="dev-menu__row">
<input
class="input"
type="url"
placeholder="http://192.168.1.42:5173"
value={url()}
onInput={e => setUrl(e.currentTarget.value)}
onKeyDown={e => e.key === 'Enter' && handleSave()}
/>
</div>
<div class="dev-menu__actions">
<button
class="btn"
onClick={handleSave}
disabled={!url().trim() || url().trim() === saved()}
>
Speichern &amp; verbinden
</button>
<Show when={saved()}>
<button class="btn btn--ghost" onClick={handleClear}>
Dev-Modus deaktivieren
</button>
</Show>
</div>
</div>
</div>
)
}
+8
View File
@@ -0,0 +1,8 @@
import { registerPlugin } from '@capacitor/core'
export interface DevPlugin {
activate(options: { url: string }): Promise<void>
deactivate(): Promise<void>
}
export const Dev = registerPlugin<DevPlugin>('Dev')