Files
o62lte-customising-plan/s5e-icons/main.lua
T
2026-10-02 21:34:56 +08:00

680 lines
23 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--[[----------------------------------------------------------------------------
OS4 Icons (S5 eSIM) —— Xiaomi Watch S5 41mm(q63) 的 OS4 图标包 -> Xiaomi Watch S5 eSIM 46mm(p62lte)
目标固件:miwear.watch.p62lte_v3.112.035_full(480x480,Lua 5.4 宿主)
写入目标:两个 ROMFS 分区
/dev/app 33 条(vela_app.bin 卷名 app sb_size 103042192)
/dev/health 14 条(vela_health.bin 卷名 health sb_size 206924736)
47 条负载每条都恰好等于原槽位长度(144x144 ARGB8888 = 82956 B),
所以 ROMFS 的 inode 表 / 后续所有偏移一个字节都不动。
界面按 480x480 设计(_tools/s5e_pages.py 本机真字体渲染 + 圆屏切边检查 0 越界)。
真机已验证的底层能力(沿用 S5->S4 那个表盘):
* 宿主不调用 ui.init -> 加载期建界面
* SCRIPT_PATH 为空 -> 用 debug.getinfo 反推目录
* .face 不解包,资源仍在 resource.bin 里 -> 按偏移读出来用
* os.execute / dd 可用;os.execute 的返回值不可靠 -> 一律用内容比对判成败
------------------------------------------------------------------------------]]
local lvgl = require("lvgl")
local W, H = 480, 480
local BLK = 4
-- 等长覆盖只保证「偏移」512 对齐;RLE 素材的长度常常不是 4 的倍数(如 1635 / 3047),
-- 那种记录必须退化成 bs=1(偏移本身对齐,所以 bs=1 也精确)。
local function ddBlk(off, len)
if off % BLK == 0 and len % BLK == 0 then return BLK end
return 1
end
local DEVS = { app = "/dev/app", health = "/dev/health" }
local DEVLIST = { "app", "health" } -- 顺序固定:app 先,health 后
local TMPH = "/tmp/s5e_head.bin" -- 版本校验读出的 64 字节
local TMPW = "/tmp/s5e_wr.bin" -- 待写入的负载(resource.bin 里抠出来的那一份)
local TMPB = "/tmp/s5e_back.bin" -- 写完之后回读出来的那一份
--------------------------------------------------------------------- 自身目录
local SRC = ""
if debug and debug.getinfo then
local info = debug.getinfo(1, "S")
SRC = (info and info.source) or ""
end
if type(SRC) ~= "string" then SRC = "" end
if SRC:sub(1, 1) == "@" then SRC = SRC:sub(2) end
SRC = SRC:gsub("\\", "/")
local THIS_DIR = SRC:match("^(.*)/[^/]+$") or ""
local SP = THIS_DIR ~= "" and (THIS_DIR .. "/") or ""
local PARENT = SP:gsub("[^/]+/$", "")
local RES = nil
do
local c = { PARENT .. "resource.bin", SP .. "../resource.bin", SP .. "resource.bin" }
for i = 1, #c do
local f = io.open(c[i], "rb")
if f then f:close(); RES = c[i]; break end
end
end
--------------------------------------------------------------------- 资源偏移
-- UI 图片 { resource.bin 内偏移, 长度, 名字 } —— 由 _tools/s5e_build.py 回填
local UIIMG = {
__UIIMG__
}
-- 负载表:{ 设备, 分区内偏移, 长度, resource.bin 内偏移 } —— 同样由脚本回填
local PAYTBL = {
__PAY__
__RB__
}
local function prepImages()
local paths = {}
if not RES then return paths end
local f = io.open(RES, "rb")
if not f then return paths end
for i = 1, #UIIMG do
local e = UIIMG[i]
f:seek("set", e[1])
local d = f:read(e[2])
if d and #d == e[2] then
local p = "/tmp/s5e_ui_" .. e[3] .. ".bin"
local o = io.open(p, "wb")
if o then o:write(d); o:close(); paths[e[3]] = p end
end
end
f:close()
return paths
end
local IMG = prepImages()
--------------------------------------------------------------------- 颜色与字体
local C_GRAY, C_WHITE = '#999999', '#ffffff'
local C_OK, C_ERR = '#43cf7c', '#d43030'
local C_PLATE, C_CIRC = 0x002927, 0x4f4f4f
local function getFont(name, size)
local f = nil
pcall(function() f = lvgl.Font(name, size) end)
return f
end
local F_CLOCK = getFont('MiSans-Semibold', 29)
local F_TITLE = getFont('MiSans-Medium', 31)
local F_ITEM = getFont('MiSans-Medium', 29)
local F_BODY = getFont('MiSans-Medium', 25)
local F_VER = getFont('MiSans-Medium', 21)
--------------------------------------------------------------------- 按下高亮
local HL = 0x005149
local pressed = nil
local tickCount = 0
local function setBg(obj, c)
pcall(function() obj:set { bg_color = c } end)
end
local function pressRelease()
if pressed then
setBg(pressed.obj, C_PLATE)
pressed = nil
end
end
--------------------------------------------------------------------- 基础
local ui = {}
local built = false
local pages = {}
local clocks = {}
local job = { run = false, list = nil, i = 0, ok = 0, errs = 0, skipped = 0,
variant = 'replace', bad = {}, done = {}, total = {} }
local pendingVariant = 'replace'
local confirmFrom = 'home'
local function newPage()
local p = nil
pcall(function()
p = lvgl.Object(nil, { x = 0, y = 0, w = W, h = H, bg_color = 0x000000,
bg_opa = 255, border_width = 0, pad_all = 0 })
p:clear_flag(lvgl.FLAG.SCROLLABLE)
p:add_flag(lvgl.FLAG.EVENT_BUBBLE)
end)
return p
end
local function show(p)
if not p then return end
pressRelease()
for k, v in pairs(pages) do
pcall(function() v:add_flag(lvgl.FLAG.HIDDEN) end)
end
pcall(function() p:clear_flag(lvgl.FLAG.HIDDEN) end)
end
local function bind(obj, fn)
if not obj then return end
local ev = lvgl.EVENT
if ev and ev.SHORT_CLICKED then
if pcall(function() obj:onevent(ev.SHORT_CLICKED, function() fn() end) end) then return end
end
if pcall(function() obj:onClicked(function() fn() end) end) then return end
pcall(function() obj:onevent(ev and ev.CLICKED, fn) end)
end
local function bindTap(obj, onTap)
if not obj then return end
local ok = pcall(function()
obj:onPressed(function()
setBg(obj, HL)
pressed = { obj = obj, t = tickCount }
end)
obj:onClicked(function()
pressRelease()
if onTap then onTap() end
end)
end)
if not ok then bind(obj, onTap) end
end
local function setClickable(o)
if not o then return end
pcall(function() o:add_flag(lvgl.FLAG.CLICKABLE) end)
end
local function lab(parent, o)
o.text_font = o.text_font or F_BODY
local l = nil
pcall(function() l = parent:Label(o) end)
return l
end
local function clockLabel(parent)
-- 居中不用 text_align(它要的是 lv_text_align_t;传 ALIGN.CENTER 会越界被忽略)
-- 改为按真字体量出的宽度手工算 x
local l = lab(parent, { x = 201, y = 4, w = 78, h = 46, text = '--:--',
text_color = C_GRAY, font_size = 29, text_font = F_CLOCK })
clocks[#clocks + 1] = l
return l
end
local function pageTitle(parent, text, x, w)
return lab(parent, { x = x, y = 36, w = w, h = 46, text = text,
text_color = C_WHITE, font_size = 31, text_font = F_TITLE })
end
local function backArrow(parent, x, onTap)
local im = nil
if IMG.back then
pcall(function() im = parent:Image { src = IMG.back, x = x or 167, y = 44 } end)
setClickable(im)
bind(im, onTap)
end
return im
end
-- 列表项:底板(整块可点) + 圆形遮罩 + 图标 + 标题(pad_all=0 必须,否则子元素右下偏移)
local function listItem(parent, y, iconkey, title, onTap)
local plate = nil
pcall(function()
plate = parent:Object { x = 50, y = y, w = 379, h = 91, pad_all = 0,
bg_color = C_PLATE, border_width = 0, radius = 35 }
end)
if plate then
if onTap then
setClickable(plate)
bindTap(plate, onTap)
end
pcall(function()
plate:Object { x = 21, y = 20, w = 52, h = 52,
bg_color = C_CIRC, border_width = 0, radius = 26 }
end)
if IMG[iconkey] then
pcall(function() plate:Image { src = IMG[iconkey], x = 31, y = 30 } end)
end
lab(plate, { x = 82, y = 26, w = 214, h = 40, text = title,
text_color = C_WHITE, font_size = 29, text_font = F_ITEM })
end
return plate
end
local function bigButton(parent, text)
local b = nil
pcall(function()
b = parent:Object { x = 111, y = 376, w = 258, h = 82, pad_all = 0,
bg_color = C_PLATE, border_width = 0, radius = 41 }
end)
if b then setClickable(b) end
-- "确认替换"/"重启手表" 29px 量得 116px -> 居中 x = 111 + (258-116)/2 = 182
lab(b, { x = 71, y = 22, w = 126, h = 40, text = text,
text_color = C_WHITE, font_size = 29, text_font = F_ITEM })
return b
end
local function clockTick()
local t = '--:--'
if os and type(os.date) == "function" then
local ok, s = pcall(os.date, "%H:%M")
if ok and type(s) == "string" then t = s end
end
for i = 1, #clocks do
pcall(function() clocks[i]:set { text = t } end)
end
end
--------------------------------------------------------------------- 外壳
local function slurp(p, n)
local f = io.open(p, "rb")
if not f then return nil end
local d = f:read(n or "*a")
f:close()
return d
end
local function sh(cmd)
if type(os.execute) ~= "function" then return -1 end
local a, b, c = os.execute(cmd)
if type(a) == "number" then return a end
if a == true then return 0 end
if type(c) == "number" then return c end
if type(b) == "number" then return b end
return -1
end
-- 从 resource.bin 里取一段(负载 / 原始字节)
local function slice(off, len)
if not RES then return nil end
local f = io.open(RES, "rb")
if not f then return nil end
f:seek("set", off)
local d = f:read(len)
f:close()
return d
end
-- 读设备上某偏移处的 head 字节(不写,只读)
local function readBack(dev, off, len, tmp, bsz)
if os.remove then pcall(os.remove, tmp) end
sh(string.format("dd if=%s of=%s bs=%d skip=%d count=%d",
DEVS[dev], tmp, bsz or 1, bsz and (off // bsz) or off,
bsz and (len // bsz) or len))
return slurp(tmp, len)
end
--------------------------------------------------------------------- 预检查
local desc1 = nil
local function setDesc1(txt, color)
if not desc1 then return end
pcall(function() desc1:set { text = txt, text_color = color } end)
end
local function doCheck()
if not RES then
setDesc1('找不到 resource.bin,无法操作。', C_ERR)
return false
end
local list = PAYTBL.pay or {}
if #list == 0 then
setDesc1('预检查失败:负载表为空。', C_ERR)
return false
end
local msg = {}
for i = 1, #DEVLIST do
local d = DEVLIST[i]
local h = readBack(d, 0, 48, TMPH, 1)
if not h or #h < 48 then
msg[#msg + 1] = d .. ' 读不到'
elseif h:sub(1, 8) ~= "-rom1fs-" then
msg[#msg + 1] = d .. ' 头不是 ROMFS'
else
local z = h:find("\0", 17, true)
local vol = h:sub(17, (z or 33) - 1)
local size = string.unpack("<I4", h, 9)
if vol ~= d then
msg[#msg + 1] = d .. ' 卷名是 ' .. vol
else
msg[#msg + 1] = string.format('%s 可读 %.0fMB', d, size / 1048576)
end
end
end
local bad, total = 0, 0
for i = 1, #list do
local r = list[i]
local b = ddBlk(r[2], r[3])
if (r[2] % b) ~= 0 or (r[3] % b) ~= 0 then bad = bad + 1 end
total = total + r[3]
end
local head = string.format('预检查 | %d 条 | %.2fMB', #list, total / 1048576)
if bad > 0 then
setDesc1(head .. string.format('\n%d 项未对齐!', bad), C_ERR)
return false
end
local okAll = true
for i = 1, #msg do
if msg[i]:find('读不到') or msg[i]:find('不是 ROMFS') or msg[i]:find('卷名') then okAll = false end
end
-- 抽样版本校验:每个分区取第一条负载,比对设备上真实的 64 字节与原厂字节。
-- 这一步能确认"负载偏移是给这一版固件算的",而且只读、不写任何字节。
for i = 1, #DEVLIST do
local d = DEVLIST[i]
local idx = nil
for k = 1, #list do
if list[k][1] == d then idx = k break end
end
if idx then
local o = PAYTBL.rb[idx]
local p = PAYTBL.pay[idx]
local want = o and slice(o[4], 64) or nil
local new = p and slice(p[4], 64) or nil
local got = o and readBack(d, o[2], 64, TMPH, 1) or nil
if want and got and #got >= 64 and got == want then
msg[#msg + 1] = d .. ' 原厂✓'
elseif new and got and #got >= 64 and got == new then
msg[#msg + 1] = d .. ' 已替换✓'
else
msg[#msg + 1] = d .. ' 抽样×'
okAll = false
end
end
end
setDesc1(head .. '\n' .. table.concat(msg, ' / '), okAll and C_OK or C_ERR)
return okAll
end
--------------------------------------------------------------------- 替换 / 回滚 / 校验
local logLabel = nil
local rebootBtn = nil
local runningTitle = nil
local logLines = {}
local function logOut(s)
logLines[#logLines + 1] = s
while #logLines > 8 do table.remove(logLines, 1) end
if logLabel then
pcall(function() logLabel:set { text = table.concat(logLines, "\n") } end)
end
end
local function showReboot()
if rebootBtn then pcall(function() rebootBtn:clear_flag(lvgl.FLAG.HIDDEN) end) end
end
-- 一条记录的动作(三条 dd:版本校验读 / 写 / 回读):
-- mode='write' : 先确认设备上现有 64B 等于原厂字节(版本对得上),写入新图标,整段回读比对
-- mode='rollback' : 反向 —— 先确认现有 64B 等于替换后的字节,再写回原厂字节并回读比对
-- 判据一律是内容比对,不看 os.execute 的返回值(这台机器上它不可靠)
local function runOne(i, mode)
local srcname = (mode == 'rollback') and 'rb' or 'pay'
local wantname = (mode == 'rollback') and 'pay' or 'rb'
local rec = PAYTBL[srcname][i]
local want = PAYTBL[wantname][i]
if not rec or not want then return 'err' end
local dev = rec[1]
-- 现状认定:等于「原厂字节」(want)或等于「替换后字节」(rec)都算对版。
-- 这样重复点替换、或断电后半途重来都不会被判成「版本不符」而卡住。
local headW = slice(want[4], 64)
local headO = slice(rec[4], 64)
if not headW or not headO or #headW < 64 or #headO < 64 then return 'err' end
local cur = readBack(dev, rec[2], 64, TMPH, 1)
if not cur or #cur < 64 then return 'err' end
if cur ~= headW and cur ~= headO then return 'ver' end
local blob = slice(rec[4], rec[3])
if not blob or #blob ~= rec[3] then return 'err' end
-- 先把负载落到 /tmp(resource.bin 内的偏移不保证 4 对齐,所以不能用 dd 直接 skip)
local wf = io.open(TMPW, "wb")
if not wf then return 'err' end
wf:write(blob)
wf:close()
local b = ddBlk(rec[2], rec[3])
sh(string.format("dd if=%s of=%s bs=%d seek=%d count=%d",
TMPW, DEVS[dev], b, rec[2] // b, rec[3] // b))
local back = readBack(dev, rec[2], rec[3], TMPB, b)
if not back or #back ~= rec[3] or back ~= blob then return 'err' end
return 'ok'
end
local function startJob(variant)
if job.run then return end
local pay = PAYTBL.pay or {}
if #pay == 0 then
setDesc1('负载表为空,已中止。', C_ERR)
return
end
job.run, job.i, job.ok, job.errs, job.skipped = true, 0, 0, 0, 0
job.variant, job.bad, job.done, job.total = variant, {}, {}, {}
job.list = pay
for k = 1, #pay do
local d = pay[k][1]
job.total[d] = (job.total[d] or 0) + 1
end
logLines = {}
logOut((variant == 'rollback') and '> 开始回滚' or '> 开始替换')
local _devs = {}
for d in pairs(job.total) do _devs[#_devs + 1] = d end
table.sort(_devs)
local _parts = {}
for i = 1, #_devs do
_parts[#_parts + 1] = string.format('%s %d 条', _devs[i], job.total[_devs[i]])
end
logOut('> ' .. table.concat(_parts, ' / '))
logOut('> 读取 resource.bin')
if rebootBtn then pcall(function() rebootBtn:add_flag(lvgl.FLAG.HIDDEN) end) end
if runningTitle then
pcall(function()
runningTitle:set { text = (variant == 'rollback') and '正在回滚' or '正在替换' }
end)
end
show(pages.running)
end
local function tick()
tickCount = tickCount + 1
if pressed and (tickCount - pressed.t) > 40 then pressRelease() end
if not job.run or not job.list then return end
job.i = job.i + 1
local n = #job.list
if job.i > n then
job.run = false
logOut((job.variant == 'rollback') and '> 回滚完毕,请重启手表。'
or '> 替换完毕,请重启手表。')
logOut(string.format('> 成功 %d / 失败 %d(共 %d)', job.ok, job.errs, n))
if job.skipped > 0 then
logOut(string.format('> 版本不符跳过 %d 条', job.skipped))
end
logOut(string.format('> app %d/%d health %d/%d',
job.done.app or 0, job.total.app or 0,
job.done.health or 0, job.total.health or 0))
showReboot()
return
end
local dev = job.list[job.i][1]
if job.bad[dev] then
job.skipped = job.skipped + 1
job.errs = job.errs + 1
else
local r = runOne(job.i, (job.variant == 'rollback') and 'rollback' or 'write')
if r == 'ok' then
job.ok = job.ok + 1
job.done[dev] = (job.done[dev] or 0) + 1
elseif r == 'ver' then
job.errs = job.errs + 1
job.skipped = job.skipped + 1
job.bad[dev] = true
logOut('> ' .. dev .. ' 版本不符,已跳过')
else
job.errs = job.errs + 1
end
end
if job.i % 10 == 0 or job.i == n then
logOut(string.format('> %d/%d 成 %d 失 %d', job.i, n, job.ok, job.errs))
end
end
--------------------------------------------------------------------- 页面
local function buildHome()
local p = newPage()
if not p then return nil end
clockLabel(p)
pageTitle(p, 'OS4 Icons', 164, 152)
listItem(p, 88, 'ic_help', '快速帮助', function() show(pages.help) end)
listItem(p, 186, 'ic_replace', '开始替换', function()
pendingVariant = 'replace'
confirmFrom = 'home'
show(pages.confirm)
end)
listItem(p, 285, 'ic_about', '关于', function() show(pages.about) end)
return p
end
local function buildHelp()
local p = newPage()
if not p then return nil end
clockLabel(p)
backArrow(p, 167, function() show(pages.home) end)
local t = pageTitle(p, '快速帮助', 188, 124)
setClickable(t)
bind(t, function() show(pages.home) end)
listItem(p, 88, 'ic_check', '环境预检查', function() doCheck() end)
desc1 = lab(p, { x = 67, y = 182, w = 348, h = 76,
text = '检查 app / health 两个分区\n与 47 条负载的完整性。',
text_color = C_GRAY, font_size = 25, text_font = F_BODY })
listItem(p, 264, 'ic_rollback', '回滚修改', function()
pendingVariant = 'rollback'
confirmFrom = 'help'
show(pages.confirm)
end)
lab(p, { x = 67, y = 355, w = 348, h = 36,
text = '恢复 47 个图标的原始样式。',
text_color = C_GRAY, font_size = 25, text_font = F_BODY })
return p
end
local function buildConfirm()
local p = newPage()
if not p then return nil end
clockLabel(p)
local function goBack() show(pages[confirmFrom] or pages.home) end
backArrow(p, 167, goBack)
local t = pageTitle(p, '开始替换', 188, 124)
setClickable(t)
bind(t, goBack)
-- 多色文本不能用 recolor -> 拆成多个标签 + 手工算 x(宽度用真 MiSans 25px 量出)
lab(p, { x = 60, y = 93, w = 360, h = 36,
text = '替换图标包之前,请先确认自己', text_color = C_GRAY,
font_size = 25, text_font = F_BODY })
lab(p, { x = 60, y = 126, w = 360, h = 36,
text = '的系统版本号为 ', text_color = C_GRAY, font_size = 25, text_font = F_BODY })
lab(p, { x = 244, y = 126, w = 120, h = 36, text = '3.112.035',
text_color = C_WHITE, font_size = 25, text_font = F_BODY })
lab(p, { x = 356, y = 126, w = 40, h = 36, text = '!',
text_color = C_GRAY, font_size = 25, text_font = F_BODY })
lab(p, { x = 60, y = 196, w = 360, h = 36, text = '请确认自己的手表型号为',
text_color = C_GRAY, font_size = 25, text_font = F_BODY })
lab(p, { x = 60, y = 229, w = 372, h = 36, text = 'Xiaomi Watch S5 eSIM 46mm',
text_color = C_WHITE, font_size = 25, text_font = F_BODY })
lab(p, { x = 427, y = 229, w = 40, h = 36, text = '!',
text_color = C_GRAY, font_size = 25, text_font = F_BODY })
lab(p, { x = 60, y = 331, w = 360, h = 36, text = '搞机有风险,变砖后果自负。',
text_color = C_GRAY, font_size = 25, text_font = F_BODY })
local b = bigButton(p, '确认替换')
bindTap(b, function() startJob(pendingVariant) end)
return p
end
local function buildAbout()
local p = newPage()
if not p then return nil end
clockLabel(p)
backArrow(p, 198, function() show(pages.home) end)
local t = pageTitle(p, '关于', 220, 62)
setClickable(t)
bind(t, function() show(pages.home) end)
pcall(function()
p:Object { x = 54, y = 81, w = 373, h = 210, pad_all = 0,
bg_color = C_PLATE, border_width = 0, radius = 31 }
end)
if IMG.logo then
pcall(function() p:Image { src = IMG.logo, x = 200, y = 111 } end)
end
lab(p, { x = 170, y = 198, w = 141, h = 40, text = 'OS4 Icons',
text_color = C_WHITE, font_size = 29, text_font = F_ITEM })
lab(p, { x = 216, y = 234, w = 49, h = 28, text = '1.0.0',
text_color = C_GRAY, font_size = 21, text_font = F_VER })
listItem(p, 300, 'ic_qq', '732681995', nil)
return p
end
local function buildRunning()
local p = newPage()
if not p then return nil end
clockLabel(p)
runningTitle = pageTitle(p, '正在替换', 178, 124)
logLabel = lab(p, { x = 67, y = 93, w = 348, h = 277, text = '',
text_color = C_WHITE, font_size = 25, text_font = F_BODY })
rebootBtn = bigButton(p, '重启手表')
if rebootBtn then pcall(function() rebootBtn:add_flag(lvgl.FLAG.HIDDEN) end) end
bindTap(rebootBtn, function()
logOut('> 正在重启…')
sh("sync")
sh("reboot")
end)
return p
end
--------------------------------------------------------------------- 组装
local function build()
if built then return end
built = true
pages.home = buildHome()
pages.help = buildHelp()
pages.confirm = buildConfirm()
pages.about = buildAbout()
pages.running = buildRunning()
if pages.home then show(pages.home) end
clockTick()
pcall(function()
ui.timer = lvgl.Timer { period = 40, repeat_count = -1, cb = function() tick() end }
if ui.timer and ui.timer.resume then ui.timer:resume() end
end)
pcall(function()
ui.clockTimer = lvgl.Timer { period = 1000, repeat_count = -1,
cb = function() clockTick() end }
if ui.clockTimer and ui.clockTimer.resume then ui.clockTimer:resume() end
end)
end
local okbuild, errbuild = pcall(build)
if not okbuild then
pcall(function()
local r = lvgl.Object(nil, { x = 0, y = 0, w = W, h = H, bg_color = 0x000000,
bg_opa = 255, border_width = 0 })
r:Label { x = 20, y = 20, w = W - 40, h = H - 40,
text = 'BUILD FAILED:\n' .. tostring(errbuild),
text_color = '#ffffff', font_size = 13, text_font = F_BODY }
end)
end
ui.init = function()
pcall(build)
return ui.root
end
function pageOnPause()
if ui.timer then pcall(function() ui.timer:pause() end) end
if ui.clockTimer then pcall(function() ui.clockTimer:pause() end) end
end
function pageOnResume()
if ui.timer then pcall(function() ui.timer:resume() end) end
if ui.clockTimer then pcall(function() ui.clockTimer:resume() end) end
end
return ui