|
|
@@ -71,6 +71,7 @@ type MapPageData = MapEngineViewState & {
|
|
|
showGameInfoPanel: boolean
|
|
|
showResultScene: boolean
|
|
|
showSystemSettingsPanel: boolean
|
|
|
+ showHeartRateDevicePicker: boolean
|
|
|
showCenterScaleRuler: boolean
|
|
|
showPunchHintBanner: boolean
|
|
|
punchHintFxClass: string
|
|
|
@@ -92,6 +93,7 @@ type MapPageData = MapEngineViewState & {
|
|
|
resultSceneHeroLabel: string
|
|
|
resultSceneHeroValue: string
|
|
|
resultSceneRows: MapEngineGameInfoRow[]
|
|
|
+ resultSceneCountdownText: string
|
|
|
panelTimerText: string
|
|
|
panelTimerMode: 'elapsed' | 'countdown'
|
|
|
panelMileageText: string
|
|
|
@@ -157,6 +159,7 @@ const PUNCH_HINT_AUTO_HIDE_MS = 30000
|
|
|
const PUNCH_HINT_FX_DURATION_MS = 420
|
|
|
const PUNCH_HINT_HAPTIC_GAP_MS = 2400
|
|
|
const SESSION_RECOVERY_PERSIST_INTERVAL_MS = 5000
|
|
|
+const RESULT_EXIT_REDIRECT_DELAY_MS = 3000
|
|
|
let currentGameLaunchEnvelope: GameLaunchEnvelope = getDemoGameLaunchEnvelope()
|
|
|
let mapEngine: MapEngine | null = null
|
|
|
let stageCanvasAttached = false
|
|
|
@@ -172,6 +175,8 @@ let panelMileageFxTimer = 0
|
|
|
let panelSpeedFxTimer = 0
|
|
|
let panelHeartRateFxTimer = 0
|
|
|
let sessionRecoveryPersistTimer = 0
|
|
|
+let resultExitRedirectTimer = 0
|
|
|
+let resultExitCountdownTimer = 0
|
|
|
let lastPunchHintHapticAt = 0
|
|
|
let currentSystemSettingsConfig: SystemSettingsConfig | undefined
|
|
|
let currentRemoteMapConfig: RemoteMapConfig | undefined
|
|
|
@@ -179,6 +184,8 @@ let systemSettingsLockLifetimeActive = false
|
|
|
let syncedBackendSessionStartId = ''
|
|
|
let syncedBackendSessionFinishId = ''
|
|
|
let shouldAutoRestoreRecoverySnapshot = false
|
|
|
+let redirectedToResultPage = false
|
|
|
+let pendingHeartRateSwitchDeviceName: string | null = null
|
|
|
const DEBUG_MOCK_CHANNEL_ID_STORAGE_KEY = 'cmr.debug.mockChannelId.v1'
|
|
|
const DEBUG_MOCK_AUTO_CONNECT_STORAGE_KEY = 'cmr.debug.autoConnectMockSources.v1'
|
|
|
let lastCenterScaleRulerStablePatch: Pick<
|
|
|
@@ -469,6 +476,34 @@ function clearSessionRecoveryPersistTimer() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+function clearResultExitRedirectTimer() {
|
|
|
+ if (resultExitRedirectTimer) {
|
|
|
+ clearTimeout(resultExitRedirectTimer)
|
|
|
+ resultExitRedirectTimer = 0
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function clearResultExitCountdownTimer() {
|
|
|
+ if (resultExitCountdownTimer) {
|
|
|
+ clearInterval(resultExitCountdownTimer)
|
|
|
+ resultExitCountdownTimer = 0
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function navigateAwayFromMapAfterCancel() {
|
|
|
+ const pages = getCurrentPages()
|
|
|
+ if (pages.length > 1) {
|
|
|
+ wx.navigateBack({
|
|
|
+ delta: 1,
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ wx.redirectTo({
|
|
|
+ url: '/pages/home/home',
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
function hasExplicitLaunchOptions(options?: MapPageLaunchOptions | null): boolean {
|
|
|
if (!options) {
|
|
|
return false
|
|
|
@@ -776,11 +811,12 @@ function buildEmptyResultSceneSnapshot(): MapEngineResultSnapshot {
|
|
|
|
|
|
Page({
|
|
|
data: {
|
|
|
- showDebugPanel: false,
|
|
|
- showGameInfoPanel: false,
|
|
|
- showResultScene: false,
|
|
|
- showSystemSettingsPanel: false,
|
|
|
- showCenterScaleRuler: false,
|
|
|
+ showDebugPanel: false,
|
|
|
+ showGameInfoPanel: false,
|
|
|
+ showResultScene: false,
|
|
|
+ showSystemSettingsPanel: false,
|
|
|
+ showHeartRateDevicePicker: false,
|
|
|
+ showCenterScaleRuler: false,
|
|
|
statusBarHeight: 0,
|
|
|
topInsetHeight: 12,
|
|
|
hudPanelIndex: 0,
|
|
|
@@ -798,6 +834,7 @@ Page({
|
|
|
resultSceneHeroLabel: '本局用时',
|
|
|
resultSceneHeroValue: '--',
|
|
|
resultSceneRows: buildEmptyResultSceneSnapshot().rows,
|
|
|
+ resultSceneCountdownText: '',
|
|
|
panelTimerText: '00:00:00',
|
|
|
panelTimerMode: 'elapsed',
|
|
|
panelMileageText: '0m',
|
|
|
@@ -927,8 +964,11 @@ Page({
|
|
|
|
|
|
onLoad(options: MapPageLaunchOptions) {
|
|
|
clearSessionRecoveryPersistTimer()
|
|
|
+ clearResultExitRedirectTimer()
|
|
|
+ clearResultExitCountdownTimer()
|
|
|
syncedBackendSessionStartId = ''
|
|
|
syncedBackendSessionFinishId = ''
|
|
|
+ redirectedToResultPage = false
|
|
|
shouldAutoRestoreRecoverySnapshot = options && options.recoverSession === '1'
|
|
|
currentGameLaunchEnvelope = resolveGameLaunchEnvelope(options)
|
|
|
if (!hasExplicitLaunchOptions(options)) {
|
|
|
@@ -959,6 +999,7 @@ Page({
|
|
|
const includeRulerFields = this.data.showCenterScaleRuler
|
|
|
let shouldSyncRuntimeSystemSettings = false
|
|
|
let nextLockLifetimeActive = isSystemSettingsLockLifetimeActive()
|
|
|
+ let heartRateSwitchToastText = ''
|
|
|
const nextData: Partial<MapPageData> = filterDebugOnlyPatch({
|
|
|
...nextPatch,
|
|
|
}, includeDebugFields, includeRulerFields)
|
|
|
@@ -1054,6 +1095,8 @@ Page({
|
|
|
: this.data.animationLevel
|
|
|
let shouldSyncBackendSessionStart = false
|
|
|
let backendSessionFinishStatus: 'finished' | 'failed' | null = null
|
|
|
+ let shouldOpenResultExitPrompt = false
|
|
|
+ let resultPageSnapshot: MapEngineResultSnapshot | null = null
|
|
|
|
|
|
if (nextAnimationLevel === 'lite') {
|
|
|
clearHudFxTimer('timer')
|
|
|
@@ -1112,13 +1155,24 @@ Page({
|
|
|
shouldSyncRuntimeSystemSettings = true
|
|
|
clearSessionRecoverySnapshot()
|
|
|
clearSessionRecoveryPersistTimer()
|
|
|
- this.syncResultSceneSnapshot()
|
|
|
+ clearResultExitRedirectTimer()
|
|
|
+ clearResultExitCountdownTimer()
|
|
|
+ resultPageSnapshot = mapEngine ? mapEngine.getResultSceneSnapshot() : null
|
|
|
nextData.showResultScene = true
|
|
|
nextData.showDebugPanel = false
|
|
|
nextData.showGameInfoPanel = false
|
|
|
nextData.showSystemSettingsPanel = false
|
|
|
clearGameInfoPanelSyncTimer()
|
|
|
backendSessionFinishStatus = nextPatch.gameSessionStatus === 'finished' ? 'finished' : 'failed'
|
|
|
+ shouldOpenResultExitPrompt = true
|
|
|
+ if (resultPageSnapshot) {
|
|
|
+ nextData.resultSceneTitle = resultPageSnapshot.title
|
|
|
+ nextData.resultSceneSubtitle = resultPageSnapshot.subtitle
|
|
|
+ nextData.resultSceneHeroLabel = resultPageSnapshot.heroLabel
|
|
|
+ nextData.resultSceneHeroValue = resultPageSnapshot.heroValue
|
|
|
+ nextData.resultSceneRows = resultPageSnapshot.rows
|
|
|
+ }
|
|
|
+ nextData.resultSceneCountdownText = '3 秒后自动进入成绩页'
|
|
|
} else if (
|
|
|
nextPatch.gameSessionStatus !== this.data.gameSessionStatus
|
|
|
&& nextPatch.gameSessionStatus === 'idle'
|
|
|
@@ -1128,6 +1182,8 @@ Page({
|
|
|
shouldSyncRuntimeSystemSettings = true
|
|
|
clearSessionRecoverySnapshot()
|
|
|
clearSessionRecoveryPersistTimer()
|
|
|
+ clearResultExitRedirectTimer()
|
|
|
+ clearResultExitCountdownTimer()
|
|
|
} else if (
|
|
|
nextPatch.gameSessionStatus !== this.data.gameSessionStatus
|
|
|
&& nextPatch.gameSessionStatus === 'running'
|
|
|
@@ -1138,6 +1194,19 @@ Page({
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (
|
|
|
+ pendingHeartRateSwitchDeviceName
|
|
|
+ && nextPatch.heartRateConnected === true
|
|
|
+ && typeof nextPatch.heartRateDeviceText === 'string'
|
|
|
+ ) {
|
|
|
+ const connectedDeviceName = nextPatch.heartRateDeviceText.trim()
|
|
|
+ if (connectedDeviceName && connectedDeviceName === pendingHeartRateSwitchDeviceName) {
|
|
|
+ heartRateSwitchToastText = `已切换到 ${connectedDeviceName}`
|
|
|
+ nextData.statusText = `已切换心率带:${connectedDeviceName}`
|
|
|
+ pendingHeartRateSwitchDeviceName = null
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (Object.keys(nextData).length || Object.keys(derivedPatch).length) {
|
|
|
this.setData({
|
|
|
...nextData,
|
|
|
@@ -1152,9 +1221,20 @@ Page({
|
|
|
if (backendSessionFinishStatus) {
|
|
|
this.syncBackendSessionFinish(backendSessionFinishStatus)
|
|
|
}
|
|
|
- if (shouldSyncRuntimeSystemSettings) {
|
|
|
- this.applyRuntimeSystemSettings(nextLockLifetimeActive)
|
|
|
- }
|
|
|
+ if (shouldOpenResultExitPrompt && resultPageSnapshot) {
|
|
|
+ this.stashPendingResultSnapshot(resultPageSnapshot)
|
|
|
+ this.presentResultExitPrompt()
|
|
|
+ }
|
|
|
+ if (heartRateSwitchToastText) {
|
|
|
+ wx.showToast({
|
|
|
+ title: `${heartRateSwitchToastText},并设为首选设备`,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1800,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if (shouldSyncRuntimeSystemSettings) {
|
|
|
+ this.applyRuntimeSystemSettings(nextLockLifetimeActive)
|
|
|
+ }
|
|
|
if (this.data.showGameInfoPanel) {
|
|
|
this.scheduleGameInfoPanelSnapshotSync()
|
|
|
}
|
|
|
@@ -1169,6 +1249,10 @@ Page({
|
|
|
if (backendSessionFinishStatus) {
|
|
|
this.syncBackendSessionFinish(backendSessionFinishStatus)
|
|
|
}
|
|
|
+ if (shouldOpenResultExitPrompt && resultPageSnapshot) {
|
|
|
+ this.stashPendingResultSnapshot(resultPageSnapshot)
|
|
|
+ this.presentResultExitPrompt()
|
|
|
+ }
|
|
|
if (shouldSyncRuntimeSystemSettings) {
|
|
|
this.applyRuntimeSystemSettings(nextLockLifetimeActive)
|
|
|
}
|
|
|
@@ -1209,6 +1293,7 @@ Page({
|
|
|
...buildResolvedSystemSettingsPatch(systemSettingsState),
|
|
|
showDebugPanel: false,
|
|
|
showGameInfoPanel: false,
|
|
|
+ showResultScene: false,
|
|
|
showSystemSettingsPanel: false,
|
|
|
statusBarHeight,
|
|
|
topInsetHeight: Math.max(statusBarHeight + 12, menuButtonBottom + 20),
|
|
|
@@ -1218,6 +1303,12 @@ Page({
|
|
|
gameInfoSubtitle: '未开始',
|
|
|
gameInfoLocalRows: [],
|
|
|
gameInfoGlobalRows: buildEmptyGameInfoSnapshot().globalRows,
|
|
|
+ resultSceneTitle: '本局结果',
|
|
|
+ resultSceneSubtitle: '未开始',
|
|
|
+ resultSceneHeroLabel: '本局用时',
|
|
|
+ resultSceneHeroValue: '--',
|
|
|
+ resultSceneRows: buildEmptyResultSceneSnapshot().rows,
|
|
|
+ resultSceneCountdownText: '',
|
|
|
panelTimerText: '00:00:00',
|
|
|
panelTimerMode: 'elapsed',
|
|
|
panelTimerFxClass: '',
|
|
|
@@ -1349,6 +1440,18 @@ Page({
|
|
|
stageCanvasAttached = false
|
|
|
this.measureStageAndCanvas()
|
|
|
this.loadGameLaunchEnvelope(currentGameLaunchEnvelope)
|
|
|
+ const app = getApp<IAppOption>()
|
|
|
+ const pendingHeartRateAutoConnect = app.globalData ? app.globalData.pendingHeartRateAutoConnect : null
|
|
|
+ if (pendingHeartRateAutoConnect && pendingHeartRateAutoConnect.enabled && mapEngine) {
|
|
|
+ const pendingDeviceName = pendingHeartRateAutoConnect.deviceName || '心率带'
|
|
|
+ app.globalData.pendingHeartRateAutoConnect = null
|
|
|
+ mapEngine.handleConnectHeartRate()
|
|
|
+ this.setData({
|
|
|
+ statusText: `正在自动连接局前设备:${pendingDeviceName}`,
|
|
|
+ heartRateStatusText: `正在自动连接 ${pendingDeviceName}`,
|
|
|
+ heartRateDeviceText: pendingDeviceName,
|
|
|
+ })
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
onShow() {
|
|
|
@@ -1360,6 +1463,8 @@ Page({
|
|
|
|
|
|
onHide() {
|
|
|
this.persistSessionRecoverySnapshot()
|
|
|
+ clearResultExitRedirectTimer()
|
|
|
+ clearResultExitCountdownTimer()
|
|
|
if (mapEngine) {
|
|
|
mapEngine.handleAppHide()
|
|
|
}
|
|
|
@@ -1368,6 +1473,8 @@ Page({
|
|
|
onUnload() {
|
|
|
this.persistSessionRecoverySnapshot()
|
|
|
clearSessionRecoveryPersistTimer()
|
|
|
+ clearResultExitRedirectTimer()
|
|
|
+ clearResultExitCountdownTimer()
|
|
|
syncedBackendSessionStartId = ''
|
|
|
syncedBackendSessionFinishId = ''
|
|
|
clearGameInfoPanelSyncTimer()
|
|
|
@@ -1388,6 +1495,7 @@ Page({
|
|
|
systemSettingsLockLifetimeActive = false
|
|
|
currentGameLaunchEnvelope = getDemoGameLaunchEnvelope()
|
|
|
shouldAutoRestoreRecoverySnapshot = false
|
|
|
+ redirectedToResultPage = false
|
|
|
stageCanvasAttached = false
|
|
|
},
|
|
|
|
|
|
@@ -1528,6 +1636,57 @@ Page({
|
|
|
})
|
|
|
},
|
|
|
|
|
|
+ stashPendingResultSnapshot(snapshot: MapEngineResultSnapshot) {
|
|
|
+ const app = getApp<IAppOption>()
|
|
|
+ if (app.globalData) {
|
|
|
+ app.globalData.pendingResultSnapshot = snapshot
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ redirectToResultPage() {
|
|
|
+ if (redirectedToResultPage) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ clearResultExitRedirectTimer()
|
|
|
+ clearResultExitCountdownTimer()
|
|
|
+ redirectedToResultPage = true
|
|
|
+ const sessionContext = getCurrentBackendSessionContext()
|
|
|
+ const resultUrl = sessionContext
|
|
|
+ ? `/pages/result/result?sessionId=${encodeURIComponent(sessionContext.sessionId)}`
|
|
|
+ : '/pages/result/result'
|
|
|
+ wx.redirectTo({
|
|
|
+ url: resultUrl,
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ presentResultExitPrompt() {
|
|
|
+ clearResultExitRedirectTimer()
|
|
|
+ clearResultExitCountdownTimer()
|
|
|
+
|
|
|
+ let remainingSeconds = Math.ceil(RESULT_EXIT_REDIRECT_DELAY_MS / 1000)
|
|
|
+ this.setData({
|
|
|
+ showResultScene: true,
|
|
|
+ resultSceneCountdownText: `${remainingSeconds} 秒后自动进入成绩页`,
|
|
|
+ })
|
|
|
+
|
|
|
+ resultExitCountdownTimer = setInterval(() => {
|
|
|
+ remainingSeconds -= 1
|
|
|
+ if (remainingSeconds <= 0) {
|
|
|
+ clearResultExitCountdownTimer()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.setData({
|
|
|
+ resultSceneCountdownText: `${remainingSeconds} 秒后自动进入成绩页`,
|
|
|
+ })
|
|
|
+ }, 1000) as unknown as number
|
|
|
+
|
|
|
+ resultExitRedirectTimer = setTimeout(() => {
|
|
|
+ resultExitRedirectTimer = 0
|
|
|
+ this.redirectToResultPage()
|
|
|
+ }, RESULT_EXIT_REDIRECT_DELAY_MS) as unknown as number
|
|
|
+ },
|
|
|
+
|
|
|
restoreRecoverySnapshot(snapshot: SessionRecoverySnapshot) {
|
|
|
systemSettingsLockLifetimeActive = true
|
|
|
this.applyRuntimeSystemSettings(true)
|
|
|
@@ -2052,20 +2211,53 @@ Page({
|
|
|
},
|
|
|
|
|
|
handleConnectHeartRate() {
|
|
|
- if (mapEngine) {
|
|
|
- mapEngine.handleConnectHeartRate()
|
|
|
- }
|
|
|
- },
|
|
|
+ if (this.data.lockHeartRateDevice || this.data.heartRateSourceMode !== 'real') {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (mapEngine) {
|
|
|
+ mapEngine.handleConnectHeartRate()
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
- handleDisconnectHeartRate() {
|
|
|
+ handleOpenHeartRateDevicePicker() {
|
|
|
+ if (this.data.lockHeartRateDevice || this.data.heartRateSourceMode !== 'real') {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.setData({
|
|
|
+ showHeartRateDevicePicker: true,
|
|
|
+ })
|
|
|
if (mapEngine) {
|
|
|
- mapEngine.handleDisconnectHeartRate()
|
|
|
+ mapEngine.handleConnectHeartRate()
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ handleCloseHeartRateDevicePicker() {
|
|
|
+ this.setData({
|
|
|
+ showHeartRateDevicePicker: false,
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ handleDisconnectHeartRate() {
|
|
|
+ if (this.data.lockHeartRateDevice || this.data.heartRateSourceMode !== 'real') {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (mapEngine) {
|
|
|
+ mapEngine.handleDisconnectHeartRate()
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
handleConnectHeartRateDevice(event: WechatMiniprogram.BaseEvent<{ deviceId?: string }>) {
|
|
|
if (mapEngine && event.currentTarget && event.currentTarget.dataset && event.currentTarget.dataset.deviceId) {
|
|
|
- mapEngine.handleConnectHeartRateDevice(event.currentTarget.dataset.deviceId)
|
|
|
+ const targetDeviceId = event.currentTarget.dataset.deviceId
|
|
|
+ const targetDevice = this.data.heartRateDiscoveredDevices.find((item) => item.deviceId === targetDeviceId)
|
|
|
+ pendingHeartRateSwitchDeviceName = targetDevice ? targetDevice.name : null
|
|
|
+ mapEngine.handleConnectHeartRateDevice(targetDeviceId)
|
|
|
+ this.setData({
|
|
|
+ showHeartRateDevicePicker: false,
|
|
|
+ statusText: targetDevice
|
|
|
+ ? `正在切换到 ${targetDevice.name}`
|
|
|
+ : '正在切换心率带设备',
|
|
|
+ })
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -2174,9 +2366,21 @@ Page({
|
|
|
cancelText: '取消',
|
|
|
success: (result) => {
|
|
|
if (result.confirm && mapEngine) {
|
|
|
+ clearResultExitRedirectTimer()
|
|
|
+ clearResultExitCountdownTimer()
|
|
|
this.syncBackendSessionFinish('cancelled')
|
|
|
+ clearSessionRecoverySnapshot()
|
|
|
+ clearSessionRecoveryPersistTimer()
|
|
|
systemSettingsLockLifetimeActive = false
|
|
|
mapEngine.handleForceExitGame()
|
|
|
+ wx.showToast({
|
|
|
+ title: '已退出当前对局',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1000,
|
|
|
+ })
|
|
|
+ setTimeout(() => {
|
|
|
+ navigateAwayFromMapAfterCancel()
|
|
|
+ }, 180)
|
|
|
}
|
|
|
},
|
|
|
})
|
|
|
@@ -2312,24 +2516,11 @@ Page({
|
|
|
handleResultSceneTap() {},
|
|
|
|
|
|
handleCloseResultScene() {
|
|
|
- this.setData({
|
|
|
- showResultScene: false,
|
|
|
- })
|
|
|
+ this.redirectToResultPage()
|
|
|
},
|
|
|
|
|
|
handleRestartFromResult() {
|
|
|
- if (!mapEngine) {
|
|
|
- return
|
|
|
- }
|
|
|
- this.setData({
|
|
|
- showResultScene: false,
|
|
|
- }, () => {
|
|
|
- if (mapEngine) {
|
|
|
- systemSettingsLockLifetimeActive = true
|
|
|
- this.applyRuntimeSystemSettings(true)
|
|
|
- mapEngine.handleStartGame()
|
|
|
- }
|
|
|
- })
|
|
|
+ this.redirectToResultPage()
|
|
|
},
|
|
|
|
|
|
handleOpenSystemSettingsPanel() {
|