mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 17:23:25 +00:00
ADD - expand phone application platform
Add the Companies app, resilient call handling, custom app registry, vendor compatibility, storage policies, frontend bridge, tests, documentation, config, locale, and SQL migrations.
This commit is contained in:
@@ -11,7 +11,11 @@ import { Gamepad2, Grid2X2, Search } from 'lucide-vue-next'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { PHONE_APPS } from '@/config/apps'
|
||||
import {
|
||||
getPhoneAppLabel,
|
||||
isLaunchablePhoneApp,
|
||||
PHONE_APPS,
|
||||
} from '@/config/apps'
|
||||
import { useAppStoreStore } from '@/stores/app-store'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
import type {
|
||||
@@ -35,16 +39,11 @@ const tabBarColors = {
|
||||
}
|
||||
const catalog = computed(() =>
|
||||
PHONE_APPS.filter((app): app is LaunchablePhoneAppDefinition => {
|
||||
if (
|
||||
app.component === null ||
|
||||
app.route === null ||
|
||||
app.id === 'app-store'
|
||||
) {
|
||||
if (!isLaunchablePhoneApp(app) || app.id === 'app-store') {
|
||||
return false
|
||||
}
|
||||
|
||||
const installed =
|
||||
app.category !== 'games' || appStore.claimedApps.includes(app.id)
|
||||
const installed = appStore.isInstalled(app.id)
|
||||
return (
|
||||
!installed ||
|
||||
appStore.homeLayout.hidden.includes(app.id) ||
|
||||
@@ -63,7 +62,9 @@ const shownApps = computed(() => {
|
||||
const search = query.value.trim().toLocaleLowerCase(phone.lang)
|
||||
if (!search) return catalog.value
|
||||
return catalog.value.filter((app) =>
|
||||
phone.t(app.labelKey).toLocaleLowerCase(phone.lang).includes(search),
|
||||
getPhoneAppLabel(app, phone.t)
|
||||
.toLocaleLowerCase(phone.lang)
|
||||
.includes(search),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -76,8 +77,7 @@ function appAction(
|
||||
): 'get' | 'installing' | 'open' {
|
||||
if (appStore.installingApps[app.id]) return 'installing'
|
||||
|
||||
const installed =
|
||||
app.category !== 'games' || appStore.claimedApps.includes(app.id)
|
||||
const installed = appStore.isInstalled(app.id)
|
||||
if (
|
||||
installed &&
|
||||
!appStore.homeLayout.hidden.includes(app.id) &&
|
||||
@@ -130,13 +130,13 @@ function handleApp(app: LaunchablePhoneAppDefinition): void {
|
||||
draggable="false"
|
||||
/>
|
||||
<div>
|
||||
<strong>{{ phone.t(app.labelKey) }}</strong>
|
||||
<strong>{{ getPhoneAppLabel(app, phone.t) }}</strong>
|
||||
<small>{{ phone.t(`Home.groups.${app.category}`) }}</small>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="appStore.installingApps[app.id]"
|
||||
:aria-label="`${phone.t(app.labelKey)} ${phone.t(
|
||||
:aria-label="`${getPhoneAppLabel(app, phone.t)} ${phone.t(
|
||||
`Apps.appStore.${appAction(app)}`,
|
||||
)}`"
|
||||
@click="handleApp(app)"
|
||||
|
||||
@@ -51,6 +51,7 @@ import type {
|
||||
BillingStatus,
|
||||
InvoiceSummary,
|
||||
} from '@/types/billing'
|
||||
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
|
||||
|
||||
type BillingTab = 'overview' | 'inbox' | 'history'
|
||||
type BillingScreen = 'main' | 'detail'
|
||||
@@ -225,6 +226,7 @@ async function disputeInvoice(): Promise<void> {
|
||||
}
|
||||
|
||||
function onBillingMessage(event: MessageEvent): void {
|
||||
if (!isTrustedRootMessageSource(event.source, window)) return
|
||||
if (
|
||||
event.data?.type !== 'billing:changed' &&
|
||||
event.data?.type !== 'billing:new'
|
||||
|
||||
@@ -20,6 +20,7 @@ import type { MediaType, PhoneMedia, UploadResult } from '@/types/media'
|
||||
import { createGameView, type GameView } from '@/utils/gameView'
|
||||
import { formatRecordingDuration, mediaErrorKey } from '@/utils/media'
|
||||
import { nuiCall } from '@/utils/nui'
|
||||
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
|
||||
|
||||
type CaptureItem = {
|
||||
error?: string
|
||||
@@ -333,6 +334,7 @@ function onKeydown(event: KeyboardEvent): void {
|
||||
}
|
||||
|
||||
function onMessage(event: MessageEvent): void {
|
||||
if (!isTrustedRootMessageSource(event.source, window)) return
|
||||
const message = event.data as {
|
||||
data?: Record<string, unknown>
|
||||
type?: string
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -80,6 +80,7 @@ import type {
|
||||
} from '@/types/crewlink'
|
||||
import { copyText } from '@/utils/clipboard'
|
||||
import { nuiCall } from '@/utils/nui'
|
||||
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
|
||||
|
||||
type CrewLinkTab = 'map' | 'group' | 'pings' | 'profile'
|
||||
type CrewLinkSheet =
|
||||
@@ -644,6 +645,7 @@ function onWheel(event: WheelEvent): void {
|
||||
}
|
||||
|
||||
function onCrewLinkMessage(event: MessageEvent): void {
|
||||
if (!isTrustedRootMessageSource(event.source, window)) return
|
||||
if (event.data?.type === 'crewlink:changed') void crew.bootstrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
mergeMedia,
|
||||
} from '@/utils/media'
|
||||
import { nuiCall } from '@/utils/nui'
|
||||
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
|
||||
|
||||
const isDevelopment = import.meta.env.DEV
|
||||
const developmentGalleryState = isDevelopment
|
||||
@@ -331,6 +332,7 @@ async function deleteSelected(): Promise<void> {
|
||||
}
|
||||
|
||||
function onMessage(event: MessageEvent): void {
|
||||
if (!isTrustedRootMessageSource(event.source, window)) return
|
||||
const message = event.data as { data?: DeleteResult; type?: string }
|
||||
if (
|
||||
message.type !== 'media:deleteResult' ||
|
||||
|
||||
@@ -44,6 +44,7 @@ import type {
|
||||
GarageVehicleStatus,
|
||||
GarageValetState,
|
||||
} from '@/types/garage'
|
||||
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
|
||||
|
||||
type GarageFilter = 'all' | GarageVehicleStatus
|
||||
|
||||
@@ -208,6 +209,7 @@ async function cancelValet(): Promise<void> {
|
||||
}
|
||||
|
||||
function handleValetStatus(event: MessageEvent): void {
|
||||
if (!isTrustedRootMessageSource(event.source, window)) return
|
||||
if (event.data?.type !== 'garage:valet-status') return
|
||||
garage.setValetState((event.data.data as GarageValetState | null) ?? null)
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
type MailSwipeAction,
|
||||
type MailSwipeAxis,
|
||||
} from '@/utils/mailSwipe'
|
||||
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
|
||||
|
||||
type AuthMode = 'login' | 'register'
|
||||
type MailScreen = 'folders' | 'list' | 'message' | 'compose'
|
||||
@@ -594,6 +595,7 @@ function goBack(): void {
|
||||
}
|
||||
|
||||
function onMailEvent(event: MessageEvent<MailEvent>): void {
|
||||
if (!isTrustedRootMessageSource(event.source, window)) return
|
||||
if (event.data.type === 'mail:changed' && event.data.data?.counts) {
|
||||
mail.setCounts(event.data.data.counts)
|
||||
if (screen.value === 'list') {
|
||||
|
||||
@@ -121,8 +121,11 @@ const knownContactNumbers = computed(
|
||||
)
|
||||
const contactSuggestions = computed(() => {
|
||||
const query = composerNumber.value.trim().toLocaleLowerCase(phone.lang)
|
||||
if (!query) return calls.contacts.slice(0, 8)
|
||||
return calls.contacts
|
||||
const contacts = calls.contacts.filter(
|
||||
(contact) => contact.canMessage !== false,
|
||||
)
|
||||
if (!query) return contacts.slice(0, 8)
|
||||
return contacts
|
||||
.filter((contact) =>
|
||||
`${contact.name} ${contact.phone_number}`
|
||||
.toLocaleLowerCase(phone.lang)
|
||||
@@ -138,6 +141,9 @@ const activeContact = computed(() =>
|
||||
(contact) => contact.phone_number === messages.activeNumber,
|
||||
),
|
||||
)
|
||||
const activeCanMessage = computed(
|
||||
() => activeContact.value?.canMessage !== false,
|
||||
)
|
||||
const attachmentPanelOpen = computed(
|
||||
() => emojiOpen.value || attachmentPicker.value !== null,
|
||||
)
|
||||
@@ -348,6 +354,13 @@ function beginCompose(): void {
|
||||
}
|
||||
|
||||
async function chooseRecipient(number: string): Promise<void> {
|
||||
const contact = calls.contacts.find(
|
||||
(candidate) => candidate.phone_number === number,
|
||||
)
|
||||
if (contact?.canMessage === false) {
|
||||
showToast(phone.t('Apps.messages.messagingUnavailable'))
|
||||
return
|
||||
}
|
||||
composerNumber.value = number
|
||||
if (!(await messages.openThread(number))) {
|
||||
showToast(errorText('invalid_number'))
|
||||
@@ -386,6 +399,10 @@ function openContactDetails(): void {
|
||||
}
|
||||
|
||||
async function saveContactDetails(): Promise<void> {
|
||||
if (activeContact.value?.readonly) {
|
||||
showToast(phone.t('Apps.phone.errors.readonly_contact'))
|
||||
return
|
||||
}
|
||||
if (!contactNameDraft.value.trim() || !contactNumberDraft.value.trim()) return
|
||||
const response = await calls.saveContact({
|
||||
id: activeContact.value?.id,
|
||||
@@ -403,6 +420,10 @@ async function saveContactDetails(): Promise<void> {
|
||||
|
||||
async function deleteActiveContact(): Promise<void> {
|
||||
if (!activeContact.value) return
|
||||
if (activeContact.value.readonly) {
|
||||
showToast(phone.t('Apps.phone.errors.readonly_contact'))
|
||||
return
|
||||
}
|
||||
if (!(await calls.deleteContact(activeContact.value.id))) {
|
||||
showToast(phone.t('Apps.messages.contactDeleteFailed'))
|
||||
return
|
||||
@@ -412,7 +433,7 @@ async function deleteActiveContact(): Promise<void> {
|
||||
}
|
||||
|
||||
async function callActiveContact(): Promise<void> {
|
||||
if (!messages.activeNumber) return
|
||||
if (!messages.activeNumber || activeContact.value?.canCall === false) return
|
||||
const response = await calls.dial(messages.activeNumber)
|
||||
if (!response.success) showToast(phone.t('Apps.messages.callFailed'))
|
||||
}
|
||||
@@ -454,7 +475,7 @@ async function sendAttachment(
|
||||
mediaAssetId: string,
|
||||
mediaDurationMs?: number,
|
||||
): Promise<void> {
|
||||
if (!messages.activeNumber || sending.value) return
|
||||
if (!messages.activeNumber || !activeCanMessage.value || sending.value) return
|
||||
attachmentMenuOpen.value = false
|
||||
attachmentPicker.value = null
|
||||
sending.value = true
|
||||
@@ -504,7 +525,14 @@ function queueGifSearch(): void {
|
||||
}
|
||||
|
||||
async function sendTextMessage(): Promise<void> {
|
||||
if (!messages.activeNumber || !draft.value.trim() || sending.value) return
|
||||
if (
|
||||
!messages.activeNumber ||
|
||||
!activeCanMessage.value ||
|
||||
!draft.value.trim() ||
|
||||
sending.value
|
||||
) {
|
||||
return
|
||||
}
|
||||
const body = draft.value
|
||||
draft.value = ''
|
||||
emojiOpen.value = false
|
||||
@@ -542,6 +570,7 @@ function sampleMicrophone(): void {
|
||||
}
|
||||
|
||||
async function startVoiceRecording(): Promise<void> {
|
||||
if (!activeCanMessage.value) return
|
||||
emojiOpen.value = false
|
||||
if (
|
||||
!navigator.mediaDevices?.getUserMedia ||
|
||||
@@ -660,7 +689,7 @@ async function finishVoiceRecording(): Promise<void> {
|
||||
const waveform = compressedWaveform()
|
||||
const shouldDiscard = discardRecording
|
||||
cleanupRecorder()
|
||||
if (shouldDiscard) return
|
||||
if (shouldDiscard || !activeCanMessage.value) return
|
||||
const blob = new Blob(chunks, { type: mime })
|
||||
if (!blob.size || blob.size > VOICE_MAX_BYTES) {
|
||||
showToast(phone.t('Apps.messages.recordingTooLarge'))
|
||||
@@ -1011,6 +1040,7 @@ onBeforeUnmount(() => {
|
||||
</button>
|
||||
<strong>{{ phone.t('Apps.messages.contactDetails') }}</strong>
|
||||
<button
|
||||
v-if="!activeContact?.readonly"
|
||||
type="button"
|
||||
@click="
|
||||
contactEditing ? saveContactDetails() : (contactEditing = true)
|
||||
@@ -1035,14 +1065,27 @@ onBeforeUnmount(() => {
|
||||
</span>
|
||||
</span>
|
||||
<h2>{{ activeTitle }}</h2>
|
||||
<small>{{ messages.activeNumber }}</small>
|
||||
<small>
|
||||
{{ messages.activeNumber }}
|
||||
<template v-if="activeContact?.readonly">
|
||||
· {{ phone.t('Apps.phone.officialContact') }}
|
||||
</template>
|
||||
</small>
|
||||
</div>
|
||||
<div class="messages-contact-details__actions">
|
||||
<button type="button" @click="callActiveContact">
|
||||
<button
|
||||
v-if="activeContact?.canCall !== false"
|
||||
type="button"
|
||||
@click="callActiveContact"
|
||||
>
|
||||
<PhoneIcon :size="20" />
|
||||
<span>{{ phone.t('Apps.messages.call') }}</span>
|
||||
</button>
|
||||
<button type="button" @click="contactDetailsOpen = false">
|
||||
<button
|
||||
v-if="activeCanMessage"
|
||||
type="button"
|
||||
@click="contactDetailsOpen = false"
|
||||
>
|
||||
<MessageCircle :size="20" />
|
||||
<span>{{ phone.t('Apps.messages.messageAction') }}</span>
|
||||
</button>
|
||||
@@ -1052,7 +1095,7 @@ onBeforeUnmount(() => {
|
||||
<span>{{ phone.t('Apps.messages.contactName') }}</span>
|
||||
<input
|
||||
v-model="contactNameDraft"
|
||||
:readonly="!contactEditing"
|
||||
:readonly="!contactEditing || activeContact?.readonly"
|
||||
:placeholder="phone.t('Apps.messages.contactName')"
|
||||
/>
|
||||
</label>
|
||||
@@ -1060,7 +1103,7 @@ onBeforeUnmount(() => {
|
||||
<span>{{ phone.t('Apps.messages.phoneNumber') }}</span>
|
||||
<input
|
||||
v-model="contactNumberDraft"
|
||||
:readonly="!contactEditing"
|
||||
:readonly="!contactEditing || activeContact?.readonly"
|
||||
inputmode="tel"
|
||||
/>
|
||||
</label>
|
||||
@@ -1075,7 +1118,7 @@ onBeforeUnmount(() => {
|
||||
{{ phone.t('Apps.messages.addContact') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
v-else-if="!activeContact.readonly"
|
||||
type="button"
|
||||
class="messages-contact-details__delete"
|
||||
@click="deleteActiveContact"
|
||||
@@ -1119,7 +1162,10 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</k-messages>
|
||||
|
||||
<section v-if="attachmentMenuOpen" class="messages-attachment-menu">
|
||||
<section
|
||||
v-if="activeCanMessage && attachmentMenuOpen"
|
||||
class="messages-attachment-menu"
|
||||
>
|
||||
<button type="button" @click="openMediaApp('photos', 'photo')">
|
||||
<span><Images :size="20" /></span>
|
||||
{{ phone.t('Apps.messages.attachPhoto') }}
|
||||
@@ -1142,7 +1188,10 @@ onBeforeUnmount(() => {
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section v-if="attachmentPicker" class="messages-media-picker">
|
||||
<section
|
||||
v-if="activeCanMessage && attachmentPicker"
|
||||
class="messages-media-picker"
|
||||
>
|
||||
<header>
|
||||
<strong>
|
||||
{{ phone.t('Apps.messages.gifs') }}
|
||||
@@ -1190,12 +1239,12 @@ onBeforeUnmount(() => {
|
||||
</section>
|
||||
|
||||
<FullEmojiPicker
|
||||
v-if="emojiOpen"
|
||||
v-if="activeCanMessage && emojiOpen"
|
||||
@close="emojiOpen = false"
|
||||
@pick="appendEmoji"
|
||||
/>
|
||||
|
||||
<section v-if="recording" class="messages-recorder">
|
||||
<section v-if="activeCanMessage && recording" class="messages-recorder">
|
||||
<button
|
||||
type="button"
|
||||
class="messages-recorder__cancel"
|
||||
@@ -1224,7 +1273,7 @@ onBeforeUnmount(() => {
|
||||
</section>
|
||||
|
||||
<k-messagebar
|
||||
v-else
|
||||
v-else-if="activeCanMessage"
|
||||
class="messages-messagebar"
|
||||
:placeholder="phone.t('Apps.messages.message')"
|
||||
:value="draft"
|
||||
@@ -1270,6 +1319,9 @@ onBeforeUnmount(() => {
|
||||
</k-toolbar-pane>
|
||||
</template>
|
||||
</k-messagebar>
|
||||
<k-block v-else class="text-center text-sm text-[#8e8e93]">
|
||||
{{ phone.t('Apps.messages.messagingUnavailable') }}
|
||||
</k-block>
|
||||
</k-page>
|
||||
|
||||
<k-toast :opened="toastOpened" position="center" @click="toastOpened = false">
|
||||
|
||||
@@ -102,6 +102,10 @@ function openContact(contact?: PhoneContact, number = ''): void {
|
||||
}
|
||||
|
||||
async function saveContact(): Promise<void> {
|
||||
if (editingContact.value?.readonly) {
|
||||
error.value = phone.t('Apps.phone.errors.readonly_contact')
|
||||
return
|
||||
}
|
||||
const number = normalizePhoneNumber(contactNumber.value)
|
||||
if (!contactName.value.trim() || !number) {
|
||||
error.value = phone.t('Apps.phone.errors.invalid_contact')
|
||||
@@ -137,6 +141,10 @@ async function answerCall(): Promise<void> {
|
||||
|
||||
async function deleteEditedContact(): Promise<void> {
|
||||
if (!editingContact.value) return
|
||||
if (editingContact.value.readonly) {
|
||||
error.value = phone.t('Apps.phone.errors.readonly_contact')
|
||||
return
|
||||
}
|
||||
if (await calls.deleteContact(editingContact.value.id)) {
|
||||
editorOpened.value = false
|
||||
}
|
||||
@@ -299,12 +307,17 @@ onMounted(() => {
|
||||
v-for="contact in visibleContacts"
|
||||
:key="contact.id"
|
||||
:title="contact.name"
|
||||
:subtitle="formatPhoneNumber(contact.phone_number)"
|
||||
:subtitle="
|
||||
contact.readonly
|
||||
? `${formatPhoneNumber(contact.phone_number)} · ${phone.t('Apps.phone.officialContact')}`
|
||||
: formatPhoneNumber(contact.phone_number)
|
||||
"
|
||||
link
|
||||
@click="openContact(contact)"
|
||||
>
|
||||
<template #after>
|
||||
<k-button
|
||||
v-if="contact.canCall !== false"
|
||||
clear
|
||||
rounded
|
||||
@click.stop="startCall(contact.phone_number)"
|
||||
@@ -402,31 +415,44 @@ onMounted(() => {
|
||||
<k-dialog :opened="editorOpened" @backdropclick="editorOpened = false">
|
||||
<template #title>{{
|
||||
phone.t(
|
||||
editingContact ? 'Apps.phone.editContact' : 'Apps.phone.addContact',
|
||||
editingContact?.readonly
|
||||
? 'Apps.phone.officialContact'
|
||||
: editingContact
|
||||
? 'Apps.phone.editContact'
|
||||
: 'Apps.phone.addContact',
|
||||
)
|
||||
}}</template>
|
||||
<k-list strong inset>
|
||||
<k-list-input
|
||||
:value="contactName"
|
||||
:label="phone.t('Apps.phone.contactName')"
|
||||
:readonly="editingContact?.readonly"
|
||||
@input="contactName = eventValue($event)"
|
||||
/>
|
||||
<k-list-input
|
||||
:value="contactNumber"
|
||||
:label="phone.t('Apps.phone.phoneNumber')"
|
||||
inputmode="numeric"
|
||||
:readonly="editingContact?.readonly"
|
||||
@input="contactNumber = eventValue($event)"
|
||||
/>
|
||||
</k-list>
|
||||
<p v-if="error" class="text-sm text-[#ff3b30]">{{ error }}</p>
|
||||
<template #buttons>
|
||||
<k-dialog-button @click="editorOpened = false">{{
|
||||
phone.t('Common.cancel')
|
||||
phone.t(editingContact?.readonly ? 'Common.done' : 'Common.cancel')
|
||||
}}</k-dialog-button>
|
||||
<k-dialog-button v-if="editingContact" @click="deleteEditedContact">{{
|
||||
<k-dialog-button
|
||||
v-if="editingContact && !editingContact.readonly"
|
||||
@click="deleteEditedContact"
|
||||
>{{
|
||||
phone.t('Common.delete')
|
||||
}}</k-dialog-button>
|
||||
<k-dialog-button strong @click="saveContact">{{
|
||||
<k-dialog-button
|
||||
v-if="!editingContact?.readonly"
|
||||
strong
|
||||
@click="saveContact"
|
||||
>{{
|
||||
phone.t('Common.save')
|
||||
}}</k-dialog-button>
|
||||
</template>
|
||||
|
||||
@@ -28,6 +28,7 @@ import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
import { useRadioStore } from '@/stores/radio'
|
||||
import type { RadioHistoryEntry } from '@/types/radio'
|
||||
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
|
||||
|
||||
type RadioTab = 'radio' | 'settings'
|
||||
|
||||
@@ -152,6 +153,7 @@ async function saveRadioProfile(): Promise<void> {
|
||||
}
|
||||
|
||||
function onMessage(event: MessageEvent): void {
|
||||
if (!isTrustedRootMessageSource(event.source, window)) return
|
||||
if (event.data?.type === 'radio:updated' && event.data.data?.members) {
|
||||
memberSnapshotAt.value = Date.now()
|
||||
radio.updateMembers(event.data.data.members)
|
||||
|
||||
@@ -49,7 +49,11 @@ import {
|
||||
} from 'vue'
|
||||
|
||||
import { PHONE_FRAME_COLORS } from '@/config/appearance'
|
||||
import { isLaunchablePhoneApp, PHONE_APPS } from '@/config/apps'
|
||||
import {
|
||||
getPhoneAppLabel,
|
||||
isLaunchablePhoneApp,
|
||||
PHONE_APPS,
|
||||
} from '@/config/apps'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
import PhonePasscode from '@/components/PhonePasscode.vue'
|
||||
import { useAccountStore } from '@/stores/account'
|
||||
@@ -281,7 +285,7 @@ const activeTitle = computed(() => {
|
||||
}
|
||||
if (activeView.value === 'notification-detail') {
|
||||
return selectedNotificationApp.value
|
||||
? phone.t(selectedNotificationApp.value.labelKey)
|
||||
? getPhoneAppLabel(selectedNotificationApp.value, phone.t)
|
||||
: phone.t('Apps.settings.notifications')
|
||||
}
|
||||
return phone.t(`Apps.settings.${activeView.value}`)
|
||||
@@ -1030,7 +1034,7 @@ onBeforeUnmount(() => {
|
||||
v-for="app in notificationApps"
|
||||
:key="app.id"
|
||||
link
|
||||
:title="phone.t(app.labelKey)"
|
||||
:title="getPhoneAppLabel(app, phone.t)"
|
||||
:after="
|
||||
phone.t(
|
||||
phone.preferences.settings.notifications[app.id].enabled
|
||||
@@ -1058,7 +1062,9 @@ onBeforeUnmount(() => {
|
||||
"
|
||||
>
|
||||
<k-list strong inset>
|
||||
<k-list-item :title="phone.t(selectedNotificationApp.labelKey)">
|
||||
<k-list-item
|
||||
:title="getPhoneAppLabel(selectedNotificationApp, phone.t)"
|
||||
>
|
||||
<template #media>
|
||||
<img
|
||||
class="w-12 h-12 object-contain"
|
||||
@@ -1080,7 +1086,7 @@ onBeforeUnmount(() => {
|
||||
"
|
||||
:aria-label="
|
||||
phone.t('Apps.settings.toggle.notifications', {
|
||||
app: phone.t(selectedNotificationApp.labelKey),
|
||||
app: getPhoneAppLabel(selectedNotificationApp, phone.t),
|
||||
})
|
||||
"
|
||||
@change="
|
||||
@@ -1110,7 +1116,7 @@ onBeforeUnmount(() => {
|
||||
"
|
||||
:aria-label="
|
||||
phone.t('Apps.settings.toggle.notificationSounds', {
|
||||
app: phone.t(selectedNotificationApp.labelKey),
|
||||
app: getPhoneAppLabel(selectedNotificationApp, phone.t),
|
||||
})
|
||||
"
|
||||
@change="
|
||||
|
||||
@@ -78,6 +78,7 @@ import type {
|
||||
SkyRideRide,
|
||||
SkyRideRideStatus,
|
||||
} from '@/types/skyride'
|
||||
import { isTrustedRootMessageSource } from '@/utils/windowMessages'
|
||||
|
||||
type SkyRideTab = 'home' | 'rides' | 'activity' | 'messages' | 'profile'
|
||||
type LocationTarget = 'pickup' | 'destination'
|
||||
@@ -677,6 +678,7 @@ function selectTab(tab: SkyRideTab): void {
|
||||
}
|
||||
|
||||
function handleSkyRideMessage(event: MessageEvent<unknown>): void {
|
||||
if (!isTrustedRootMessageSource(event.source, window)) return
|
||||
if (typeof event.data !== 'object' || event.data === null) return
|
||||
const message = event.data as Partial<SkyRideChangedMessage>
|
||||
if (message.type !== 'skyride:changed' || !message.data) return
|
||||
|
||||
Reference in New Issue
Block a user