mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 17:01:18 +00:00
feat(phone): unify popups and add dynamic island
This commit is contained in:
+4
-16
@@ -13,6 +13,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { SkyProvider } from '@/ui'
|
||||
import PhoneHomeIndicator from '@/components/PhoneHomeIndicator.vue'
|
||||
import PhoneControlCenter from '@/components/PhoneControlCenter.vue'
|
||||
import PhoneDynamicIsland from '@/components/PhoneDynamicIsland.vue'
|
||||
import PhoneMediaCapture from '@/components/PhoneMediaCapture.vue'
|
||||
import PhoneMemoRecorder from '@/components/PhoneMemoRecorder.vue'
|
||||
import PhoneLockScreen from '@/components/PhoneLockScreen.vue'
|
||||
@@ -316,9 +317,6 @@ const DARK_STATUS_BAR_APP_IDS = new Set([
|
||||
const isDevelopmentRoute = computed(
|
||||
() => isDevelopment && route.name === 'development-sky-ui',
|
||||
)
|
||||
const showActiveCallReturn = computed(
|
||||
() => Boolean(calls.activeCall) && route.params.appId !== 'phone',
|
||||
)
|
||||
const appTransitionName = computed(() =>
|
||||
route.query.transition === 'app-switch' ? 'app-switch' : 'app-window',
|
||||
)
|
||||
@@ -1084,7 +1082,6 @@ function onMessage(event: MessageEvent<AppMessage>): void {
|
||||
isUnlocking.value = false
|
||||
loadUnlockedPhoneData()
|
||||
}
|
||||
window.setTimeout(() => void router.push('/apps/phone'), 0)
|
||||
} else if (event.data?.type === 'sim:picker' && event.data.data) {
|
||||
simPicker.value = event.data.data as unknown as SimPickerPayload
|
||||
} else if (event.data?.type === 'sim:picker-close') {
|
||||
@@ -1330,12 +1327,6 @@ function toggleHardwareAlertMute(): void {
|
||||
showHardwareVolumeHud()
|
||||
}
|
||||
|
||||
function returnToActiveCall(): void {
|
||||
if (!calls.activeCall) return
|
||||
controlCenterOpened.value = false
|
||||
void router.push('/apps/phone')
|
||||
}
|
||||
|
||||
function unlockCamera(): void {
|
||||
if (phone.security.enabled) {
|
||||
pendingUnlockRoute.value = '/apps/camera'
|
||||
@@ -1414,9 +1405,7 @@ onMounted(() => {
|
||||
window.setTimeout(() => {
|
||||
notifications.show({
|
||||
appId: 'messages',
|
||||
persistent: !developmentParameters.has(
|
||||
'mutedNotificationPreview',
|
||||
),
|
||||
persistent: !developmentParameters.has('mutedNotificationPreview'),
|
||||
route: '/apps/messages',
|
||||
text: 'You still got that spare alternator?',
|
||||
title: 'Tommy V',
|
||||
@@ -1570,7 +1559,7 @@ onBeforeUnmount(() => {
|
||||
@open="openNotificationPreview"
|
||||
/>
|
||||
<div
|
||||
v-if="phone.isOpen || notifications.current"
|
||||
v-if="phone.isOpen || notifications.current || calls.activeCall"
|
||||
class="phone-resolution-wrapper phone-resolution-wrapper--primary"
|
||||
>
|
||||
<div class="phone-resolution-canvas phone-resolution-canvas--primary">
|
||||
@@ -1682,14 +1671,13 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<PhoneStatusBar
|
||||
v-if="!isLocked && !(isHomeRoute && springboardEditing)"
|
||||
:active-call-return="showActiveCallReturn"
|
||||
:control-center-opened="controlCenterOpened"
|
||||
:interactive="!setupRequired"
|
||||
:lockable="!setupRequired"
|
||||
@active-call="returnToActiveCall"
|
||||
@control-center="toggleControlCenter"
|
||||
@lock="lockPhone"
|
||||
/>
|
||||
<PhoneDynamicIsland v-if="!setupRequired" />
|
||||
<SpringboardView
|
||||
v-if="!isDevelopmentRoute && !setupRequired"
|
||||
@edit-mode-change="springboardEditing = $event"
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const source = readFileSync(
|
||||
new URL('./PhoneDynamicIsland.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const appSource = readFileSync(new URL('../App.vue', import.meta.url), 'utf8')
|
||||
const recorderSource = readFileSync(
|
||||
new URL('./PhoneMemoRecorder.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const memosSource = readFileSync(
|
||||
new URL('../views/apps/MemosApp.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const clockSource = readFileSync(
|
||||
new URL('../views/apps/ClockApp.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
describe('Phone Dynamic Island contract', () => {
|
||||
it('renders once at phone shell level instead of forcing calls into Phone', () => {
|
||||
expect(appSource).toContain(
|
||||
"import PhoneDynamicIsland from '@/components/PhoneDynamicIsland.vue'",
|
||||
)
|
||||
expect(appSource).toContain('<PhoneDynamicIsland v-if="!setupRequired" />')
|
||||
expect(appSource).toContain(
|
||||
'phone.isOpen || notifications.current || calls.activeCall',
|
||||
)
|
||||
expect(appSource).not.toContain(
|
||||
"window.setTimeout(() => void router.push('/apps/phone'), 0)",
|
||||
)
|
||||
})
|
||||
|
||||
it('prioritizes calls and exposes answer, decline, and hangup controls', () => {
|
||||
expect(source.indexOf("return 'incoming-call'")).toBeLessThan(
|
||||
source.indexOf("return 'recording'"),
|
||||
)
|
||||
expect(source).toContain('@click.stop="calls.answer()"')
|
||||
expect(source).toContain('@click.stop="endCall"')
|
||||
expect(source).toContain('void calls.decline()')
|
||||
expect(source).toContain('void calls.hangup()')
|
||||
expect(source).toContain('call.answeredAt ?? call.startedAt')
|
||||
})
|
||||
|
||||
it('connects music, recorder, timer, and stopwatch controls to their stores', () => {
|
||||
expect(source).toContain('@click.stop="music.previous()"')
|
||||
expect(source).toContain('@click.stop="music.toggle()"')
|
||||
expect(source).toContain('@click.stop="music.next()"')
|
||||
expect(source).toContain("postRecorderCommand('memo:recordStop')")
|
||||
expect(source).toContain('clock.pauseTimer(Date.now())')
|
||||
expect(source).toContain('clock.pauseStopwatch(Date.now())')
|
||||
expect(source).toContain('clock.addLap(Date.now())')
|
||||
})
|
||||
|
||||
it('keeps recorder state available across app changes', () => {
|
||||
expect(recorderSource).toContain(
|
||||
"message.type === 'memo:recordStateRequest'",
|
||||
)
|
||||
expect(memosSource).toContain(
|
||||
"postRecorderCommand('memo:recordStateRequest')",
|
||||
)
|
||||
expect(memosSource).not.toContain(
|
||||
"if (recordingActive.value) postRecorderCommand('memo:recordCancel')",
|
||||
)
|
||||
})
|
||||
|
||||
it('opens both clock live activities on the correct clock tab', () => {
|
||||
expect(source).toContain("'/apps/clock?section=timer'")
|
||||
expect(source).toContain("'/apps/clock?section=stopwatch'")
|
||||
expect(clockSource).toContain(
|
||||
"section === 'timer' || section === 'stopwatch'",
|
||||
)
|
||||
})
|
||||
|
||||
it('animates state changes and moves popup notifications below expanded UI', () => {
|
||||
expect(source).toContain('<Transition name="phone-dynamic-island">')
|
||||
expect(source).toContain(
|
||||
'<Transition name="phone-dynamic-island-content" mode="out-in">',
|
||||
)
|
||||
expect(source).toContain(".phone-dynamic-island[data-expanded='true']")
|
||||
expect(source).toContain('~ .phone-notification-provider')
|
||||
expect(source).toContain('@media (prefers-reduced-motion: reduce)')
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -557,6 +557,8 @@ function onMessage(event: MessageEvent): void {
|
||||
void stopRecording(data)
|
||||
} else if (message.type === 'memo:recordCancel') {
|
||||
cancelRecording()
|
||||
} else if (message.type === 'memo:recordStateRequest') {
|
||||
postRecorderState(currentState)
|
||||
} else if (message.type === 'memos:uploadReady') {
|
||||
void uploadReady(data as MemoUploadReady)
|
||||
} else if (message.type === 'memos:uploadResult') {
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('Banking app Sky UI migration', () => {
|
||||
'SkyButton',
|
||||
'SkySpinner',
|
||||
'SkyEmptyState',
|
||||
'SkyToast',
|
||||
'SkyNotification',
|
||||
]) {
|
||||
expect(source).toContain(`<${component}`)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
SkySpinner,
|
||||
SkyTabBar,
|
||||
SkyTabButton,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
|
||||
type BankingTab = 'home' | 'activity'
|
||||
@@ -696,13 +696,10 @@ onBeforeUnmount(() => {
|
||||
</section>
|
||||
</SkySheet>
|
||||
|
||||
<SkyToast
|
||||
<SkyNotification
|
||||
:opened="cooldownToastOpened"
|
||||
position="center"
|
||||
vertical-position="bottom"
|
||||
:text="errorMessage('reload_cooldown')"
|
||||
@click="closeCooldownToast"
|
||||
>
|
||||
{{ errorMessage('reload_cooldown') }}
|
||||
</SkyToast>
|
||||
/>
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('Billing app Sky UI migration', () => {
|
||||
'SkyTabBar',
|
||||
'SkyTabButton',
|
||||
'SkySheet',
|
||||
'SkyToast',
|
||||
'SkyNotification',
|
||||
]) {
|
||||
expect(source).toContain(`<${component}`)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
SkySpinner,
|
||||
SkyTabBar,
|
||||
SkyTabButton,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
|
||||
|
||||
@@ -771,9 +771,11 @@ onBeforeUnmount(() => {
|
||||
</section>
|
||||
</SkySheet>
|
||||
|
||||
<SkyToast :opened="toastOpen" position="center" class="billing-toast">
|
||||
{{ toastText }}
|
||||
</SkyToast>
|
||||
<SkyNotification
|
||||
:opened="toastOpen"
|
||||
:text="toastText"
|
||||
class="billing-notification"
|
||||
/>
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
@@ -1523,7 +1525,7 @@ onBeforeUnmount(() => {
|
||||
var(--billing-panel);
|
||||
}
|
||||
}
|
||||
.billing-toast {
|
||||
.billing-notification {
|
||||
z-index: 50;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
SkyButton,
|
||||
SkyGlass,
|
||||
SkyIcon,
|
||||
SkyNotification,
|
||||
SkyNavbar,
|
||||
SkyAppPage,
|
||||
SkySearchbar,
|
||||
@@ -2113,11 +2114,7 @@ onMounted(async () => {
|
||||
app-id="citymarkt"
|
||||
:app-name="phone.t('Apps.citymarkt.name')"
|
||||
/>
|
||||
<Transition name="toast"
|
||||
><div v-if="feedback" class="citymarkt__toast">
|
||||
{{ feedback }}
|
||||
</div></Transition
|
||||
>
|
||||
<SkyNotification :opened="Boolean(feedback)" :text="feedback" />
|
||||
</sky-app-page>
|
||||
</template>
|
||||
|
||||
@@ -3014,30 +3011,6 @@ onMounted(async () => {
|
||||
background: var(--panel);
|
||||
color: #ff796f;
|
||||
}
|
||||
.citymarkt__toast {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
right: 18px;
|
||||
bottom: 92px;
|
||||
left: 18px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 11px;
|
||||
background: #f4f4ee;
|
||||
color: #171816;
|
||||
box-shadow: 0 8px 30px #0007;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
}
|
||||
.toast-enter-active,
|
||||
.toast-leave-active {
|
||||
transition: 0.2s;
|
||||
}
|
||||
.toast-enter-from,
|
||||
.toast-leave-to {
|
||||
transform: translateY(8px);
|
||||
opacity: 0;
|
||||
}
|
||||
.citymarkt {
|
||||
--color-primary: var(--yellow);
|
||||
position: relative;
|
||||
@@ -3740,8 +3713,7 @@ onMounted(async () => {
|
||||
font-size: 10.5px;
|
||||
}
|
||||
.citymarkt__meta,
|
||||
.citymarkt__inline-auth,
|
||||
.citymarkt__toast {
|
||||
.citymarkt__inline-auth {
|
||||
font-size: 12px;
|
||||
}
|
||||
.citymarkt__composer textarea,
|
||||
|
||||
@@ -47,7 +47,9 @@ const phone = usePhoneStore()
|
||||
const clock = useClockStore()
|
||||
const route = useRoute()
|
||||
const tab = ref<'world' | 'alarm' | 'stopwatch' | 'timer'>(
|
||||
route.query.section === 'timer' ? 'timer' : 'world',
|
||||
route.query.section === 'timer' || route.query.section === 'stopwatch'
|
||||
? route.query.section
|
||||
: 'world',
|
||||
)
|
||||
const alarmEditor = ref<
|
||||
{ mode: 'create' } | { id: string; mode: 'edit' } | null
|
||||
@@ -205,7 +207,7 @@ watch([() => clock.stopwatchStartedAt, tab], ([startedAt, activeTab]) => {
|
||||
watch(
|
||||
() => route.query.section,
|
||||
(section) => {
|
||||
if (section === 'timer') tab.value = 'timer'
|
||||
if (section === 'timer' || section === 'stopwatch') tab.value = section
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
SkyScrollRail,
|
||||
SkySection,
|
||||
SkySheet,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
SkyToggle,
|
||||
SkyToolbarPane,
|
||||
} from '@/ui'
|
||||
@@ -2599,14 +2599,11 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</SkyDialog>
|
||||
|
||||
<SkyToast
|
||||
<SkyNotification
|
||||
:opened="toastOpened"
|
||||
position="center"
|
||||
vertical-position="center"
|
||||
:text="toastText"
|
||||
@click="toastOpened = false"
|
||||
>
|
||||
{{ toastText }}
|
||||
</SkyToast>
|
||||
/>
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
SkySheet,
|
||||
SkyTabBar,
|
||||
SkyTabButton,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
SkyToggle,
|
||||
SkyToolbarPane,
|
||||
} from '@/ui'
|
||||
@@ -2352,9 +2352,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
</sky-dialog>
|
||||
|
||||
<sky-toast :opened="Boolean(toastText)" position="center">{{
|
||||
toastText
|
||||
}}</sky-toast>
|
||||
<sky-notification :opened="Boolean(toastText)" :text="toastText" />
|
||||
</sky-app-page>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ import {
|
||||
SkySettingsRow,
|
||||
SkySheet,
|
||||
SkySpinner,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
|
||||
const VOICE_MAX_DURATION_MS = 60_000
|
||||
@@ -1896,7 +1896,7 @@ onBeforeUnmount(() => {
|
||||
margin: 12px 0 8px;
|
||||
}
|
||||
|
||||
.dc-toast {
|
||||
.dc-notification {
|
||||
z-index: 150;
|
||||
}
|
||||
|
||||
@@ -2852,13 +2852,10 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</SkyDialog>
|
||||
|
||||
<SkyToast
|
||||
<SkyNotification
|
||||
:opened="Boolean(toast)"
|
||||
position="center"
|
||||
vertical-position="center"
|
||||
class="dc-toast"
|
||||
>
|
||||
{{ toast }}
|
||||
</SkyToast>
|
||||
:text="toast"
|
||||
class="dc-notification"
|
||||
/>
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
@@ -56,7 +56,7 @@ import {
|
||||
SkySegmented,
|
||||
SkySegmentedButton,
|
||||
SkySheet,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
SkyToggle,
|
||||
SkyMessagebar,
|
||||
} from '@/ui'
|
||||
@@ -2334,7 +2334,7 @@ onMounted(async () => {
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<SkyToast :opened="Boolean(feedback)">{{ feedback }}</SkyToast>
|
||||
<SkyNotification :opened="Boolean(feedback)" :text="feedback" />
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
SkyTabBar,
|
||||
SkyTabButton,
|
||||
SkyToolbarPane,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
SkyToggle,
|
||||
} from '@/ui'
|
||||
import {
|
||||
@@ -2237,9 +2237,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</sky-dialog>
|
||||
|
||||
<sky-toast :opened="Boolean(actionToast)" position="center">
|
||||
{{ actionToast }}
|
||||
</sky-toast>
|
||||
<sky-notification :opened="Boolean(actionToast)" :text="actionToast" />
|
||||
</sky-app-page>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ import {
|
||||
SkySegmentedButton,
|
||||
SkySheet,
|
||||
SkySpinner,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
SkyToggle,
|
||||
} from '@/ui'
|
||||
import { nuiCall } from '@/utils/nui'
|
||||
@@ -3361,13 +3361,11 @@ onBeforeUnmount(() => {
|
||||
</SkyDialogButton>
|
||||
</template>
|
||||
</SkyDialog>
|
||||
<SkyToast
|
||||
<SkyNotification
|
||||
:opened="Boolean(feedback)"
|
||||
position="center"
|
||||
:text="feedback"
|
||||
@click="feedback = ''"
|
||||
>
|
||||
{{ feedback }}
|
||||
</SkyToast>
|
||||
/>
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
SkySpinner,
|
||||
SkySegmented,
|
||||
SkySegmentedButton,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
import {
|
||||
ChevronLeft,
|
||||
@@ -1625,13 +1625,11 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</sky-dialog>
|
||||
|
||||
<sky-toast
|
||||
<sky-notification
|
||||
:opened="toastOpened"
|
||||
position="center"
|
||||
:text="toastText"
|
||||
@click="toastOpened = false"
|
||||
>
|
||||
{{ toastText }}
|
||||
</sky-toast>
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
SkySegmentedButton,
|
||||
SkySheet,
|
||||
SkySpinner,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
|
||||
type GarageFilter = 'all' | GarageVehicleStatus
|
||||
@@ -570,14 +570,11 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</SkyDialog>
|
||||
|
||||
<SkyToast
|
||||
<SkyNotification
|
||||
:opened="toastOpened"
|
||||
position="center"
|
||||
vertical-position="center"
|
||||
:text="toastText"
|
||||
@click="toastOpened = false"
|
||||
>
|
||||
{{ toastText }}
|
||||
</SkyToast>
|
||||
/>
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
SkyNavbar,
|
||||
SkySheet,
|
||||
SkySpinner,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
|
||||
const phone = usePhoneStore()
|
||||
@@ -572,14 +572,11 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</sky-dialog>
|
||||
|
||||
<SkyToast
|
||||
<SkyNotification
|
||||
:opened="toastOpened"
|
||||
position="center"
|
||||
vertical-position="bottom"
|
||||
:text="toastText"
|
||||
@click="toastOpened = false"
|
||||
>
|
||||
{{ toastText }}
|
||||
</SkyToast>
|
||||
/>
|
||||
</sky-app-page>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
SkyButton,
|
||||
SkyGlass,
|
||||
SkyNavbar,
|
||||
SkyNotification,
|
||||
SkyAppPage,
|
||||
SkyPillNavigation,
|
||||
SkySearchbar,
|
||||
@@ -1507,11 +1508,7 @@ onMounted(async () => {
|
||||
:app-name="phone.t('Apps.localPages.name')"
|
||||
@logged-out="tab = 'profile'"
|
||||
/>
|
||||
<Transition name="toast"
|
||||
><div v-if="feedback" class="pages__toast">
|
||||
{{ feedback }}
|
||||
</div></Transition
|
||||
>
|
||||
<SkyNotification :opened="Boolean(feedback)" :text="feedback" />
|
||||
</sky-app-page>
|
||||
</template>
|
||||
|
||||
@@ -2189,30 +2186,6 @@ onMounted(async () => {
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
}
|
||||
.pages__toast {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
right: 17px;
|
||||
bottom: 91px;
|
||||
left: 17px;
|
||||
padding: 11px;
|
||||
border-radius: 11px;
|
||||
background: #fff6cf;
|
||||
color: #1b2023;
|
||||
box-shadow: 0 8px 30px #0007;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
}
|
||||
.toast-enter-active,
|
||||
.toast-leave-active {
|
||||
transition: 0.2s;
|
||||
}
|
||||
.toast-enter-from,
|
||||
.toast-leave-to {
|
||||
transform: translateY(8px);
|
||||
opacity: 0;
|
||||
}
|
||||
.pages__header {
|
||||
height: 64px;
|
||||
padding: 6px 16px 8px;
|
||||
@@ -2738,9 +2711,6 @@ onMounted(async () => {
|
||||
.pages__photo-picker i {
|
||||
font-size: 10px;
|
||||
}
|
||||
.pages__toast {
|
||||
font-size: 12px;
|
||||
}
|
||||
.pages {
|
||||
--color-primary: var(--yellow);
|
||||
position: relative;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
SkyAppPage,
|
||||
SkySheet,
|
||||
SkySpinner,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
SkyToolbar,
|
||||
SkyToolbarPane,
|
||||
} from '@/ui'
|
||||
@@ -2046,13 +2046,11 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</sky-dialog>
|
||||
|
||||
<sky-toast
|
||||
<sky-notification
|
||||
:opened="toastOpened"
|
||||
position="center"
|
||||
:text="toastText"
|
||||
@click="toastOpened = false"
|
||||
>
|
||||
{{ toastText }}
|
||||
</sky-toast>
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
SkyGlass,
|
||||
SkySpinner,
|
||||
SkySheet,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
import {
|
||||
LocateFixed,
|
||||
@@ -850,9 +850,7 @@ onBeforeUnmount(() => {
|
||||
</sky-sheet>
|
||||
</div>
|
||||
|
||||
<sky-toast :opened="Boolean(toastText)" position="center">
|
||||
{{ toastText }}
|
||||
</sky-toast>
|
||||
<sky-notification :opened="Boolean(toastText)" :text="toastText" />
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -412,6 +412,13 @@ function onMessage(event: MessageEvent): void {
|
||||
const state = message.data as MemoRecorderState
|
||||
if (!state || !Array.isArray(state.levels)) return
|
||||
recorderState.value = state
|
||||
if (
|
||||
['paused', 'recording', 'starting', 'stopping', 'uploading'].includes(
|
||||
state.state,
|
||||
)
|
||||
) {
|
||||
view.value = 'recording'
|
||||
}
|
||||
if (state.error) showNotification(recorderError(state.error))
|
||||
} else if (message.type === 'memo:saved') {
|
||||
const memo = message.data as MemoDto
|
||||
@@ -423,12 +430,14 @@ function onMessage(event: MessageEvent): void {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => window.addEventListener('message', onMessage))
|
||||
onMounted(() => {
|
||||
window.addEventListener('message', onMessage)
|
||||
postRecorderCommand('memo:recordStateRequest')
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('message', onMessage)
|
||||
stopPlayback()
|
||||
if (recordingActive.value) postRecorderCommand('memo:recordCancel')
|
||||
if (view.value === 'detail') void persistSelected()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -308,8 +308,11 @@ describe('MessagesApp Sky UI contract', () => {
|
||||
const contactProfile = source.slice(contactProfileStart, threadScrollStart)
|
||||
const blockDialogOpened = source.indexOf(':opened="blockDialogOpened"')
|
||||
const blockDialogStart = source.lastIndexOf('<SkyDialog', blockDialogOpened)
|
||||
const toastStart = source.indexOf('<SkyToast', blockDialogStart)
|
||||
const blockDialog = source.slice(blockDialogStart, toastStart)
|
||||
const notificationStart = source.indexOf(
|
||||
'<SkyNotification',
|
||||
blockDialogStart,
|
||||
)
|
||||
const blockDialog = source.slice(blockDialogStart, notificationStart)
|
||||
|
||||
expect(contactProfile).toContain('kind="action"')
|
||||
expect(contactProfile).toContain('tone="danger"')
|
||||
|
||||
@@ -75,7 +75,7 @@ import {
|
||||
SkySettingsRow,
|
||||
SkySheet,
|
||||
SkySpinner,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
SkyToolbar,
|
||||
} from '@/ui'
|
||||
|
||||
@@ -1964,13 +1964,11 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</SkyDialog>
|
||||
|
||||
<SkyToast
|
||||
<SkyNotification
|
||||
:opened="toastOpened"
|
||||
position="center"
|
||||
:text="toastText"
|
||||
@click="toastOpened = false"
|
||||
>
|
||||
{{ toastText }}
|
||||
</SkyToast>
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
SkyRange,
|
||||
SkySearchbar,
|
||||
SkySheet,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
import {
|
||||
Check,
|
||||
@@ -1479,9 +1479,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</sky-dialog>
|
||||
|
||||
<sky-toast position="center" :opened="Boolean(toastText)">{{
|
||||
toastText
|
||||
}}</sky-toast>
|
||||
<sky-notification :opened="Boolean(toastText)" :text="toastText" />
|
||||
</sky-app-page>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ import {
|
||||
SkySegmentedButton,
|
||||
SkySheet,
|
||||
SkySpinner,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
SkyToggle,
|
||||
} from '@/ui'
|
||||
|
||||
@@ -2498,12 +2498,7 @@ onBeforeUnmount(() => {
|
||||
}}</SkyDialogButton></template
|
||||
></SkyDialog
|
||||
>
|
||||
<SkyToast
|
||||
:opened="Boolean(feedback)"
|
||||
position="center"
|
||||
vertical-position="center"
|
||||
>{{ feedback }}</SkyToast
|
||||
>
|
||||
<SkyNotification :opened="Boolean(feedback)" :text="feedback" />
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('RadioApp Sky UI contract', () => {
|
||||
expect(source).toContain('<SkyRange')
|
||||
expect(source).toContain('<SkySettingsGroup')
|
||||
expect(source).toContain('<SkySettingsRow')
|
||||
expect(source).toContain('<SkyToast')
|
||||
expect(source).toContain('<SkyNotification')
|
||||
})
|
||||
|
||||
it('renders Radio and Settings in full-width bottom Glass navigation', () => {
|
||||
@@ -186,6 +186,7 @@ describe('RadioApp Sky UI contract', () => {
|
||||
expect(source).toContain('<SkyEmptyState')
|
||||
expect(source).toContain('role="alert"')
|
||||
expect(source).toContain(':opened="Boolean(feedback)"')
|
||||
expect(source).toContain('vertical-position="center"')
|
||||
expect(source).toContain(':text="feedback"')
|
||||
expect(source).not.toContain('vertical-position=')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
SkySettingsRow,
|
||||
SkySpinner,
|
||||
SkyStatusCard,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
SkyToggle,
|
||||
} from '@/ui'
|
||||
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
|
||||
@@ -537,14 +537,11 @@ onBeforeUnmount(() => {
|
||||
</SkySegmented>
|
||||
</SkyPillNavigation>
|
||||
|
||||
<SkyToast
|
||||
<SkyNotification
|
||||
:opened="Boolean(feedback)"
|
||||
position="center"
|
||||
vertical-position="center"
|
||||
:text="feedback"
|
||||
@click="feedback = ''"
|
||||
>
|
||||
{{ feedback }}
|
||||
</SkyToast>
|
||||
/>
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ import {
|
||||
SkySettingsRangeRow,
|
||||
SkySettingsRow,
|
||||
SkySpinner,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
import {
|
||||
APPEARANCE_MODE_IDS,
|
||||
@@ -1863,14 +1863,11 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</SkyDialog>
|
||||
|
||||
<SkyToast
|
||||
<SkyNotification
|
||||
:opened="Boolean(accountToast)"
|
||||
position="center"
|
||||
vertical-position="center"
|
||||
:text="accountToast"
|
||||
@click="accountToast = ''"
|
||||
>
|
||||
{{ accountToast }}
|
||||
</SkyToast>
|
||||
/>
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ import {
|
||||
SkySpinner as kPreloader,
|
||||
SkyTabBar as kTabbar,
|
||||
SkyTabButton as kTabbarLink,
|
||||
SkyToast as kToast,
|
||||
SkyNotification as kNotification,
|
||||
SkyToggle as kToggle,
|
||||
} from '@/ui'
|
||||
|
||||
@@ -1747,9 +1747,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
</k-dialog>
|
||||
|
||||
<k-toast :opened="Boolean(toastText)" position="center">{{
|
||||
toastText
|
||||
}}</k-toast>
|
||||
<k-notification :opened="Boolean(toastText)" :text="toastText" />
|
||||
</k-page>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -83,4 +83,11 @@ describe('phone apps use Sky UI', () => {
|
||||
expect(source, file).not.toMatch(/\b:colors=/)
|
||||
}
|
||||
})
|
||||
|
||||
it('uses top notifications instead of app-level toasts', () => {
|
||||
for (const { file, source } of appSources) {
|
||||
expect(source, file).not.toMatch(/\bSkyToast\b|<sky-toast|<k-toast/)
|
||||
expect(source, file).not.toMatch(/(?:citymarkt|pages)__toast/)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
SkyNavbar,
|
||||
SkyScrollArea,
|
||||
SkySpinner,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
|
||||
const phone = usePhoneStore()
|
||||
@@ -283,14 +283,11 @@ watch(
|
||||
</SkyLink>
|
||||
</div>
|
||||
|
||||
<SkyToast
|
||||
<SkyNotification
|
||||
:opened="cooldownToastOpened"
|
||||
position="center"
|
||||
vertical-position="bottom"
|
||||
:text="phone.t('Apps.weather.errors.reload_cooldown')"
|
||||
@click="closeCooldownToast"
|
||||
>
|
||||
{{ phone.t('Apps.weather.errors.reload_cooldown') }}
|
||||
</SkyToast>
|
||||
/>
|
||||
</SkyAppPage>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
SkySearchbar,
|
||||
SkySegmented,
|
||||
SkySegmentedButton,
|
||||
SkyToast,
|
||||
SkyNotification,
|
||||
} from '@/ui'
|
||||
import {
|
||||
BriefcaseBusiness,
|
||||
@@ -1514,13 +1514,11 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</sky-dialog>
|
||||
|
||||
<sky-toast
|
||||
<sky-notification
|
||||
:opened="toastOpened"
|
||||
position="center"
|
||||
:text="toastText"
|
||||
@click="toastOpened = false"
|
||||
>
|
||||
{{ toastText }}
|
||||
</sky-toast>
|
||||
/>
|
||||
</sky-app-page>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user