import { type AnimationLevel } from '../../utils/animationLevel' import { type TrackColorPreset, type TrackDisplayMode, type TrackStyleProfile, type TrackTailLengthPreset } from '../presentation/trackStyleConfig' import { type GpsMarkerColorPreset, type GpsMarkerSizePreset, type GpsMarkerStyleId } from '../presentation/gpsMarkerStyleConfig' export type SideButtonPlacement = 'left' | 'right' export type CenterScaleRulerAnchorMode = 'screen-center' | 'compass-center' export type UserNorthReferenceMode = 'magnetic' | 'true' export type CompassTuningProfile = 'smooth' | 'balanced' | 'responsive' export type SettingLockKey = | 'lockAnimationLevel' | 'lockTrackMode' | 'lockTrackTailLength' | 'lockTrackColor' | 'lockTrackStyle' | 'lockGpsMarkerVisible' | 'lockGpsMarkerStyle' | 'lockGpsMarkerSize' | 'lockGpsMarkerColor' | 'lockSideButtonPlacement' | 'lockAutoRotate' | 'lockCompassTuning' | 'lockScaleRulerVisible' | 'lockScaleRulerAnchor' | 'lockNorthReference' | 'lockHeartRateDevice' export type StoredUserSettings = { animationLevel?: AnimationLevel trackDisplayMode?: TrackDisplayMode trackTailLength?: TrackTailLengthPreset trackColorPreset?: TrackColorPreset trackStyleProfile?: TrackStyleProfile gpsMarkerVisible?: boolean gpsMarkerStyle?: GpsMarkerStyleId gpsMarkerSize?: GpsMarkerSizePreset gpsMarkerColorPreset?: GpsMarkerColorPreset autoRotateEnabled?: boolean compassTuningProfile?: CompassTuningProfile northReferenceMode?: UserNorthReferenceMode sideButtonPlacement?: SideButtonPlacement showCenterScaleRuler?: boolean centerScaleRulerAnchorMode?: CenterScaleRulerAnchorMode } export interface SystemSettingsConfig { values: Partial locks: Partial> } export type ResolvedSystemSettingsState = { values: Required locks: Record } export const USER_SETTINGS_STORAGE_KEY = 'cmr_user_settings_v1' export const DEFAULT_STORED_USER_SETTINGS: Required = { animationLevel: 'standard', trackDisplayMode: 'full', trackTailLength: 'medium', trackColorPreset: 'mint', trackStyleProfile: 'neon', gpsMarkerVisible: true, gpsMarkerStyle: 'beacon', gpsMarkerSize: 'medium', gpsMarkerColorPreset: 'cyan', autoRotateEnabled: true, compassTuningProfile: 'balanced', northReferenceMode: 'magnetic', sideButtonPlacement: 'left', showCenterScaleRuler: false, centerScaleRulerAnchorMode: 'screen-center', } export const DEFAULT_SETTING_LOCKS: Record = { lockAnimationLevel: false, lockTrackMode: false, lockTrackTailLength: false, lockTrackColor: false, lockTrackStyle: false, lockGpsMarkerVisible: false, lockGpsMarkerStyle: false, lockGpsMarkerSize: false, lockGpsMarkerColor: false, lockSideButtonPlacement: false, lockAutoRotate: false, lockCompassTuning: false, lockScaleRulerVisible: false, lockScaleRulerAnchor: false, lockNorthReference: false, lockHeartRateDevice: false, } export const SETTING_LOCK_VALUE_MAP: Record = { lockAnimationLevel: 'animationLevel', lockTrackMode: 'trackDisplayMode', lockTrackTailLength: 'trackTailLength', lockTrackColor: 'trackColorPreset', lockTrackStyle: 'trackStyleProfile', lockGpsMarkerVisible: 'gpsMarkerVisible', lockGpsMarkerStyle: 'gpsMarkerStyle', lockGpsMarkerSize: 'gpsMarkerSize', lockGpsMarkerColor: 'gpsMarkerColorPreset', lockSideButtonPlacement: 'sideButtonPlacement', lockAutoRotate: 'autoRotateEnabled', lockCompassTuning: 'compassTuningProfile', lockScaleRulerVisible: 'showCenterScaleRuler', lockScaleRulerAnchor: 'centerScaleRulerAnchorMode', lockNorthReference: 'northReferenceMode', lockHeartRateDevice: null, } function normalizeStoredUserSettings(raw: unknown): StoredUserSettings { if (!raw || typeof raw !== 'object') { return {} } const normalized = raw as Record const settings: StoredUserSettings = {} if (normalized.animationLevel === 'standard' || normalized.animationLevel === 'lite') { settings.animationLevel = normalized.animationLevel } if (normalized.trackDisplayMode === 'none' || normalized.trackDisplayMode === 'full' || normalized.trackDisplayMode === 'tail') { settings.trackDisplayMode = normalized.trackDisplayMode } if (normalized.trackTailLength === 'short' || normalized.trackTailLength === 'medium' || normalized.trackTailLength === 'long') { settings.trackTailLength = normalized.trackTailLength } if (normalized.trackStyleProfile === 'classic' || normalized.trackStyleProfile === 'neon') { settings.trackStyleProfile = normalized.trackStyleProfile } if (typeof normalized.gpsMarkerVisible === 'boolean') { settings.gpsMarkerVisible = normalized.gpsMarkerVisible } if ( normalized.gpsMarkerStyle === 'dot' || normalized.gpsMarkerStyle === 'beacon' || normalized.gpsMarkerStyle === 'disc' || normalized.gpsMarkerStyle === 'badge' ) { settings.gpsMarkerStyle = normalized.gpsMarkerStyle } if (normalized.gpsMarkerSize === 'small' || normalized.gpsMarkerSize === 'medium' || normalized.gpsMarkerSize === 'large') { settings.gpsMarkerSize = normalized.gpsMarkerSize } if ( normalized.gpsMarkerColorPreset === 'mint' || normalized.gpsMarkerColorPreset === 'cyan' || normalized.gpsMarkerColorPreset === 'sky' || normalized.gpsMarkerColorPreset === 'blue' || normalized.gpsMarkerColorPreset === 'violet' || normalized.gpsMarkerColorPreset === 'pink' || normalized.gpsMarkerColorPreset === 'orange' || normalized.gpsMarkerColorPreset === 'yellow' ) { settings.gpsMarkerColorPreset = normalized.gpsMarkerColorPreset } if ( normalized.trackColorPreset === 'mint' || normalized.trackColorPreset === 'cyan' || normalized.trackColorPreset === 'sky' || normalized.trackColorPreset === 'blue' || normalized.trackColorPreset === 'violet' || normalized.trackColorPreset === 'pink' || normalized.trackColorPreset === 'orange' || normalized.trackColorPreset === 'yellow' ) { settings.trackColorPreset = normalized.trackColorPreset } if (normalized.northReferenceMode === 'magnetic' || normalized.northReferenceMode === 'true') { settings.northReferenceMode = normalized.northReferenceMode } if (typeof normalized.autoRotateEnabled === 'boolean') { settings.autoRotateEnabled = normalized.autoRotateEnabled } if (normalized.compassTuningProfile === 'smooth' || normalized.compassTuningProfile === 'balanced' || normalized.compassTuningProfile === 'responsive') { settings.compassTuningProfile = normalized.compassTuningProfile } if (normalized.sideButtonPlacement === 'left' || normalized.sideButtonPlacement === 'right') { settings.sideButtonPlacement = normalized.sideButtonPlacement } if (typeof normalized.showCenterScaleRuler === 'boolean') { settings.showCenterScaleRuler = normalized.showCenterScaleRuler } if (normalized.centerScaleRulerAnchorMode === 'screen-center' || normalized.centerScaleRulerAnchorMode === 'compass-center') { settings.centerScaleRulerAnchorMode = normalized.centerScaleRulerAnchorMode } return settings } export function loadStoredUserSettings(storageKey = USER_SETTINGS_STORAGE_KEY): StoredUserSettings { try { return normalizeStoredUserSettings(wx.getStorageSync(storageKey)) } catch { return {} } } export function persistStoredUserSettings( settings: StoredUserSettings, storageKey = USER_SETTINGS_STORAGE_KEY, ): void { try { wx.setStorageSync(storageKey, settings) } catch {} } export function mergeStoredUserSettings( current: StoredUserSettings, patch: Partial, ): StoredUserSettings { return { ...current, ...patch, } } export function buildInitialSystemSettingsState( stored: StoredUserSettings, config?: Partial, ): ResolvedSystemSettingsState { const values = { ...DEFAULT_STORED_USER_SETTINGS, ...(config && config.values ? config.values : {}), } const locks = { ...DEFAULT_SETTING_LOCKS, ...(config && config.locks ? config.locks : {}), } const resolvedValues: Required = { ...values, } for (const [lockKey, isLocked] of Object.entries(locks) as Array<[SettingLockKey, boolean]>) { const valueKey = SETTING_LOCK_VALUE_MAP[lockKey] if (!valueKey) { continue } if (!isLocked && stored[valueKey] !== undefined) { ;(resolvedValues as Record)[valueKey] = stored[valueKey] } } for (const [key, value] of Object.entries(stored) as Array<[keyof StoredUserSettings, StoredUserSettings[keyof StoredUserSettings]]>) { const matchingLockKey = (Object.keys(SETTING_LOCK_VALUE_MAP) as SettingLockKey[]) .find((lockKey) => SETTING_LOCK_VALUE_MAP[lockKey] === key) if (matchingLockKey && locks[matchingLockKey]) { continue } if (value !== undefined) { ;(resolvedValues as Record)[key] = value } } return { values: resolvedValues, locks, } } export function buildRuntimeSettingLocks( locks: Partial> | undefined, runtimeActive: boolean, ): Partial> { const sourceLocks = locks || {} if (runtimeActive) { return { ...sourceLocks } } const unlocked: Partial> = {} for (const key of Object.keys(sourceLocks) as SettingLockKey[]) { unlocked[key] = false } return unlocked } export function resolveSystemSettingsState( config?: Partial, storageKey = USER_SETTINGS_STORAGE_KEY, runtimeActive = false, ): ResolvedSystemSettingsState { return buildInitialSystemSettingsState( loadStoredUserSettings(storageKey), { values: config && config.values ? config.values : {}, locks: buildRuntimeSettingLocks(config && config.locks ? config.locks : {}, runtimeActive), }, ) }