systemSettingsState.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import { type AnimationLevel } from '../../utils/animationLevel'
  2. import { type TrackColorPreset, type TrackDisplayMode, type TrackStyleProfile, type TrackTailLengthPreset } from '../presentation/trackStyleConfig'
  3. import { type GpsMarkerColorPreset, type GpsMarkerSizePreset, type GpsMarkerStyleId } from '../presentation/gpsMarkerStyleConfig'
  4. export type SideButtonPlacement = 'left' | 'right'
  5. export type CenterScaleRulerAnchorMode = 'screen-center' | 'compass-center'
  6. export type UserNorthReferenceMode = 'magnetic' | 'true'
  7. export type CompassTuningProfile = 'smooth' | 'balanced' | 'responsive'
  8. export type SettingLockKey =
  9. | 'lockAnimationLevel'
  10. | 'lockTrackMode'
  11. | 'lockTrackTailLength'
  12. | 'lockTrackColor'
  13. | 'lockTrackStyle'
  14. | 'lockGpsMarkerVisible'
  15. | 'lockGpsMarkerStyle'
  16. | 'lockGpsMarkerSize'
  17. | 'lockGpsMarkerColor'
  18. | 'lockSideButtonPlacement'
  19. | 'lockAutoRotate'
  20. | 'lockCompassTuning'
  21. | 'lockScaleRulerVisible'
  22. | 'lockScaleRulerAnchor'
  23. | 'lockNorthReference'
  24. | 'lockHeartRateDevice'
  25. export type StoredUserSettings = {
  26. animationLevel?: AnimationLevel
  27. trackDisplayMode?: TrackDisplayMode
  28. trackTailLength?: TrackTailLengthPreset
  29. trackColorPreset?: TrackColorPreset
  30. trackStyleProfile?: TrackStyleProfile
  31. gpsMarkerVisible?: boolean
  32. gpsMarkerStyle?: GpsMarkerStyleId
  33. gpsMarkerSize?: GpsMarkerSizePreset
  34. gpsMarkerColorPreset?: GpsMarkerColorPreset
  35. autoRotateEnabled?: boolean
  36. compassTuningProfile?: CompassTuningProfile
  37. northReferenceMode?: UserNorthReferenceMode
  38. sideButtonPlacement?: SideButtonPlacement
  39. showCenterScaleRuler?: boolean
  40. centerScaleRulerAnchorMode?: CenterScaleRulerAnchorMode
  41. }
  42. export interface SystemSettingsConfig {
  43. values: Partial<StoredUserSettings>
  44. locks: Partial<Record<SettingLockKey, boolean>>
  45. }
  46. export type ResolvedSystemSettingsState = {
  47. values: Required<StoredUserSettings>
  48. locks: Record<SettingLockKey, boolean>
  49. }
  50. export const USER_SETTINGS_STORAGE_KEY = 'cmr_user_settings_v1'
  51. export const DEFAULT_STORED_USER_SETTINGS: Required<StoredUserSettings> = {
  52. animationLevel: 'standard',
  53. trackDisplayMode: 'full',
  54. trackTailLength: 'medium',
  55. trackColorPreset: 'mint',
  56. trackStyleProfile: 'neon',
  57. gpsMarkerVisible: true,
  58. gpsMarkerStyle: 'beacon',
  59. gpsMarkerSize: 'medium',
  60. gpsMarkerColorPreset: 'cyan',
  61. autoRotateEnabled: true,
  62. compassTuningProfile: 'balanced',
  63. northReferenceMode: 'magnetic',
  64. sideButtonPlacement: 'left',
  65. showCenterScaleRuler: false,
  66. centerScaleRulerAnchorMode: 'screen-center',
  67. }
  68. export const DEFAULT_SETTING_LOCKS: Record<SettingLockKey, boolean> = {
  69. lockAnimationLevel: false,
  70. lockTrackMode: false,
  71. lockTrackTailLength: false,
  72. lockTrackColor: false,
  73. lockTrackStyle: false,
  74. lockGpsMarkerVisible: false,
  75. lockGpsMarkerStyle: false,
  76. lockGpsMarkerSize: false,
  77. lockGpsMarkerColor: false,
  78. lockSideButtonPlacement: false,
  79. lockAutoRotate: false,
  80. lockCompassTuning: false,
  81. lockScaleRulerVisible: false,
  82. lockScaleRulerAnchor: false,
  83. lockNorthReference: false,
  84. lockHeartRateDevice: false,
  85. }
  86. export const SETTING_LOCK_VALUE_MAP: Record<SettingLockKey, keyof StoredUserSettings | null> = {
  87. lockAnimationLevel: 'animationLevel',
  88. lockTrackMode: 'trackDisplayMode',
  89. lockTrackTailLength: 'trackTailLength',
  90. lockTrackColor: 'trackColorPreset',
  91. lockTrackStyle: 'trackStyleProfile',
  92. lockGpsMarkerVisible: 'gpsMarkerVisible',
  93. lockGpsMarkerStyle: 'gpsMarkerStyle',
  94. lockGpsMarkerSize: 'gpsMarkerSize',
  95. lockGpsMarkerColor: 'gpsMarkerColorPreset',
  96. lockSideButtonPlacement: 'sideButtonPlacement',
  97. lockAutoRotate: 'autoRotateEnabled',
  98. lockCompassTuning: 'compassTuningProfile',
  99. lockScaleRulerVisible: 'showCenterScaleRuler',
  100. lockScaleRulerAnchor: 'centerScaleRulerAnchorMode',
  101. lockNorthReference: 'northReferenceMode',
  102. lockHeartRateDevice: null,
  103. }
  104. function normalizeStoredUserSettings(raw: unknown): StoredUserSettings {
  105. if (!raw || typeof raw !== 'object') {
  106. return {}
  107. }
  108. const normalized = raw as Record<string, unknown>
  109. const settings: StoredUserSettings = {}
  110. if (normalized.animationLevel === 'standard' || normalized.animationLevel === 'lite') {
  111. settings.animationLevel = normalized.animationLevel
  112. }
  113. if (normalized.trackDisplayMode === 'none' || normalized.trackDisplayMode === 'full' || normalized.trackDisplayMode === 'tail') {
  114. settings.trackDisplayMode = normalized.trackDisplayMode
  115. }
  116. if (normalized.trackTailLength === 'short' || normalized.trackTailLength === 'medium' || normalized.trackTailLength === 'long') {
  117. settings.trackTailLength = normalized.trackTailLength
  118. }
  119. if (normalized.trackStyleProfile === 'classic' || normalized.trackStyleProfile === 'neon') {
  120. settings.trackStyleProfile = normalized.trackStyleProfile
  121. }
  122. if (typeof normalized.gpsMarkerVisible === 'boolean') {
  123. settings.gpsMarkerVisible = normalized.gpsMarkerVisible
  124. }
  125. if (
  126. normalized.gpsMarkerStyle === 'dot'
  127. || normalized.gpsMarkerStyle === 'beacon'
  128. || normalized.gpsMarkerStyle === 'disc'
  129. || normalized.gpsMarkerStyle === 'badge'
  130. ) {
  131. settings.gpsMarkerStyle = normalized.gpsMarkerStyle
  132. }
  133. if (normalized.gpsMarkerSize === 'small' || normalized.gpsMarkerSize === 'medium' || normalized.gpsMarkerSize === 'large') {
  134. settings.gpsMarkerSize = normalized.gpsMarkerSize
  135. }
  136. if (
  137. normalized.gpsMarkerColorPreset === 'mint'
  138. || normalized.gpsMarkerColorPreset === 'cyan'
  139. || normalized.gpsMarkerColorPreset === 'sky'
  140. || normalized.gpsMarkerColorPreset === 'blue'
  141. || normalized.gpsMarkerColorPreset === 'violet'
  142. || normalized.gpsMarkerColorPreset === 'pink'
  143. || normalized.gpsMarkerColorPreset === 'orange'
  144. || normalized.gpsMarkerColorPreset === 'yellow'
  145. ) {
  146. settings.gpsMarkerColorPreset = normalized.gpsMarkerColorPreset
  147. }
  148. if (
  149. normalized.trackColorPreset === 'mint'
  150. || normalized.trackColorPreset === 'cyan'
  151. || normalized.trackColorPreset === 'sky'
  152. || normalized.trackColorPreset === 'blue'
  153. || normalized.trackColorPreset === 'violet'
  154. || normalized.trackColorPreset === 'pink'
  155. || normalized.trackColorPreset === 'orange'
  156. || normalized.trackColorPreset === 'yellow'
  157. ) {
  158. settings.trackColorPreset = normalized.trackColorPreset
  159. }
  160. if (normalized.northReferenceMode === 'magnetic' || normalized.northReferenceMode === 'true') {
  161. settings.northReferenceMode = normalized.northReferenceMode
  162. }
  163. if (typeof normalized.autoRotateEnabled === 'boolean') {
  164. settings.autoRotateEnabled = normalized.autoRotateEnabled
  165. }
  166. if (normalized.compassTuningProfile === 'smooth' || normalized.compassTuningProfile === 'balanced' || normalized.compassTuningProfile === 'responsive') {
  167. settings.compassTuningProfile = normalized.compassTuningProfile
  168. }
  169. if (normalized.sideButtonPlacement === 'left' || normalized.sideButtonPlacement === 'right') {
  170. settings.sideButtonPlacement = normalized.sideButtonPlacement
  171. }
  172. if (typeof normalized.showCenterScaleRuler === 'boolean') {
  173. settings.showCenterScaleRuler = normalized.showCenterScaleRuler
  174. }
  175. if (normalized.centerScaleRulerAnchorMode === 'screen-center' || normalized.centerScaleRulerAnchorMode === 'compass-center') {
  176. settings.centerScaleRulerAnchorMode = normalized.centerScaleRulerAnchorMode
  177. }
  178. return settings
  179. }
  180. export function loadStoredUserSettings(storageKey = USER_SETTINGS_STORAGE_KEY): StoredUserSettings {
  181. try {
  182. return normalizeStoredUserSettings(wx.getStorageSync(storageKey))
  183. } catch {
  184. return {}
  185. }
  186. }
  187. export function persistStoredUserSettings(
  188. settings: StoredUserSettings,
  189. storageKey = USER_SETTINGS_STORAGE_KEY,
  190. ): void {
  191. try {
  192. wx.setStorageSync(storageKey, settings)
  193. } catch {}
  194. }
  195. export function mergeStoredUserSettings(
  196. current: StoredUserSettings,
  197. patch: Partial<StoredUserSettings>,
  198. ): StoredUserSettings {
  199. return {
  200. ...current,
  201. ...patch,
  202. }
  203. }
  204. export function buildInitialSystemSettingsState(
  205. stored: StoredUserSettings,
  206. config?: Partial<SystemSettingsConfig>,
  207. ): ResolvedSystemSettingsState {
  208. const values = {
  209. ...DEFAULT_STORED_USER_SETTINGS,
  210. ...(config && config.values ? config.values : {}),
  211. }
  212. const locks = {
  213. ...DEFAULT_SETTING_LOCKS,
  214. ...(config && config.locks ? config.locks : {}),
  215. }
  216. const resolvedValues: Required<StoredUserSettings> = {
  217. ...values,
  218. }
  219. for (const [lockKey, isLocked] of Object.entries(locks) as Array<[SettingLockKey, boolean]>) {
  220. const valueKey = SETTING_LOCK_VALUE_MAP[lockKey]
  221. if (!valueKey) {
  222. continue
  223. }
  224. if (!isLocked && stored[valueKey] !== undefined) {
  225. ;(resolvedValues as Record<string, unknown>)[valueKey] = stored[valueKey]
  226. }
  227. }
  228. for (const [key, value] of Object.entries(stored) as Array<[keyof StoredUserSettings, StoredUserSettings[keyof StoredUserSettings]]>) {
  229. const matchingLockKey = (Object.keys(SETTING_LOCK_VALUE_MAP) as SettingLockKey[])
  230. .find((lockKey) => SETTING_LOCK_VALUE_MAP[lockKey] === key)
  231. if (matchingLockKey && locks[matchingLockKey]) {
  232. continue
  233. }
  234. if (value !== undefined) {
  235. ;(resolvedValues as Record<string, unknown>)[key] = value
  236. }
  237. }
  238. return {
  239. values: resolvedValues,
  240. locks,
  241. }
  242. }
  243. export function buildRuntimeSettingLocks(
  244. locks: Partial<Record<SettingLockKey, boolean>> | undefined,
  245. runtimeActive: boolean,
  246. ): Partial<Record<SettingLockKey, boolean>> {
  247. const sourceLocks = locks || {}
  248. if (runtimeActive) {
  249. return { ...sourceLocks }
  250. }
  251. const unlocked: Partial<Record<SettingLockKey, boolean>> = {}
  252. for (const key of Object.keys(sourceLocks) as SettingLockKey[]) {
  253. unlocked[key] = false
  254. }
  255. return unlocked
  256. }
  257. export function resolveSystemSettingsState(
  258. config?: Partial<SystemSettingsConfig>,
  259. storageKey = USER_SETTINGS_STORAGE_KEY,
  260. runtimeActive = false,
  261. ): ResolvedSystemSettingsState {
  262. return buildInitialSystemSettingsState(
  263. loadStoredUserSettings(storageKey),
  264. {
  265. values: config && config.values ? config.values : {},
  266. locks: buildRuntimeSettingLocks(config && config.locks ? config.locks : {}, runtimeActive),
  267. },
  268. )
  269. }