修复主题恢复与勋章状态
This commit is contained in:
@@ -5,6 +5,21 @@ const darkMod = require('./utils/darkMode')
|
|||||||
const voice = require('./utils/voice')
|
const voice = require('./utils/voice')
|
||||||
const config = require('./config')
|
const config = require('./config')
|
||||||
|
|
||||||
|
const refreshOpenThemeViews = () => {
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
pages.forEach(p => {
|
||||||
|
if (p && typeof p.applyThemeToPage === 'function') {
|
||||||
|
p.applyThemeToPage()
|
||||||
|
} else if (p && p.route) {
|
||||||
|
try { themeMod.applyThemeToPage(p) } catch (e) {}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const tb = p && p.getTabBar && p.getTabBar()
|
||||||
|
if (tb && typeof tb.updateTheme === 'function') tb.updateTheme()
|
||||||
|
} catch (e) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
App({
|
App({
|
||||||
async onLaunch() {
|
async onLaunch() {
|
||||||
// 尽早设导航栏色 + 窗口背景:原生导航栏在 JS 执行前用 app.json 写死的
|
// 尽早设导航栏色 + 窗口背景:原生导航栏在 JS 执行前用 app.json 写死的
|
||||||
@@ -13,22 +28,7 @@ App({
|
|||||||
|
|
||||||
// Init dark mode listener (refreshes all open pages when system theme changes)
|
// Init dark mode listener (refreshes all open pages when system theme changes)
|
||||||
darkMod.watchDarkMode((isDark) => {
|
darkMod.watchDarkMode((isDark) => {
|
||||||
const pages = getCurrentPages()
|
refreshOpenThemeViews()
|
||||||
pages.forEach(p => {
|
|
||||||
if (p && typeof p.applyThemeToPage === 'function') {
|
|
||||||
p.applyThemeToPage()
|
|
||||||
} else if (p && p.route) {
|
|
||||||
// index/records/leaderboard 等页面没定义 applyThemeToPage 方法,
|
|
||||||
// 但都在 onLoad/onShow 里套用过主题,这里直接用 themeMod 兜底刷新,
|
|
||||||
// 否则系统深/浅色切换时这些页不会实时变色。
|
|
||||||
try { themeMod.applyThemeToPage(p) } catch (e) {}
|
|
||||||
}
|
|
||||||
// 自定义 tabBar 不在 pages 列表里,单独通知它切换暗黑/明亮
|
|
||||||
try {
|
|
||||||
const tb = p.getTabBar && p.getTabBar()
|
|
||||||
if (tb && typeof tb.updateTheme === 'function') tb.updateTheme()
|
|
||||||
} catch (e) {}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Init cloud storage (no-op if not configured)
|
// Init cloud storage (no-op if not configured)
|
||||||
@@ -158,6 +158,10 @@ App({
|
|||||||
}
|
}
|
||||||
if (cloudData.themeId) {
|
if (cloudData.themeId) {
|
||||||
themeMod.setTheme(cloudData.themeId)
|
themeMod.setTheme(cloudData.themeId)
|
||||||
|
// Cloud restore is asynchronous: pages may have already rendered
|
||||||
|
// with their initial orange data. Reapply immediately so they don't
|
||||||
|
// wait for a later tab switch to reflect the restored theme.
|
||||||
|
refreshOpenThemeViews()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const util = require('../../utils/util')
|
|||||||
const themeMod = require('../../utils/theme')
|
const themeMod = require('../../utils/theme')
|
||||||
const iconsMod = require('../../utils/icons')
|
const iconsMod = require('../../utils/icons')
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
|
const { getBadgeProgress, getBadgeCelebration } = require('../../utils/badge-state')
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
@@ -79,7 +80,6 @@ Page({
|
|||||||
const totalDays = stats.totalDays
|
const totalDays = stats.totalDays
|
||||||
const badgeDefs = config.trainingDayBadges
|
const badgeDefs = config.trainingDayBadges
|
||||||
const n = badgeDefs.length
|
const n = badgeDefs.length
|
||||||
const maxDays = badgeDefs[n - 1].days
|
|
||||||
const TRACK_PAD = 4 // 首尾留白,保证首尾节点/标签不溢出
|
const TRACK_PAD = 4 // 首尾留白,保证首尾节点/标签不溢出
|
||||||
const segWidth = n > 1 ? (100 - TRACK_PAD * 2) / (n - 1) : 0
|
const segWidth = n > 1 ? (100 - TRACK_PAD * 2) / (n - 1) : 0
|
||||||
const posOf = (i) => TRACK_PAD + i * segWidth
|
const posOf = (i) => TRACK_PAD + i * segWidth
|
||||||
@@ -88,17 +88,7 @@ Page({
|
|||||||
unlocked: totalDays >= b.days,
|
unlocked: totalDays >= b.days,
|
||||||
pos: Math.round(posOf(i) * 10) / 10
|
pos: Math.round(posOf(i) * 10) / 10
|
||||||
}))
|
}))
|
||||||
let badgeProgress
|
const badgeProgress = getBadgeProgress(badgeDefs, totalDays)
|
||||||
if (totalDays <= 0) {
|
|
||||||
badgeProgress = 0
|
|
||||||
} else if (totalDays >= maxDays) {
|
|
||||||
badgeProgress = 100
|
|
||||||
} else {
|
|
||||||
let i = 0
|
|
||||||
while (i < n - 1 && totalDays >= badgeDefs[i + 1].days) i++
|
|
||||||
const ratio = (totalDays - badgeDefs[i].days) / (badgeDefs[i + 1].days - badgeDefs[i].days)
|
|
||||||
badgeProgress = Math.round((posOf(i) + ratio * segWidth) * 10) / 10
|
|
||||||
}
|
|
||||||
|
|
||||||
// 单枚解锁庆祝反馈:对比持久化"已见解锁集合",只庆祝新增勋章。
|
// 单枚解锁庆祝反馈:对比持久化"已见解锁集合",只庆祝新增勋章。
|
||||||
// 首次运行(无存储)仅初始化集合,避免老用户一打开就被历史勋章刷屏。
|
// 首次运行(无存储)仅初始化集合,避免老用户一打开就被历史勋章刷屏。
|
||||||
@@ -319,9 +309,8 @@ Page({
|
|||||||
wx.setStorageSync(KEY, unlockedDays)
|
wx.setStorageSync(KEY, unlockedDays)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const prevSet = new Set(prev)
|
const { newly, seen } = getBadgeCelebration(prev, unlockedDays)
|
||||||
const newly = unlockedDays.filter(d => !prevSet.has(d))
|
wx.setStorageSync(KEY, seen)
|
||||||
wx.setStorageSync(KEY, unlockedDays)
|
|
||||||
if (newly.length === 0) return null
|
if (newly.length === 0) return null
|
||||||
const names = newly.map(d => {
|
const names = newly.map(d => {
|
||||||
const b = config.trainingDayBadges.find(x => x.days === d)
|
const b = config.trainingDayBadges.find(x => x.days === d)
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ Page({
|
|||||||
wx.removeStorageSync('app_theme')
|
wx.removeStorageSync('app_theme')
|
||||||
wx.removeStorageSync('custom_plans')
|
wx.removeStorageSync('custom_plans')
|
||||||
wx.removeStorageSync('user_profile')
|
wx.removeStorageSync('user_profile')
|
||||||
|
wx.removeStorageSync('badge_celebrated')
|
||||||
|
|
||||||
// 3. Clear every cloud doc for this openid. Local data is already gone
|
// 3. Clear every cloud doc for this openid. Local data is already gone
|
||||||
// even if this fails, so surface that partial failure honestly.
|
// even if this fails, so surface that partial failure honestly.
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
const test = require('node:test')
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
|
||||||
|
const { getBadgeProgress, getBadgeCelebration } = require('../utils/badge-state')
|
||||||
|
|
||||||
|
const badges = [{ days: 3 }, { days: 7 }, { days: 14 }]
|
||||||
|
|
||||||
|
test('badge progress remains zero before the first milestone', () => {
|
||||||
|
assert.equal(getBadgeProgress(badges, 1), 0)
|
||||||
|
assert.equal(getBadgeProgress(badges, 2), 0)
|
||||||
|
assert.equal(getBadgeProgress(badges, 3), 4)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('badge celebration keeps prior milestones seen after records are deleted', () => {
|
||||||
|
const afterDeletion = getBadgeCelebration([3], [])
|
||||||
|
assert.deepEqual(afterDeletion, { newly: [], seen: [3] })
|
||||||
|
|
||||||
|
const afterRetraining = getBadgeCelebration(afterDeletion.seen, [3])
|
||||||
|
assert.deepEqual(afterRetraining, { newly: [], seen: [3] })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('badge celebration only reports milestones never seen before', () => {
|
||||||
|
assert.deepEqual(getBadgeCelebration([3], [3, 7]), {
|
||||||
|
newly: [7],
|
||||||
|
seen: [3, 7]
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
const TRACK_PAD = 4
|
||||||
|
|
||||||
|
const getBadgeProgress = (badges, totalDays) => {
|
||||||
|
const defs = Array.isArray(badges) ? badges : []
|
||||||
|
if (defs.length === 0) return 0
|
||||||
|
|
||||||
|
const days = Math.max(0, Number(totalDays) || 0)
|
||||||
|
if (days < defs[0].days) return 0
|
||||||
|
if (defs.length === 1 || days >= defs[defs.length - 1].days) return 100
|
||||||
|
|
||||||
|
const segmentWidth = (100 - TRACK_PAD * 2) / (defs.length - 1)
|
||||||
|
let index = 0
|
||||||
|
while (index < defs.length - 1 && days >= defs[index + 1].days) index++
|
||||||
|
|
||||||
|
const ratio = (days - defs[index].days) / (defs[index + 1].days - defs[index].days)
|
||||||
|
return Math.round((TRACK_PAD + index * segmentWidth + ratio * segmentWidth) * 10) / 10
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBadgeCelebration = (previous, unlockedDays) => {
|
||||||
|
const unlocked = Array.isArray(unlockedDays) ? unlockedDays : []
|
||||||
|
if (!Array.isArray(previous)) return { newly: [], seen: Array.from(new Set(unlocked)) }
|
||||||
|
|
||||||
|
const previousSet = new Set(previous)
|
||||||
|
const newly = unlocked.filter(days => !previousSet.has(days))
|
||||||
|
return { newly, seen: Array.from(new Set(previous.concat(unlocked))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getBadgeProgress, getBadgeCelebration }
|
||||||
Reference in New Issue
Block a user