- size search, records and following lists from the available width instead of a fixed three columns, so narrow tablets and landscape phones drop to two
- merge the follow schedule into a single line when a weekday filter is active, showing today or the picked weekday plus the update hour
- drop the nav scrim on rail layouts and reduce it to the phone-only bottom band
- move records and following onto lazy grids so item animations stay per card and delete keeps a single row moving
- pass the card source key into the folder page, so a netdisk folder no longer loads the home source latest list
- switch the folder page to singleTop with an internal level stack, so drilling in no longer creates one activity per level
- rebuild the list loader on every level change to drop late responses from the abandoned level
- restore the entry mode and static title when returning to the root level
- reset the grid scroll position when the level changes
- add PartitionNavStack with unit tests for push, pop and root detection
- open .py/.js sources via ACTION_VIEW and save them to the vod list without switching
- name picked sources after the file instead of the clan path
- cancel in-flight config and jar fetches so switching never waits on the abandoned source
- keep load epoch guards on jar retries and on config write-back
- show http status in the terminal playback tip and allow slow opens a grace window
- keep the current source when the detail returns no playable episode or an empty payload, only fall back after playback failure, stale subscription entries or a manual switch
- retry the same vod id once before declaring no-line or empty, so a truncated mirror response is not read as a dead source
- limit auto fallback candidates to changeable sites and keep non-changeable ones in the switch-source chips
- log detail body length and head to tell transport truncation from a parse miss
auto retry falls back soft->hard as well as hard->soft with one switch per ladder and skips when the hardware target is already in use; params sheet refreshes after kernel release so the decode row matches the new tier; continuous corner shapes for player sheets, bottom sheets and menus; new hard decode fallback tip in three locales
- iso: nested BDMV lookup, 4-thread mpls fetch with serial fallback, probe via Content-Range with stub guard, deferred volume snapshot, read/stream diagnostics with bounded log volume
- ui: douban hot search referer plus parse-guarded cache with self-heal, liquid glass defaults blur 5 / distortion 30 / translucency 50%, theme preview capsule mockup with continuous corners and dot-only nav row, fixed-height vertical nav rail with stride derived from slot count
- move quality chips from detail page into player episode sheet
- gate capsule press morph behind pressMorph flag on records, home poster and player params
- clear focus on blank tap, drag and ime dismiss in config manage page
- animate search rail site filter and align search field font weight
- boot guard, kv key spec and logging tweaks
- skip hard->soft retry when the software candidate equals the current codec
- only applies when the current decoder is already software, so hardware paths stay unchanged
- compare codec names first and fall back to the actual decode kind when unavailable
- add unit tests for the skip decision edges
- player: params sheet decode selection follows the actual codec in use
- player: keep codec stats across kernel reuse, clear only on kernel release
- player: sync global decode default on line switch with per-title lock intact
- js: add net.session and cookie jar, http gate, html query, open stream and proxy stream
- net: add proxy header policy; tests for decode display and js net pieces
- toast once per launch when a spider module opens the all files access page, suppressed for host pickers and permission requests
- log echo-storage for the notice and whitelist the prefix in the file logger
- home poster defaults to the plain layout on screens from 600dp, phones keep immersive
- extract resolveHomeLayoutVertical and cover the default and stored-choice rules with unit tests
- gradle: enable jniLibs.useLegacyPackaging, release apk 43.3 -> 27 MiB, version 1.2.6/30 -> 1.2.7/31
- changelog: add the v1.2.7 section
- notify: set GH_REPO for the release fetch step, gh cannot resolve the repo without a checkout so
the notification used to degrade to a text-only message silently; print the gh error instead of
swallowing it
- release: pass GH_TOKEN to the notify workflow
- tmdb: map episodes by site episode numbers (no season hint, single regular season, unique numbers within season count) with a cache-key fingerprint, fallback to ordered mapping; episode thumbnails badge the site number
- tmdb: tvOnly guard keeps titles carrying a season hint on tv results across pickBest, bestHit, alias cache and lookup paths
- tmdb: alt-name fallback via apple cms vod_en, threaded through beans, nameCandidates, resolve and cache entries, hero, detail sections, episode row and list cards
- player: surface decode-capability-exceeded errors as a dedicated terminal message and cancel the play timeout on retry and error paths
- osd: rebuild the audio codec probe with live/predicted source tracking and extend the decoder guess chain
- detail: hero description collapses to three lines by default
- tests: episode mapping, tv guard, alt-name resolution and serialization regressions; sync the mobile ui spec
- player: keep fullscreen action icons in the bottom bar on all portrait screens
- player: scale bottom-bar action icons with the mm system to match the transport row
- detail: align follow/tmdb sheet backgrounds with the app theme
- detail: drop the line chips row from the portrait episode sheet
- detail: bump hero circle buttons to 50dp and add cast/genre section icons
- danmu: PlayerDanmakuView subclass enables non-block mode and disables idle sleep; vsync extrapolated danmu clock with rebase, retune and drift tracking in MyVideoView
- danmu: move the danmaku layer into the player container, tune the load controller and default alpha
- controls: PlayerSlideVisibility slides by measured content height, replacing the fixed-offset AnimatedVisibility wiring in top bar and overlay
- picture: remove the obsolete restart-required hint and the RestartRequired reason, sync three locales, tests, spec and history
- tmdb: fetch and cache logo artwork with language and vote rank, fallback to text title after timeout
- hero: split fixed image area from content column, add rating line and collapsible overview
- hero: scroll parallax scale and fade, pull-down stretch, status bar icon switch when hero scrolls out
- detail: add cast and genres sections after sources, drop related section, add detail_genres strings
- tests: scroll progress, rating note and tmdb logo parsing; sync mobile ui spec
refresh vodRecord.updateTime on pause/stop/completion via HistoryWriter.touch, gated by the WatchProgressStore.save result and the new canRecord; a missing row stays a no-op so deleted entries never revive
touch writes updateTime only and never the session preview's dataJson, keeping reverseSort/note/year of stored rows
flush progress and history time before engine release/releasePlayer/enterLive; sampler.flushNow carries a main-thread guard
jianpian: guard-early-return cleanup
- data: suspend-only dao and repository contracts, allowmainthreadqueries removed (r1/r2/r4)
- boot: dns-over-https client, thunder cache cleanup and quickjs native load moved off the main thread (oncreate main thread ~55ms -> ~44ms)
- probe: debug-gated step/trace/measure instrumentation for boot and frame kv cost
- player: portrait top bar actions relocated above the seek bar and evenly spread, title no longer clipped
- strictmode: detectnetwork and vmpolicy added, penaltydeath rolled back to penaltylog
- epg: parse on the okhttp callback thread, background asset load with empty-map fallback
- tests: data layer suspend contract, watch progress queue order, epg parser
- 974 `!!` removed across app/src/main/java, app/src/python/java and the quickjs wrapper (126 files, +1489/-1186)
- replacements: null guards, hoisted locals, requireNotNull; the few ?. cases are display/log-only and registered in the batch record
- no signature, visibility, field-type or nullability changes; NPE -> IllegalArgumentException at the same call sites, all inside pre-existing catch blocks
- rule + gate registered: SKILL.md null-safety line and the gate step, avbox-code-review-spec.md gains the null-safety section
- also carries the pending PyLog release log-strip keep rules (proguard-rules.pro + companion marker)
Verified: :app:assembleDebug green; :app:testDebugUnitTest 953 passed / 0 failed / 1 skipped; null_assert_gate.py PASS (only the 2 vendored L5 exemptions remain)
- migrate the 4 Java files under quickjs/src/main/.../wrapper to Kotlin
(ContextSetter, Function, JSUtils, UriUtil); the module now has 0 Java
- clarify that quickjs/ is not vendored code: the real third-party part is the
wang.harlon.quickjs maven dependency (wrapper-java 3.2.3, 21 classes), which
cannot be migrated because the native side matches it by name; only the 4
locally written files were converted
- keep the bytecode contract identical: UriUtil keeps its 4 public statics and
JSUtils keeps isEmpty/isNotEmpty/toJsonObject/toJsonArray plus the instance
methods, all with unchanged descriptors; the only rename is
toArray(byte[]) -> toByteArray (no call site uses it)
- keep @Retention(RUNTIME) on both annotations explicit, and restore the AOSP
and caoccao Apache-2.0 headers the port had dropped
- add UriUtilTest (13 cases) written against the Java version first, so the
Kotlin port is proven equivalent by tests rather than by inspection;
resolveToUri stays untested because Uri.parse returns null under Robolectric
- quickjs: add the kotlin.android plugin and set jvmTarget to 21
- full :app:testDebugUnitTest is 904 cases / 0 failures / 1 skipped
- record the rules and pitfalls in the Kotlin migration spec (section 7.24)
- migrate 11 test files / 1241 lines from Java to Kotlin, pure language change
with no logic edits: ConfigParser, Depot, VodInfoReverse, EpisodeMatcher,
FileUtilsNativeLibRepair, SearchHelper, PlaybackAttemptState,
SubtitleFilePicker, BootGuard, KVKeySpec, KVDecoder
- keep the test count of every class identical (139 cases in total); full
:app:testDebugUnitTest stays at 891 cases / 0 failures / 1 skipped
- app/src now has 0 Java; the only remaining Java lives in the vendored
quickjs wrapper module
- fix the stale "app/src/test is out of migration scope" row and record the
test-side conversion rules in the Kotlin migration spec
- player: remove the per-key 500ms click throttle entirely, delete the dead
FastClickCheckUtil; play/pause now drives the kernel without any guard
- player: add zero-ripple press elasticity for all overlay icons (transport,
top bar, side rotate/lock) and a spring-driven seek thumb
- player: add enter/exit animations for bars, hints, side buttons and info OSD,
unified at 120ms; exit really leaves the composition tree
- player: fix the top bar stuck below the top edge by keeping the scrim out of
the content flow (Box instead of Column)
- player(osd): show audio decode kind and the real decoder name, read from
MediaCodecRenderer.getCodecInfo() with a MediaCodecList fallback
- player: unify the hard/soft decode rule into PlayerHelper.decodeKindOf
- records: reduce the gap between the segmented control and the first card
to 12dp (history + collect)
- chore: bump version to 1.2.3-alpha03 (versionCode 26) and add changelog entry
- docs: update the UI spec and the history archive