2 Commits
6 changed files with 37 additions and 85 deletions
+12 -15
View File
@@ -19,22 +19,19 @@ Morocco (Ministry of Habous), matching AlAdhan `method=21`:
All computation happens **on the watch** — no GPS, no network. Pure solar math All computation happens **on the watch** — no GPS, no network. Pure solar math
(same Meeus approach as the sundial) lives in `src/common/prayer.js`. (same Meeus approach as the sundial) lives in `src/common/prayer.js`.
## Screen (v0.4) ## Screen (v0.6)
- Pure #000000 AMOLED background - Restores the exact native list markup, row styles and page logic from v0.3,
- Larger 50px clock; no swipe instruction label which the user confirmed scrolled smoothly on their watch
- Prayer names above their times, in fixed 104px native list rows - Removes ALL scroll handlers, zoom logic and dynamic font-style bindings
- Center magnification: prayer times grow continuously from 30px to 60px as - Fixed 28px prayer names and 36px times, side by side in 80px rows
their row approaches the physical screen center; names grow from 20px to 28px - Pure black background; enlarged 50px header clock; no swipe hint
- Scroll viewport 320×264 at x73/y101, centered at y233; 80px end spacers - Next-prayer countdown above the list; normal system back exits
- Fixed next-prayer countdown below the list; lavender next-prayer highlight
- Glyph sizes change without changing row heights, avoiding scroll-position drift
- 15s clock refresh preserves scroll focus; calculation logic unchanged
- Normal system back exits; crown support not assumed
Automated math/page/focus/layout tests and release build pass. The live scroll The user reported v0.4 and v0.5 lag during swiping (not only when stopping).
magnification still needs an on-watch check. `preview-0.4.png` is a static layout `tests/native-scroll.cjs` verifies parity with the smooth v0.3 baseline and
mock with illustrative times, not a device screenshot. absence of the zoom path. v0.6 tests/build pass; device smoothness still needs
confirmation. Old preview images document earlier releases, not v0.6.
## Project layout ## Project layout
@@ -60,7 +57,7 @@ npm run release
node tests/prayer.cjs node tests/prayer.cjs
node tests/page.cjs node tests/page.cjs
node tests/amoled.cjs node tests/amoled.cjs
node tests/focus.cjs node tests/native-scroll.cjs
``` ```
## Install ## Install
+2 -2
View File
@@ -2,8 +2,8 @@
"package": "com.ilias.salah", "package": "com.ilias.salah",
"name": "SALAH", "name": "SALAH",
"icon": "/common/icon.png", "icon": "/common/icon.png",
"versionName": "0.4", "versionName": "0.6",
"versionCode": 4, "versionCode": 6,
"minAPILevel": 1, "minAPILevel": 1,
"deviceTypeList": [ "deviceTypeList": [
"watch" "watch"
+8 -26
View File
@@ -6,11 +6,11 @@
<text class="bannerLabel">{{ nextName }} in</text> <text class="bannerLabel">{{ nextName }} in</text>
<text class="bannerTime">{{ countdown }}</text> <text class="bannerTime">{{ countdown }}</text>
</div> </div>
<list class="prayers" id="prayers" onscroll="onScroll"> <list class="prayers" id="prayers">
<list-item type="spacer" class="spacer"><div></div></list-item> <list-item type="spacer" class="spacer"><div></div></list-item>
<list-item type="prayer" for="{{ row in rows }}" tid="key" class="row {{ row.key === nextKey ? 'rowNext' : '' }}"> <list-item type="prayer" for="{{ row in rows }}" tid="key" class="row {{ row.key === nextKey ? 'rowNext' : '' }}">
<text style="{{ row.nameStyle }}" class="rowName {{ row.key === nextKey ? 'nameNext' : '' }}">{{ row.label }}</text> <text class="rowName {{ row.key === nextKey ? 'nameNext' : '' }}">{{ row.label }}</text>
<text style="{{ row.timeStyle }}" class="rowTime {{ row.key === nextKey ? 'timeNext' : '' }}">{{ row.time }}</text> <text class="rowTime {{ row.key === nextKey ? 'timeNext' : '' }}">{{ row.time }}</text>
</list-item> </list-item>
<list-item type="spacer" class="spacer"><div></div></list-item> <list-item type="spacer" class="spacer"><div></div></list-item>
</list> </list>
@@ -42,7 +42,6 @@ export default {
countdown: '', countdown: '',
nextKey: 'fajr', nextKey: 'fajr',
nextName: 'FAJR', nextName: 'FAJR',
scrollY: 0,
rows: [] rows: []
}, },
onInit() {}, onInit() {},
@@ -56,22 +55,6 @@ export default {
onDestroy() { onDestroy() {
clearInterval(this.timer) clearInterval(this.timer)
}, },
onScroll(event) {
if (!event || typeof event.scrollY !== 'number' || !isFinite(event.scrollY)) return
this.scrollY = Math.max(0, Math.min(520, event.scrollY))
this.applyFocus()
},
applyFocus() {
// Row heights never change: only glyph sizes change, avoiding scroll jumps.
const offset = this.scrollY || 0
this.rows = this.rows.map((row, index) => {
const focus = Math.max(0, 1 - Math.abs(index * 104 - offset) / 104)
return Object.assign({}, row, {
timeStyle: { fontSize: Math.round(30 + 30 * focus) + 'px' },
nameStyle: { fontSize: Math.round(20 + 8 * focus) + 'px' }
})
})
},
update() { update() {
const now = new Date() const now = new Date()
const hh = String(now.getHours()).padStart(2, '0') const hh = String(now.getHours()).padStart(2, '0')
@@ -93,7 +76,6 @@ export default {
passed: t[key] < nowMin, passed: t[key] < nowMin,
rowStyle: { width: ROW_WIDTHS[i] + 'px' } rowStyle: { width: ROW_WIDTHS[i] + 'px' }
})) }))
this.applyFocus()
} }
} }
</script> </script>
@@ -101,15 +83,15 @@ export default {
.page { width: 466px; height: 466px; background-color: #000000; } .page { width: 466px; height: 466px; background-color: #000000; }
.city { position: absolute; top: 12px; left: 103px; width: 260px; height: 26px; color: #aaa5b5; font-size: 20px; text-align: center; } .city { position: absolute; top: 12px; left: 103px; width: 260px; height: 26px; color: #aaa5b5; font-size: 20px; text-align: center; }
.clock { position: absolute; top: 35px; left: 103px; width: 260px; height: 56px; color: #ffffff; font-size: 50px; text-align: center; } .clock { position: absolute; top: 35px; left: 103px; width: 260px; height: 56px; color: #ffffff; font-size: 50px; text-align: center; }
.banner { position: absolute; top: 383px; left: 53px; width: 360px; height: 36px; background-color: #000000; flex-direction: row; justify-content: center; align-items: center; } .banner { position: absolute; top: 104px; left: 53px; width: 360px; height: 36px; background-color: #000000; flex-direction: row; justify-content: center; align-items: center; }
.bannerLabel { color: #c5a3ff; font-size: 24px; margin-right: 8px; } .bannerLabel { color: #c5a3ff; font-size: 24px; margin-right: 8px; }
.bannerTime { color: #ffffff; font-size: 26px; font-weight: bold; } .bannerTime { color: #ffffff; font-size: 26px; font-weight: bold; }
.prayers { position: absolute; top: 101px; left: 73px; width: 320px; height: 264px; flex-direction: column; background-color: #000000; } .prayers { position: absolute; top: 146px; left: 73px; width: 320px; height: 240px; flex-direction: column; background-color: #000000; }
.spacer { width: 320px; height: 80px; background-color: #000000; } .spacer { width: 320px; height: 80px; background-color: #000000; }
.row { width: 320px; height: 104px; background-color: #000000; flex-direction: column; justify-content: center; align-items: center; padding-left: 10px; padding-right: 10px; } .row { width: 320px; height: 80px; background-color: #000000; flex-direction: row; justify-content: space-between; align-items: center; padding-left: 10px; padding-right: 10px; }
.rowNext { border-width: 1px; border-color: #a779ef; border-radius: 18px; } .rowNext { border-width: 1px; border-color: #a779ef; border-radius: 18px; }
.rowName { width: 298px; height: 30px; text-align: center; color: #d0cbd6; font-size: 28px; } .rowName { color: #d0cbd6; font-size: 28px; }
.nameNext { color: #c5a3ff; font-weight: bold; } .nameNext { color: #c5a3ff; font-weight: bold; }
.rowTime { width: 298px; height: 70px; text-align: center; color: #ffffff; font-size: 60px; } .rowTime { color: #ffffff; font-size: 36px; }
.timeNext { color: #c5a3ff; font-weight: bold; } .timeNext { color: #c5a3ff; font-weight: bold; }
</style> </style>
-40
View File
@@ -1,40 +0,0 @@
const assert = require('node:assert/strict')
const fs = require('node:fs')
const vm = require('node:vm')
const src = fs.readFileSync('src/pages/index/index.ux', 'utf8')
const body = src.match(/<script>([\s\S]*?)<\/script>/)[1].replace(/import\s+\w+\s+from\s+'[^']*';?/, '').replace('export default', 'globalThis.spec =')
const ctx = {prayer: require('../src/common/prayer.js'), setInterval: () => 1, clearInterval: () => {}}
vm.createContext(ctx); vm.runInContext(body, ctx)
const p = Object.assign({}, ctx.spec, JSON.parse(JSON.stringify(ctx.spec.private)))
p.update()
assert.equal(typeof p.onScroll, 'function', 'scroll handler drives center magnification')
const size = i => parseFloat(p.rows[i].timeStyle.fontSize)
p.onScroll({scrollY: 0})
assert.equal(size(0), 60, 'first centered prayer gets 60px time')
assert.ok(size(1) < size(0))
p.onScroll({scrollY: 104})
assert.equal(size(1), 60)
assert.equal(size(0), size(2), 'symmetric shrink around center')
p.onScroll({scrollY: 52})
assert.equal(size(0), size(1), 'continuous interpolation between rows')
assert.ok(size(0) > 30 && size(0) < 60)
p.onScroll({scrollY: 520})
assert.equal(size(5), 60, 'last prayer can be magnified')
p.update()
assert.equal(size(5), 60, 'clock refresh preserves scroll focus')
for (const offset of [-100, 9999]) {
p.onScroll({scrollY: offset})
p.rows.forEach(row => assert.ok(parseFloat(row.timeStyle.fontSize) >= 30 && parseFloat(row.timeStyle.fontSize) <= 60))
}
const before = JSON.stringify(p.rows)
for (const event of [undefined, {}, {scrollY: NaN}, {scrollY: Infinity}]) p.onScroll(event)
assert.equal(JSON.stringify(p.rows), before, 'invalid events do not corrupt styles')
assert.ok(!src.includes('SWIPE TO SCROLL'))
assert.match(src, /onscroll="onScroll"/)
assert.match(src, /style="\{\{ row.timeStyle \}\}"/)
assert.match(src, /style="\{\{ row.nameStyle \}\}"/)
function css(c,k) { return parseFloat(src.match(new RegExp('\\.'+c+'\\s*\\{([^}]+)'))[1].match(new RegExp(k+':\\s*([^;]+)'))[1]) }
assert.equal(css('prayers','top') + css('prayers','height') / 2, 233, 'magnification center is physical screen center')
assert.equal(css('row','height'), 104, 'fixed row geometry prevents scroll feedback')
assert.ok(css('clock','font-size') > 42, 'header clock enlarged too')
console.log('Center magnification, interpolation, end rows, refresh preservation, invalid events and wiring passed')
+15
View File
@@ -0,0 +1,15 @@
const assert = require('node:assert/strict')
const fs = require('node:fs')
const {execFileSync} = require('node:child_process')
const src = fs.readFileSync('src/pages/index/index.ux','utf8')
const baseline = execFileSync('git',['show','8e660f4:src/pages/index/index.ux'],{encoding:'utf8'})
assert.ok(!/onscroll|onScroll|applyFocus|timeStyle|nameStyle|_scrolling|_scrollY/.test(src), 'remove the entire scroll-driven zoom path')
assert.equal(src.match(/<list\b[\s\S]*?<\/list>/)[0], baseline.match(/<list\b[\s\S]*?<\/list>/)[0], 'native list markup matches user-confirmed smooth v0.3')
assert.equal(src.match(/<script>[\s\S]*?<\/script>/)[0], baseline.match(/<script>[\s\S]*?<\/script>/)[0], 'page logic matches smooth v0.3')
for(const name of ['prayers','spacer','row','rowName','rowTime','rowNext','nameNext','timeNext']) {
const re = new RegExp('\\.'+name+'\\s*\\{[^}]+}')
assert.equal(src.match(re)[0],baseline.match(re)[0],name+' uses proven static styling')
}
assert.ok(!src.includes('SWIPE TO SCROLL'))
assert.match(src,/font-size: 50px;/,'keep enlarged header clock')
console.log('Exact v0.3 list, row styles and script parity verified; zoom code absent; larger clock and no hint retained')
-2
View File
@@ -94,8 +94,6 @@ function page() {
p.onHide = spec.onHide p.onHide = spec.onHide
p.onDestroy = spec.onDestroy p.onDestroy = spec.onDestroy
p.update = spec.update p.update = spec.update
p.applyFocus = spec.applyFocus
p.onScroll = spec.onScroll
p.rows = [] p.rows = []
return p return p
} }