feat(stage-pocket): allow to set transparent background
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package ai.moeru.airi_pocket
|
||||
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.net.http.SslError
|
||||
import android.webkit.SslErrorHandler
|
||||
@@ -29,6 +30,7 @@ class MainActivity : BridgeActivity() {
|
||||
super.load()
|
||||
|
||||
val bridge = bridge ?: return
|
||||
bridge.webView.setBackgroundColor(Color.TRANSPARENT)
|
||||
installWebSocketBridge(bridge)
|
||||
|
||||
if (!bridge.isDevMode) {
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -14,6 +14,7 @@ class DevBridgeViewController: CAPBridgeViewController {
|
||||
|
||||
override func capacitorDidLoad() {
|
||||
super.capacitorDidLoad()
|
||||
configureTransparentBackground()
|
||||
webView?.allowsBackForwardNavigationGestures = true
|
||||
installWebSocketBridge()
|
||||
}
|
||||
@@ -44,6 +45,20 @@ class DevBridgeViewController: CAPBridgeViewController {
|
||||
webView.configuration.userContentController.add(hostBridgeMessageHandler, name: hostBridgeName)
|
||||
}
|
||||
|
||||
private func configureTransparentBackground() {
|
||||
view.isOpaque = false
|
||||
view.backgroundColor = .clear
|
||||
|
||||
guard let webView = bridge?.webView else {
|
||||
return
|
||||
}
|
||||
|
||||
webView.isOpaque = false
|
||||
webView.backgroundColor = .clear
|
||||
webView.scrollView.backgroundColor = .clear
|
||||
webView.superview?.backgroundColor = .clear
|
||||
}
|
||||
|
||||
private func dispatchWebSocketBridgeEvent(_ payload: String) {
|
||||
guard let webView = bridge?.webView else {
|
||||
return
|
||||
|
||||
@@ -32,6 +32,12 @@ html {
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
html:has(.airi-native-transparent-surface),
|
||||
body:has(.airi-native-transparent-surface),
|
||||
#app:has(.airi-native-transparent-surface) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--bg-color: var(--bg-color-dark);
|
||||
color-scheme: dark;
|
||||
|
||||
@@ -20,11 +20,15 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="containerRef" class="customized-background relative min-h-100dvh w-full overflow-hidden">
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="customized-background relative min-h-100dvh w-full overflow-hidden"
|
||||
:class="[background.kind === BackgroundKind.Transparent ? 'airi-native-transparent-surface' : '']"
|
||||
>
|
||||
<!-- Background layers -->
|
||||
<div
|
||||
class="absolute inset-0 z-0 transition-all duration-300"
|
||||
:class="[(background.blur && background.kind !== BackgroundKind.Wave) ? 'blur-md scale-110' : '']"
|
||||
:class="[(background.blur && background.kind === BackgroundKind.Image) ? 'blur-md scale-110' : '']"
|
||||
>
|
||||
<template v-if="background.kind === BackgroundKind.Wave">
|
||||
<DefaultBackground class="h-full w-full" />
|
||||
@@ -37,13 +41,16 @@ defineExpose({
|
||||
decoding="async"
|
||||
>
|
||||
</template>
|
||||
<template v-else-if="background.kind === BackgroundKind.Transparent">
|
||||
<div class="h-full w-full bg-transparent" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="h-full w-full bg-neutral-950" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Overlay (not for wave) -->
|
||||
<BackgroundGradientOverlay v-if="background.kind !== BackgroundKind.Wave" :color="topColor" />
|
||||
<BackgroundGradientOverlay v-if="background.kind === BackgroundKind.Image" :color="topColor" />
|
||||
|
||||
<!-- Content layer (kept mounted during background switches) -->
|
||||
<div class="relative z-10 h-full w-full">
|
||||
|
||||
@@ -2,3 +2,4 @@ export { default as DefaultBackground } from './index.vue'
|
||||
export { default as PartAnimatedWave } from './part-animated-wave.vue'
|
||||
export { default as PatternCross } from './pattern-cross.vue'
|
||||
export { default as DefaultBackgroundPreview } from './preview.vue'
|
||||
export { default as TransparentBackgroundPreview } from './transparent-preview.vue'
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="transparent-preview">
|
||||
<div class="transparent-preview__label">
|
||||
Transparent
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.transparent-preview {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgb(255 255 255 / 0.92);
|
||||
background-image:
|
||||
linear-gradient(45deg, rgb(226 232 240 / 0.95) 25%, transparent 25%),
|
||||
linear-gradient(-45deg, rgb(226 232 240 / 0.95) 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, rgb(226 232 240 / 0.95) 75%),
|
||||
linear-gradient(-45deg, transparent 75%, rgb(226 232 240 / 0.95) 75%);
|
||||
background-position:
|
||||
0 0,
|
||||
0 8px,
|
||||
8px -8px,
|
||||
-8px 0;
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
.transparent-preview__label {
|
||||
border: 1px solid rgb(255 255 255 / 0.7);
|
||||
border-radius: 999px;
|
||||
background: rgb(255 255 255 / 0.78);
|
||||
padding: 0.35rem 0.75rem;
|
||||
color: rgb(51 65 85 / 0.9);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
:global(.dark) .transparent-preview {
|
||||
background-color: rgb(23 23 23 / 0.92);
|
||||
background-image:
|
||||
linear-gradient(45deg, rgb(64 64 64 / 0.9) 25%, transparent 25%),
|
||||
linear-gradient(-45deg, rgb(64 64 64 / 0.9) 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, rgb(64 64 64 / 0.9) 75%),
|
||||
linear-gradient(-45deg, transparent 75%, rgb(64 64 64 / 0.9) 75%);
|
||||
}
|
||||
|
||||
:global(.dark) .transparent-preview__label {
|
||||
border-color: rgb(64 64 64 / 0.85);
|
||||
background: rgb(23 23 23 / 0.72);
|
||||
color: rgb(245 245 245 / 0.9);
|
||||
}
|
||||
</style>
|
||||
@@ -79,6 +79,10 @@ export function useBackgroundThemeColor({
|
||||
return waveThemeColor()
|
||||
}
|
||||
|
||||
if (selectedOption.value?.kind === BackgroundKind.Transparent) {
|
||||
return isDark.value ? 'rgb(18 18 18 / 0)' : 'rgb(255 255 255 / 0)'
|
||||
}
|
||||
|
||||
return sampledColor.value
|
||||
})
|
||||
|
||||
@@ -86,11 +90,11 @@ export function useBackgroundThemeColor({
|
||||
const { pause, resume } = useIntervalFn(() => {
|
||||
if (visibility.value !== 'visible')
|
||||
return
|
||||
if (selectedOption.value?.kind === BackgroundKind.Wave && themeColorsHueDynamic)
|
||||
if (selectedOption.value?.kind === BackgroundKind.Wave && themeColorsHueDynamic.value)
|
||||
void updateThemeColor()
|
||||
}, 250, { immediate: false })
|
||||
|
||||
watch([() => selectedOption.value?.kind, () => themeColorsHueDynamic], ([kind, dynamic]) => {
|
||||
watch([() => selectedOption.value?.kind, () => themeColorsHueDynamic.value], ([kind, dynamic]) => {
|
||||
if (kind === BackgroundKind.Wave && dynamic) {
|
||||
void updateThemeColor()
|
||||
resume()
|
||||
@@ -120,6 +124,12 @@ export function useBackgroundThemeColor({
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedOption.value?.kind === BackgroundKind.Transparent) {
|
||||
sampledColor.value = 'transparent'
|
||||
await updateThemeColor()
|
||||
return
|
||||
}
|
||||
|
||||
const el = backgroundSurface.value?.surfaceEl
|
||||
if (!el)
|
||||
return
|
||||
|
||||
@@ -7,11 +7,12 @@ import { useLocalStorage, useObjectUrl } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, markRaw, onScopeDispose, ref, shallowRef, watch } from 'vue'
|
||||
|
||||
import { DefaultBackgroundPreview } from '../components/Backgrounds/default'
|
||||
import { DefaultBackgroundPreview, TransparentBackgroundPreview } from '../components/Backgrounds/default'
|
||||
|
||||
export enum BackgroundKind {
|
||||
Wave = 'wave',
|
||||
Image = 'image',
|
||||
Transparent = 'transparent',
|
||||
}
|
||||
|
||||
export interface BackgroundItem extends BackgroundOption {
|
||||
@@ -36,6 +37,13 @@ export const useBackgroundStore = defineStore('background', () => {
|
||||
kind: BackgroundKind.Wave,
|
||||
component: markRaw(DefaultBackgroundPreview),
|
||||
},
|
||||
{
|
||||
id: 'transparent',
|
||||
label: 'Transparent',
|
||||
description: 'Reveal the native background behind the WebView',
|
||||
kind: BackgroundKind.Transparent,
|
||||
component: markRaw(TransparentBackgroundPreview),
|
||||
},
|
||||
]
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -143,9 +151,11 @@ export const useBackgroundStore = defineStore('background', () => {
|
||||
async function applyPickerSelection(payload: { option: BackgroundOption, color?: string }) {
|
||||
const kind = payload.option.kind === BackgroundKind.Wave
|
||||
? BackgroundKind.Wave
|
||||
: payload.option.kind === BackgroundKind.Image
|
||||
? BackgroundKind.Image
|
||||
: BackgroundKind.Image
|
||||
: payload.option.kind === BackgroundKind.Transparent
|
||||
? BackgroundKind.Transparent
|
||||
: payload.option.kind === BackgroundKind.Image
|
||||
? BackgroundKind.Image
|
||||
: BackgroundKind.Image
|
||||
|
||||
const selection: BackgroundItem = {
|
||||
...payload.option,
|
||||
|
||||
+20
-5
@@ -103,6 +103,10 @@ watch(selectedOption, async (option) => {
|
||||
if (option?.kind === 'wave') {
|
||||
previewColor.value = themeColorsHue.toString()
|
||||
}
|
||||
else if (option?.kind === 'transparent') {
|
||||
enableBlur.value = false
|
||||
previewColor.value = 'transparent'
|
||||
}
|
||||
else if (option) {
|
||||
await waitForPreviewReady()
|
||||
const result = await colorFromElement(previewRef.value!, {
|
||||
@@ -170,10 +174,21 @@ async function applySelection(isImport = false) {
|
||||
|
||||
busy.value = true
|
||||
try {
|
||||
const blur = selectedOption.value.kind === 'image' ? enableBlur.value : false
|
||||
|
||||
if (selectedOption.value.kind === 'wave') {
|
||||
const color = themeColorsHue.toString()
|
||||
|
||||
const payload = { option: { ...selectedOption.value, blur: enableBlur.value }, color }
|
||||
const payload = { option: { ...selectedOption.value, blur }, color }
|
||||
if (isImport)
|
||||
(emit as any)('import', payload) // TODO: so ugly, need to fix the typing of emit
|
||||
else
|
||||
(emit as any)('apply', payload)
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedOption.value.kind === 'transparent') {
|
||||
const payload = { option: { ...selectedOption.value, blur }, color: 'transparent' }
|
||||
if (isImport)
|
||||
(emit as any)('import', payload)
|
||||
else
|
||||
@@ -190,7 +205,7 @@ async function applySelection(isImport = false) {
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
}
|
||||
|
||||
const payload = { option: { ...selectedOption.value, blur: enableBlur.value }, color: previewColor.value }
|
||||
const payload = { option: { ...selectedOption.value, blur }, color: previewColor.value }
|
||||
if (isImport)
|
||||
(emit as any)('import', payload)
|
||||
else
|
||||
@@ -267,7 +282,7 @@ async function applySelection(isImport = false) {
|
||||
Preview
|
||||
</p>
|
||||
<label
|
||||
v-if="selectedOption?.kind !== 'wave'"
|
||||
v-if="selectedOption?.kind === 'image'"
|
||||
class="flex items-center gap-2 pb-2 text-sm text-neutral-700 dark:text-neutral-200"
|
||||
>
|
||||
<input v-model="enableBlur" type="checkbox" class="accent-primary-500">
|
||||
@@ -279,7 +294,7 @@ async function applySelection(isImport = false) {
|
||||
>
|
||||
<div
|
||||
class="h-full w-full transition-all duration-300"
|
||||
:class="[(enableBlur && selectedOption?.kind !== 'wave') ? 'blur-md scale-110' : '']"
|
||||
:class="[(enableBlur && selectedOption?.kind === 'image') ? 'blur-md scale-110' : '']"
|
||||
>
|
||||
<component
|
||||
:is="selectedOption?.component"
|
||||
@@ -295,7 +310,7 @@ async function applySelection(isImport = false) {
|
||||
Select a background
|
||||
</div>
|
||||
</div>
|
||||
<BackgroundGradientOverlay v-if="(selectedOption as any)?.kind !== 'wave'" :color="previewColor" />
|
||||
<BackgroundGradientOverlay v-if="selectedOption?.kind === 'image'" :color="previewColor" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user