merge: companion-mobile-ux UX (loader/store-driven launch/icons + android webview) into main
# Conflicts: # Android/app/build.gradle.kts # Android/app/src/main/java/com/archipelago/app/ui/screens/WebViewScreen.kt # neode-ui/src/views/apps/appsConfig.ts
This commit is contained in:
@@ -69,12 +69,12 @@
|
||||
<div class="relative flex-1 min-h-0 bg-black/40 overflow-hidden">
|
||||
<!-- Loading indicator -->
|
||||
<Transition name="content-fade">
|
||||
<div v-if="iframeLoading" class="absolute inset-0 z-10 flex items-center justify-center bg-black/40">
|
||||
<svg class="animate-spin h-8 w-8 text-white/70" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<AppLoadingScreen
|
||||
v-if="iframeLoading"
|
||||
:icon="overlayIcon"
|
||||
:title="store.title || 'App'"
|
||||
:progress="loadProgress"
|
||||
/>
|
||||
</Transition>
|
||||
<iframe
|
||||
ref="iframeRef"
|
||||
@@ -184,10 +184,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useAppLauncherStore } from '@/stores/appLauncher'
|
||||
import NostrSignConsent from '@/components/NostrSignConsent.vue'
|
||||
import NostrIdentityPicker from '@/components/NostrIdentityPicker.vue'
|
||||
import AppLoadingScreen from '@/components/AppLoadingScreen.vue'
|
||||
import { DEFAULT_APP_ICON } from '@/views/apps/appsConfig'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
|
||||
interface PaymentRequest {
|
||||
@@ -207,6 +209,39 @@ const isRefreshing = ref(false)
|
||||
const iframeLoading = ref(true)
|
||||
const iframeBlocked = ref(false)
|
||||
|
||||
// Best-guess icon for the loading screen — resolved from the /app/{id}/ path
|
||||
// when present; AppLoadingScreen's <img> falls back to the default icon if the
|
||||
// guessed asset 404s.
|
||||
const overlayIcon = computed(() => {
|
||||
const url = store.url
|
||||
if (!url) return DEFAULT_APP_ICON
|
||||
try {
|
||||
const m = new URL(url, window.location.origin).pathname.match(/^\/app\/([a-z0-9._-]+)/i)
|
||||
if (m?.[1]) return `/assets/img/app-icons/${m[1].toLowerCase()}.png`
|
||||
} catch { /* not a parseable URL */ }
|
||||
return DEFAULT_APP_ICON
|
||||
})
|
||||
|
||||
// Faux load progress (cross-origin iframes give no real progress events): ease
|
||||
// toward ~92% while loading, snap to 100% on load.
|
||||
const loadProgress = ref(0)
|
||||
let progressTimer: ReturnType<typeof setInterval> | null = null
|
||||
function stopProgress() {
|
||||
if (progressTimer) { clearInterval(progressTimer); progressTimer = null }
|
||||
}
|
||||
function startProgress() {
|
||||
stopProgress()
|
||||
loadProgress.value = 8
|
||||
progressTimer = setInterval(() => {
|
||||
loadProgress.value += Math.max(0.4, (92 - loadProgress.value) * 0.08)
|
||||
if (loadProgress.value >= 92) { loadProgress.value = 92; stopProgress() }
|
||||
}, 180)
|
||||
}
|
||||
watch(iframeLoading, (loading) => {
|
||||
if (loading) startProgress()
|
||||
else { stopProgress(); loadProgress.value = 100 }
|
||||
}, { immediate: true })
|
||||
|
||||
// Nostr identity picker state
|
||||
const showIdentityPicker = ref(false)
|
||||
const IDENTITY_STORAGE_KEY = 'archipelago_app_identity_'
|
||||
@@ -573,6 +608,7 @@ onMounted(() => {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearTimers()
|
||||
stopProgress()
|
||||
window.removeEventListener('keydown', onKeyDown, true)
|
||||
window.removeEventListener('message', onMessage)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="app-loading-screen absolute inset-0 z-10 flex flex-col items-center justify-center">
|
||||
<div class="app-loading-icon">
|
||||
<img :src="icon" :alt="title" @error="handleImageError" />
|
||||
</div>
|
||||
<p class="app-loading-title">{{ title }}</p>
|
||||
<div class="app-loading-bar">
|
||||
<div class="app-loading-fill" :style="{ width: `${clampedProgress}%` }"></div>
|
||||
</div>
|
||||
<p class="app-loading-hint">{{ hint }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { handleImageError } from '@/views/apps/appsConfig'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
icon: string
|
||||
title: string
|
||||
progress: number
|
||||
hint?: string
|
||||
}>(), {
|
||||
hint: 'Loading…',
|
||||
})
|
||||
|
||||
const clampedProgress = computed(() => Math.min(100, Math.max(0, props.progress)))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-loading-screen {
|
||||
gap: 18px;
|
||||
background: #0b0d12;
|
||||
}
|
||||
.app-loading-icon {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
|
||||
animation: app-loading-pulse 1.8s ease-in-out infinite;
|
||||
}
|
||||
.app-loading-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.app-loading-title {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.app-loading-bar {
|
||||
width: min(240px, 60vw);
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.app-loading-fill {
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, #fb923c, #f59e0b);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.app-loading-hint {
|
||||
margin: 0;
|
||||
font-size: 0.75rem;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
@keyframes app-loading-pulse {
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.05); opacity: 0.85; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user