mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-31 10:18:57 +00:00
42ac955052
Add the Companies app, resilient call handling, custom app registry, vendor compatibility, storage policies, frontend bridge, tests, documentation, config, locale, and SQL migrations.
289 lines
8.7 KiB
TypeScript
289 lines
8.7 KiB
TypeScript
import type { LaunchablePhoneAppId } from '@/types/apps'
|
|
import { cloneJsonData } from '@/utils/clone'
|
|
|
|
export const APPEARANCE_MODE_IDS = ['automatic', 'light', 'dark'] as const
|
|
export const GRAPHICS_MODE_IDS = ['performance', 'ultimate'] as const
|
|
export const PHONE_FRAME_IDS = [
|
|
'black',
|
|
'blue',
|
|
'green',
|
|
'lavender',
|
|
'red',
|
|
'white',
|
|
'orange',
|
|
'yellow',
|
|
'lime',
|
|
'teal',
|
|
'cyan',
|
|
'purple',
|
|
'pink',
|
|
'gold',
|
|
'rgb',
|
|
] as const
|
|
export const RINGTONE_IDS = ['skyline', 'horizon', 'pulse'] as const
|
|
export const NOTIFICATION_SOUND_IDS = ['chime', 'signal', 'soft'] as const
|
|
export const WALLPAPER_IDS = ['midnight', 'aurora', 'ember'] as const
|
|
export const PHONE_SCALE_MIN = 50
|
|
export const PHONE_SCALE_MAX = 150
|
|
export const PHONE_SCALE_STEP = 5
|
|
|
|
export type AppearanceMode = (typeof APPEARANCE_MODE_IDS)[number]
|
|
export type GraphicsMode = (typeof GRAPHICS_MODE_IDS)[number]
|
|
export type PhoneFrameId = (typeof PHONE_FRAME_IDS)[number]
|
|
export type RingtoneId = (typeof RINGTONE_IDS)[number]
|
|
export type NotificationSoundId = (typeof NOTIFICATION_SOUND_IDS)[number]
|
|
export type WallpaperId = (typeof WALLPAPER_IDS)[number]
|
|
export type AppNotificationPreferences = {
|
|
enabled: boolean
|
|
sounds: boolean
|
|
}
|
|
|
|
export const DEFAULT_APP_NOTIFICATION_PREFERENCES: AppNotificationPreferences =
|
|
{
|
|
enabled: true,
|
|
sounds: true,
|
|
}
|
|
|
|
export type PhonePreferencesV1 = {
|
|
settings: {
|
|
airplaneMode: boolean
|
|
appearanceMode: AppearanceMode
|
|
bluetoothEnabled: boolean
|
|
cellularEnabled: boolean
|
|
focusMode: boolean
|
|
frame: PhoneFrameId
|
|
graphicsMode: GraphicsMode
|
|
notificationSound: NotificationSoundId
|
|
notificationDurationSeconds: number
|
|
notificationVolume: number
|
|
notifications: Record<LaunchablePhoneAppId, AppNotificationPreferences>
|
|
phoneScale: number
|
|
ringtone: RingtoneId
|
|
ringtoneVolume: number
|
|
screenBrightness: number
|
|
streamerMode: boolean
|
|
wallpaper: WallpaperId
|
|
wifiEnabled: boolean
|
|
}
|
|
version: 1
|
|
}
|
|
|
|
const DEFAULT_APP_NOTIFICATIONS: Record<
|
|
LaunchablePhoneAppId,
|
|
AppNotificationPreferences
|
|
> = {
|
|
phone: { enabled: true, sounds: true },
|
|
messages: { enabled: true, sounds: true },
|
|
darkchat: { enabled: true, sounds: true },
|
|
flare: { enabled: true, sounds: true },
|
|
'app-store': { enabled: true, sounds: true },
|
|
calculator: { enabled: true, sounds: true },
|
|
snake: { enabled: true, sounds: true },
|
|
memory: { enabled: true, sounds: true },
|
|
'number-merge': { enabled: true, sounds: true },
|
|
minesweeper: { enabled: true, sounds: true },
|
|
'tower-stack': { enabled: true, sounds: true },
|
|
'sky-flappy': { enabled: true, sounds: true },
|
|
'neon-drop': { enabled: true, sounds: true },
|
|
citymarkt: { enabled: true, sounds: true },
|
|
companies: { enabled: true, sounds: true },
|
|
'local-pages': { enabled: true, sounds: true },
|
|
picstagram: { enabled: true, sounds: true },
|
|
fliptok: { enabled: true, sounds: true },
|
|
feather: { enabled: true, sounds: true },
|
|
crewlink: { enabled: true, sounds: true },
|
|
camera: { enabled: true, sounds: true },
|
|
clock: { enabled: true, sounds: true },
|
|
calendar: { enabled: true, sounds: true },
|
|
weather: { enabled: true, sounds: true },
|
|
banking: { enabled: true, sounds: true },
|
|
billing: { enabled: true, sounds: true },
|
|
garage: { enabled: true, sounds: true },
|
|
skyride: { enabled: true, sounds: true },
|
|
house: { enabled: true, sounds: true },
|
|
mail: { enabled: true, sounds: true },
|
|
map: { enabled: true, sounds: true },
|
|
notes: { enabled: true, sounds: true },
|
|
radio: { enabled: true, sounds: true },
|
|
music: { enabled: true, sounds: true },
|
|
photos: { enabled: true, sounds: true },
|
|
settings: { enabled: true, sounds: true },
|
|
}
|
|
|
|
export const DEFAULT_PHONE_PREFERENCES: PhonePreferencesV1 = {
|
|
settings: {
|
|
airplaneMode: false,
|
|
appearanceMode: 'automatic',
|
|
bluetoothEnabled: true,
|
|
cellularEnabled: true,
|
|
focusMode: false,
|
|
frame: 'black',
|
|
graphicsMode: 'performance',
|
|
notificationSound: 'chime',
|
|
notificationDurationSeconds: 10,
|
|
notificationVolume: 70,
|
|
notifications: DEFAULT_APP_NOTIFICATIONS,
|
|
phoneScale: 100,
|
|
ringtone: 'skyline',
|
|
ringtoneVolume: 80,
|
|
screenBrightness: 100,
|
|
streamerMode: false,
|
|
wallpaper: 'midnight',
|
|
wifiEnabled: true,
|
|
},
|
|
version: 1,
|
|
}
|
|
|
|
function readBoolean(value: unknown, fallback: boolean): boolean {
|
|
return typeof value === 'boolean' ? value : fallback
|
|
}
|
|
|
|
function readNumber(
|
|
value: unknown,
|
|
fallback: number,
|
|
minimum: number,
|
|
maximum: number,
|
|
): number {
|
|
return typeof value === 'number' && Number.isFinite(value)
|
|
? Math.min(maximum, Math.max(minimum, value))
|
|
: fallback
|
|
}
|
|
|
|
function readChoice<T extends string>(
|
|
value: unknown,
|
|
choices: readonly T[],
|
|
fallback: T,
|
|
): T {
|
|
return typeof value === 'string' && choices.includes(value as T)
|
|
? (value as T)
|
|
: fallback
|
|
}
|
|
|
|
function readNotifications(
|
|
value: unknown,
|
|
): Record<LaunchablePhoneAppId, AppNotificationPreferences> {
|
|
const source: Record<string, unknown> =
|
|
value && typeof value === 'object' ? (value as Record<string, unknown>) : {}
|
|
const notifications = cloneJsonData(DEFAULT_APP_NOTIFICATIONS)
|
|
|
|
for (const appId of Object.keys(source)) {
|
|
if (!/^[a-z0-9][a-z0-9._-]{1,63}$/.test(appId)) continue
|
|
const rawPreferences =
|
|
source[appId] &&
|
|
typeof source[appId] === 'object' &&
|
|
!Array.isArray(source[appId])
|
|
? (source[appId] as Partial<AppNotificationPreferences>)
|
|
: {}
|
|
const defaults =
|
|
DEFAULT_APP_NOTIFICATIONS[appId as LaunchablePhoneAppId] ??
|
|
DEFAULT_APP_NOTIFICATION_PREFERENCES
|
|
notifications[appId as LaunchablePhoneAppId] = {
|
|
enabled: readBoolean(rawPreferences.enabled, defaults.enabled),
|
|
sounds: readBoolean(rawPreferences.sounds, defaults.sounds),
|
|
}
|
|
}
|
|
|
|
return notifications
|
|
}
|
|
|
|
export function ensureAppNotificationPreferences(
|
|
preferences: PhonePreferencesV1,
|
|
appIds: LaunchablePhoneAppId[],
|
|
): void {
|
|
for (const appId of appIds) {
|
|
preferences.settings.notifications[appId] ??= cloneJsonData(
|
|
DEFAULT_APP_NOTIFICATION_PREFERENCES,
|
|
)
|
|
}
|
|
}
|
|
|
|
export function parsePhonePreferences(raw: string | null): PhonePreferencesV1 {
|
|
if (!raw) return cloneJsonData(DEFAULT_PHONE_PREFERENCES)
|
|
|
|
try {
|
|
const parsed = JSON.parse(raw) as Partial<PhonePreferencesV1>
|
|
const settings = parsed.settings
|
|
if (parsed.version !== 1 || !settings || typeof settings !== 'object') {
|
|
return cloneJsonData(DEFAULT_PHONE_PREFERENCES)
|
|
}
|
|
|
|
const defaults = DEFAULT_PHONE_PREFERENCES.settings
|
|
return {
|
|
settings: {
|
|
airplaneMode: readBoolean(settings.airplaneMode, defaults.airplaneMode),
|
|
appearanceMode: readChoice(
|
|
settings.appearanceMode,
|
|
APPEARANCE_MODE_IDS,
|
|
defaults.appearanceMode,
|
|
),
|
|
bluetoothEnabled: readBoolean(
|
|
settings.bluetoothEnabled,
|
|
defaults.bluetoothEnabled,
|
|
),
|
|
cellularEnabled: readBoolean(
|
|
settings.cellularEnabled,
|
|
defaults.cellularEnabled,
|
|
),
|
|
focusMode: readBoolean(settings.focusMode, defaults.focusMode),
|
|
frame: readChoice(settings.frame, PHONE_FRAME_IDS, defaults.frame),
|
|
graphicsMode: readChoice(
|
|
settings.graphicsMode,
|
|
GRAPHICS_MODE_IDS,
|
|
defaults.graphicsMode,
|
|
),
|
|
notificationSound: readChoice(
|
|
settings.notificationSound,
|
|
NOTIFICATION_SOUND_IDS,
|
|
defaults.notificationSound,
|
|
),
|
|
notificationDurationSeconds: readNumber(
|
|
settings.notificationDurationSeconds,
|
|
defaults.notificationDurationSeconds,
|
|
3,
|
|
30,
|
|
),
|
|
notificationVolume: readNumber(
|
|
settings.notificationVolume,
|
|
defaults.notificationVolume,
|
|
0,
|
|
100,
|
|
),
|
|
notifications: readNotifications(settings.notifications),
|
|
phoneScale: readNumber(
|
|
settings.phoneScale,
|
|
defaults.phoneScale,
|
|
PHONE_SCALE_MIN,
|
|
PHONE_SCALE_MAX,
|
|
),
|
|
ringtone: readChoice(
|
|
settings.ringtone,
|
|
RINGTONE_IDS,
|
|
defaults.ringtone,
|
|
),
|
|
ringtoneVolume: readNumber(
|
|
settings.ringtoneVolume,
|
|
defaults.ringtoneVolume,
|
|
0,
|
|
100,
|
|
),
|
|
screenBrightness: readNumber(
|
|
settings.screenBrightness,
|
|
defaults.screenBrightness,
|
|
10,
|
|
100,
|
|
),
|
|
streamerMode: readBoolean(settings.streamerMode, defaults.streamerMode),
|
|
wallpaper: readChoice(
|
|
settings.wallpaper,
|
|
WALLPAPER_IDS,
|
|
defaults.wallpaper,
|
|
),
|
|
wifiEnabled: readBoolean(settings.wifiEnabled, defaults.wifiEnabled),
|
|
},
|
|
version: 1,
|
|
}
|
|
} catch {
|
|
return cloneJsonData(DEFAULT_PHONE_PREFERENCES)
|
|
}
|
|
}
|