mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 17:23:25 +00:00
MERGE - resolve dev integration conflicts
This commit is contained in:
+178
-6
@@ -14,6 +14,7 @@ import PhoneHomeIndicator from '@/components/PhoneHomeIndicator.vue'
|
||||
import PhoneControlCenter from '@/components/PhoneControlCenter.vue'
|
||||
import PhoneMediaCapture from '@/components/PhoneMediaCapture.vue'
|
||||
import PhoneLockScreen from '@/components/PhoneLockScreen.vue'
|
||||
import PhonePasscode from '@/components/PhonePasscode.vue'
|
||||
import PhoneNotifications from '@/components/PhoneNotifications.vue'
|
||||
import NotificationPhonePreview from '@/components/NotificationPhonePreview.vue'
|
||||
import PhoneStatusBar from '@/components/PhoneStatusBar.vue'
|
||||
@@ -30,6 +31,7 @@ import { useMailStore } from '@/stores/mail'
|
||||
import { useMessagesStore } from '@/stores/messages'
|
||||
import { useDarkChatStore } from '@/stores/darkchat'
|
||||
import { useFlareStore } from '@/stores/flare'
|
||||
import { useFlipTokStore } from '@/stores/fliptok'
|
||||
import { useMediaStore } from '@/stores/media'
|
||||
import { useMarketplaceStore } from '@/stores/marketplace'
|
||||
import { useAppStoreStore } from '@/stores/app-store'
|
||||
@@ -60,6 +62,8 @@ type AppMessage = {
|
||||
| MessagesEventData
|
||||
| DarkChatEventData
|
||||
| FlareEventData
|
||||
| FlipTokVerificationData
|
||||
| FlipTokNotificationData
|
||||
| PhoneCall
|
||||
| PhoneNotificationInput
|
||||
| PhoneOpenPayload
|
||||
@@ -124,6 +128,20 @@ type CalendarReminderData = {
|
||||
text?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
type FlipTokVerificationData = {
|
||||
profileId: number
|
||||
verified: boolean
|
||||
}
|
||||
|
||||
type FlipTokNotificationData = {
|
||||
actor?: string
|
||||
device?: PhoneNotificationDevicePayload
|
||||
kind?: 'like' | 'comment' | 'follow' | 'verified'
|
||||
text?: string
|
||||
title?: string
|
||||
videoId?: string
|
||||
}
|
||||
const REFERENCE_VIEWPORT_WIDTH = 1920
|
||||
const REFERENCE_VIEWPORT_HEIGHT = 1080
|
||||
const PHONE_BASE_SCALE = 0.69
|
||||
@@ -140,6 +158,7 @@ const mail = useMailStore()
|
||||
const messages = useMessagesStore()
|
||||
const darkchat = useDarkChatStore()
|
||||
const flare = useFlareStore()
|
||||
const fliptok = useFlipTokStore()
|
||||
const media = useMediaStore()
|
||||
const marketplace = useMarketplaceStore()
|
||||
const appStore = useAppStoreStore()
|
||||
@@ -155,6 +174,13 @@ const appTransitionName = computed(() =>
|
||||
)
|
||||
const isLocked = ref(false)
|
||||
const isUnlocking = ref(false)
|
||||
const passcodeBusy = ref(false)
|
||||
const passcodeError = ref('')
|
||||
const passcodeResetKey = ref(0)
|
||||
const passcodeRetrySeconds = ref(0)
|
||||
const passcodeVisible = ref(false)
|
||||
const pendingUnlockRoute = ref<string | null>(null)
|
||||
const unlockedServicesLoaded = ref(false)
|
||||
const controlCenterOpened = ref(false)
|
||||
const simPicker = ref<SimPickerPayload | null>(null)
|
||||
const systemColorScheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
@@ -180,6 +206,7 @@ const phoneFrameImage = computed(
|
||||
)
|
||||
let clockTicker: ReturnType<typeof setInterval> | undefined
|
||||
let unlockTimer: number | undefined
|
||||
let passcodeLockTimer: number | undefined
|
||||
|
||||
function getViewportScale(): number {
|
||||
const heightScale = window.innerHeight / REFERENCE_VIEWPORT_HEIGHT
|
||||
@@ -197,12 +224,17 @@ function hydratePhone(payload: PhoneOpenPayload): void {
|
||||
media.hydrate(payload.device?.data.media?.payload)
|
||||
appStore.hydrate(payload.device?.data.apps?.payload)
|
||||
widgets.hydrate(payload.device?.data.widgets?.payload)
|
||||
void mail.bootstrap(payload.account?.email ?? '')
|
||||
if (payload.account?.email) void marketplace.loadCounts()
|
||||
}
|
||||
|
||||
function loadUnlockedPhoneData(): void {
|
||||
if (unlockedServicesLoaded.value) return
|
||||
unlockedServicesLoaded.value = true
|
||||
void mail.bootstrap(account.email)
|
||||
if (account.email) void marketplace.loadCounts()
|
||||
else marketplace.setCounts({ active: 0, unread: 0 })
|
||||
void calls.bootstrap()
|
||||
void messages.loadConversations()
|
||||
if (payload.account?.email) void darkchat.bootstrap()
|
||||
if (account.email) void darkchat.bootstrap()
|
||||
}
|
||||
|
||||
async function hydrateDevelopmentPhone(): Promise<void> {
|
||||
@@ -274,6 +306,32 @@ function onMessage(event: MessageEvent<AppMessage>): void {
|
||||
} else if (event.data?.type === 'marketplace:changed' && event.data.data) {
|
||||
const data = event.data.data as MarketplaceEventData
|
||||
if (data.counts) marketplace.setCounts(data.counts)
|
||||
} else if (
|
||||
event.data?.type === 'fliptok:verification-changed' &&
|
||||
event.data.data
|
||||
) {
|
||||
const data = event.data.data as FlipTokVerificationData
|
||||
fliptok.applyVerification(Number(data.profileId), data.verified === true)
|
||||
} else if (event.data?.type === 'fliptok:new' && event.data.data) {
|
||||
const data = event.data.data as FlipTokNotificationData
|
||||
const notification: PhoneNotificationInput = {
|
||||
appId: 'fliptok',
|
||||
subtitle: data.actor,
|
||||
text: data.text ?? phone.t('Apps.fliptok.notifications.default'),
|
||||
title: data.title ?? phone.t('Apps.fliptok.name'),
|
||||
}
|
||||
if (
|
||||
data.device &&
|
||||
(!phone.isOpen || data.device.imei !== phone.device?.imei)
|
||||
) {
|
||||
notification.device = {
|
||||
imei: data.device.imei,
|
||||
name: data.device.name,
|
||||
preferences: parsePhonePreferences(data.device.settings ?? null),
|
||||
}
|
||||
}
|
||||
notifications.show(notification)
|
||||
if (phone.isOpen) void fliptok.loadActivities()
|
||||
} else if (
|
||||
event.data?.type === 'marketplace:new-message' &&
|
||||
event.data.data
|
||||
@@ -442,8 +500,11 @@ function onMessage(event: MessageEvent<AppMessage>): void {
|
||||
) {
|
||||
calls.applyCallState(event.data.data as PhoneCall)
|
||||
controlCenterOpened.value = false
|
||||
isLocked.value = false
|
||||
isUnlocking.value = false
|
||||
if (!phone.security.enabled) {
|
||||
isLocked.value = false
|
||||
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
|
||||
@@ -470,14 +531,78 @@ function updateViewportScale(): void {
|
||||
viewportScale.value = getViewportScale()
|
||||
}
|
||||
|
||||
function unlockPhone(): void {
|
||||
function finishUnlock(): void {
|
||||
if (!isLocked.value) return
|
||||
isUnlocking.value = true
|
||||
isLocked.value = false
|
||||
passcodeVisible.value = false
|
||||
passcodeError.value = ''
|
||||
|
||||
unlockTimer = window.setTimeout(() => {
|
||||
isUnlocking.value = false
|
||||
}, 720)
|
||||
|
||||
if (pendingUnlockRoute.value) {
|
||||
const routePath = pendingUnlockRoute.value
|
||||
pendingUnlockRoute.value = null
|
||||
window.setTimeout(() => void router.push(routePath), 0)
|
||||
}
|
||||
loadUnlockedPhoneData()
|
||||
}
|
||||
|
||||
function unlockPhone(): void {
|
||||
if (!isLocked.value) return
|
||||
if (phone.security.enabled) {
|
||||
passcodeError.value = ''
|
||||
passcodeVisible.value = true
|
||||
return
|
||||
}
|
||||
finishUnlock()
|
||||
}
|
||||
|
||||
function cancelPasscode(): void {
|
||||
if (passcodeBusy.value) return
|
||||
passcodeVisible.value = false
|
||||
passcodeError.value = ''
|
||||
pendingUnlockRoute.value = null
|
||||
}
|
||||
|
||||
function startPasscodeLock(seconds: number): void {
|
||||
if (passcodeLockTimer !== undefined) window.clearInterval(passcodeLockTimer)
|
||||
passcodeRetrySeconds.value = Math.max(1, Math.ceil(seconds))
|
||||
passcodeLockTimer = window.setInterval(() => {
|
||||
passcodeRetrySeconds.value = Math.max(0, passcodeRetrySeconds.value - 1)
|
||||
if (passcodeRetrySeconds.value === 0 && passcodeLockTimer !== undefined) {
|
||||
window.clearInterval(passcodeLockTimer)
|
||||
passcodeLockTimer = undefined
|
||||
passcodeError.value = ''
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async function submitUnlockPasscode(passcode: string): Promise<void> {
|
||||
if (passcodeBusy.value || passcodeRetrySeconds.value > 0) return
|
||||
passcodeBusy.value = true
|
||||
const response = await phone.unlockWithPasscode(passcode)
|
||||
passcodeBusy.value = false
|
||||
if (response.success) {
|
||||
finishUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
passcodeResetKey.value += 1
|
||||
if (response.error === 'passcode_locked') {
|
||||
startPasscodeLock(response.data?.retryAfter ?? 30)
|
||||
passcodeError.value = phone.t('LockScreen.passcode.locked', {
|
||||
seconds: String(response.data?.retryAfter ?? 30),
|
||||
})
|
||||
return
|
||||
}
|
||||
if (response.error === 'rate_limited') {
|
||||
passcodeError.value = phone.t('LockScreen.passcode.rateLimited')
|
||||
return
|
||||
}
|
||||
passcodeError.value = phone.t('LockScreen.passcode.incorrect')
|
||||
}
|
||||
|
||||
function toggleControlCenter(): void {
|
||||
@@ -486,6 +611,11 @@ function toggleControlCenter(): void {
|
||||
}
|
||||
|
||||
function unlockCamera(): void {
|
||||
if (phone.security.enabled) {
|
||||
pendingUnlockRoute.value = '/apps/camera'
|
||||
unlockPhone()
|
||||
return
|
||||
}
|
||||
unlockPhone()
|
||||
window.setTimeout(() => void router.push('/apps/camera'), 0)
|
||||
}
|
||||
@@ -574,12 +704,33 @@ watch(
|
||||
controlCenterOpened.value = false
|
||||
isLocked.value = false
|
||||
isUnlocking.value = false
|
||||
passcodeVisible.value = false
|
||||
passcodeBusy.value = false
|
||||
passcodeError.value = ''
|
||||
pendingUnlockRoute.value = null
|
||||
unlockedServicesLoaded.value = false
|
||||
if (passcodeLockTimer !== undefined) {
|
||||
window.clearInterval(passcodeLockTimer)
|
||||
passcodeLockTimer = undefined
|
||||
}
|
||||
return
|
||||
}
|
||||
isLocked.value = true
|
||||
unlockedServicesLoaded.value = false
|
||||
controlCenterOpened.value = false
|
||||
weather.start()
|
||||
isUnlocking.value = false
|
||||
passcodeVisible.value = false
|
||||
passcodeBusy.value = false
|
||||
passcodeError.value = ''
|
||||
passcodeResetKey.value += 1
|
||||
passcodeRetrySeconds.value = Math.max(
|
||||
0,
|
||||
(phone.security.lockedUntil ?? 0) - Math.floor(Date.now() / 1000),
|
||||
)
|
||||
if (passcodeRetrySeconds.value > 0) {
|
||||
startPasscodeLock(passcodeRetrySeconds.value)
|
||||
}
|
||||
phone.setLaunchOrigin(null)
|
||||
void router.replace('/')
|
||||
},
|
||||
@@ -596,6 +747,7 @@ onBeforeUnmount(() => {
|
||||
weather.stop()
|
||||
if (clockTicker) clearInterval(clockTicker)
|
||||
if (unlockTimer !== undefined) window.clearTimeout(unlockTimer)
|
||||
if (passcodeLockTimer !== undefined) window.clearInterval(passcodeLockTimer)
|
||||
window.removeEventListener('message', onMessage)
|
||||
window.removeEventListener('keydown', onKeydown)
|
||||
window.removeEventListener('resize', updateViewportScale)
|
||||
@@ -687,6 +839,26 @@ onBeforeUnmount(() => {
|
||||
@unlock="unlockPhone"
|
||||
/>
|
||||
</Transition>
|
||||
<Transition name="lock-screen">
|
||||
<PhonePasscode
|
||||
v-if="isLocked && passcodeVisible"
|
||||
:busy="passcodeBusy"
|
||||
:disabled="passcodeRetrySeconds > 0"
|
||||
:error="passcodeError"
|
||||
:length="phone.security.length ?? 6"
|
||||
:reset-key="passcodeResetKey"
|
||||
:subtitle="
|
||||
passcodeRetrySeconds > 0
|
||||
? phone.t('LockScreen.passcode.tryAgain', {
|
||||
seconds: String(passcodeRetrySeconds),
|
||||
})
|
||||
: phone.t('LockScreen.passcode.unlockSubtitle')
|
||||
"
|
||||
:title="phone.t('LockScreen.passcode.enter')"
|
||||
@cancel="cancelPasscode"
|
||||
@complete="submitUnlockPasscode"
|
||||
/>
|
||||
</Transition>
|
||||
<PhoneNotifications
|
||||
:notification="notifications.current"
|
||||
@close="notifications.dismissCurrent()"
|
||||
|
||||
Reference in New Issue
Block a user