Commit graph

12 commits

Author SHA1 Message Date
42cb945654 Public-flip audit: scrub audit-ticket prefixes + LAN refs + tighten README
URLs → git.sulkta.com. Audit-ticket prefixes (SPEC §N, audit Track X, vc=N
audit-fix, FIX (audit ...), PORT DEVIATION) stripped from comments — technical
reasoning retained. Crafting-table LAN refs softened to 'Sulkta build host'.
README sheds marketing scaffolding + stale status tables.
2026-05-27 13:29:53 -07:00
ccd24c4ed3 vc=55: in-app auto-updater polling fdroid.sulkta.com
WorkManager periodic worker hits the repo's index-v2.json, parses
the highest versionCode for our package, compares with
BuildConfig.VERSION_CODE. When newer: posts a notification with
ACTION_VIEW on the APK URL — Android's DownloadManager picks it up
and the system installer takes over. No INSTALL_PACKAGES perm
needed.

Settings:
- Check for updates toggle (default on — closing NewPipe's silent
  staleness gap is the explicit motivation)
- Interval picker (1h / 6h / 24h, default 6h — WorkManager has a
  15-min periodic floor anyway)
- Last-checked timestamp + 'update available' tag when caught-up
  state is dirty
- Check now button — runs the same path as the worker so behaviors
  stay identical

Cold start fires one check too so users see pending updates without
waiting a full interval.

R8 keep-rule for UpdateCheckWorker added — WorkManager instantiates
workers by name via reflection.
2026-05-26 09:40:07 -07:00
ebe1fc8464 vc=52: R8 enabled + surface-handoff polish
R8 (minify + resource-shrink) flipped on for BOTH debug AND release
variants — we publish the debug APK to fdroid (per existing
pipeline), and the audit-flagged Log.d strip discipline required R8
to actually run on the variant we ship.

New strawApp/proguard-rules.pro covers:
  * UniFFI bindings (uniffi.strawcore.*) — reflective FFI dispatch
    from Rust side, must survive minification
  * JNA — Library subclasses reflectively loaded by name
  * kotlinx-serialization @Serializable — generated $$serializer
    companions, kept via both the package-anchored rule and the
    annotation-wildcard rule for belt + suspenders
  * Media3 session Parcelables (cross-process via Binder)
  * Compose runtime + Strawcore exception hierarchy

Surface-handoff polish on inline ↔ fullscreen transitions:
  setKeepContentOnPlayerReset(true) on both PlayerViews (inline in
  VideoDetail + fullscreen Player). When the detaching view's player
  is nulled on dispose, it holds the last rendered frame instead of
  flashing black. The receiving view's surface takeover then renders
  the next frame without the ~1-frame black gap. Round-4 audit
  HIGH-5 was the closest writeup.

Expected APK-size win from R8: ~30-40%. Need real-device
verification post-install — the keep rules are best-effort and a
missing rule manifests as runtime ClassNotFoundException or
silently-broken kotlinx-serialization decoding.
2026-05-26 08:43:06 -07:00
885398e3bd vc=26: look + feel pass — sulkta.com palette + Material Icons
Color palette pulled directly from sulkta.com's stylesheet — the same
greens used on the website now drive the app theme:
  #166534  deep green   (light-theme primary, top app bar background)
  #4ade80  bright lime  (dark-theme primary, accents in dark mode)
  #86efac  light green  (primaryContainer in light theme)
  #e8f5e8  pale green   (secondary container tint)
  #d97706  amber accent (tertiary)
  #374137  olive gray   (secondary on light, container on dark)

Replaces the made-up forest palette from vc=23 with the real Sulkta
brand. Same M3 tonal-role mapping so derived surfaces stay consistent.

TopAppBar redone NewPipe-style: solid deep-green bar with white
"straw" title, white hamburger + search icons. Clear bold header
instead of the previous white-with-a-pill-underneath layout.

Material Icons swapped in everywhere we had emoji:
  drawer        Person / History / PlaylistPlay / Download / Settings
  minibar       PlayArrow / Pause / Close
  fullscreen    Speed / Headphones / Videocam / Share /
                PictureInPictureAlt / KeyboardArrowDown
Pulled in material-icons-extended (4 MB APK growth, all icons).
Consistent renders across vendors; no more emoji font fallback drift.

FeedRow gets a NewPipe-style duration pill burned into the bottom-right
of every thumbnail (mm:ss / h:mm:ss). Live streams / mixes with no
duration leave it off.

Audit deferred-MED items addressed:
  MED-6: dropped the PlayerService STATE_ENDED auto-stop. Service
  shutdown is now driven only by onTaskRemoved + the minibar's ×.
  Removes the implicit "we'll never queue" assumption and is correct
  for a future autoplay/queue feature.
  LOW-7: DownloadsScreen adaptive poll — 1s while a download is
  active, 5s when idle. No more wasted DB queries when nothing
  is running.
2026-05-25 17:46:23 +00:00
e80fa4252c Clean cutover — Kotlin off NewPipeExtractor, onto uniffi.strawcore
Brings the Phase U Android-side integration onto a feature branch
where the rust wrapper now bridges to strawcore-core (the new NPE
port) instead of rustypipe.

  * strawApp/build.gradle.kts — JNA dep + cargoBuild + cargoBuildHost
    + uniffiBindgen + the merge/compile task wiring. libs.newpipe
    .extractor dropped.
  * StrawApp.kt — uniffi.strawcore.initLogging(); NewPipe.init() gone
  * 5 ViewModels (Search/VideoDetail/Player/Channel/Subscriptions)
    swapped to call uniffi.strawcore.{search,streamInfo,channelInfo}
  * PlayerScreen + PlaybackService adjusted for the new StreamInfo
    shape (dash_mpd_url / hls_url instead of NPE's manifests)
  * IosSafeHttpDataSource carried forward — strawcore-core gives us
    Android-primary URLs so the iOS cap path is mostly dead code,
    but kept as belt-and-suspenders for the rare HLS-fallback
  * NewPipeDownloader.kt + util/Thumbnails.kt deleted

Files taken wholesale from sulkta branch — the wrapper's UniFFI
surface is identical between sulkta (rustypipe-backed) and the
current Phase-7-bridged-to-strawcore-core code, so Kotlin doesn't
care which extractor is under the hood.
2026-05-24 17:54:41 -07:00
9ad3302f52 v0.1.0-X (vc=12): revert to NewPipeExtractor for working playback
Phase U (rustypipe Rust extractor) rolled back. Symptom: black screen
on play, root cause: rustypipe 0.11.4's JS deobfuscator can't parse
current YouTube player.js (YT changed the obfuscation pattern, no
upstream rustypipe release since June 2025). Switching clients
(Web → TV → Android/Ios) didn't help — the deobfuscator init fires
universally.

Kept in place for the future:
- rust/strawcore/ Cargo workspace + UniFFI scaffolding
- crafting-table runtime install (rustup + 4 Android targets +
  cargo-ndk + NDK r27c)
- The U-2..U-5 commits in history (re-runnable when rustypipe is
  fixed or we fork it).

Restored from commit 9550b207a (v0.1.0-T):
- NewPipe.init() in StrawApp.onCreate
- libs.newpipe.extractor + libs.squareup.okhttp deps
- NewPipeDownloader.kt + Thumbnails.kt
- ViewModels (Search/VideoDetail/Player/Channel/SubscriptionFeed) on
  NewPipeExtractor calls
- VideoDetailScreen Download dialog using NewPipe's StreamInfo

Future-direction memo: openclaw-workspace/memory/project_rustypipe_fork.md
— fork plan + revival path for the Rust extractor when we're ready
to maintain it.

Verified working in the Android emulator: dQw4w9WgXcQ plays, ExoPlayer
reports state=PLAYING(3), position advancing, video surface rendering.
2026-05-24 09:54:59 -07:00
a13896f5e9 v0.1.0-W (vc=10): U-4 + U-5 — channels via rustypipe + rip NewPipeExtractor
channel_info(url) UniFFI suspend fn. ChannelViewModel +
SubscriptionFeedViewModel both swap. NewPipeExtractor (Java) is OUT —
zero org.schabi.newpipe classes in the APK now.

Cleanup:
- NewPipeDownloader.kt deleted (was the OkHttp adapter)
- Thumbnails.kt deleted (rustypipe returns full URLs)
- NewPipe.init() dropped from StrawApp.onCreate
- libs.newpipe.extractor removed from build.gradle.kts
- STRAW_USER_AGENT + strawHttpClient() now live in net/Http.kt
- RydClient + SponsorBlockClient + PlayerScreen + PlaybackService all
  read from net/Http.kt instead of the extractor package

rustypipe API quirks beat:
- channel_videos(id) is the right method (channel() doesn't exist)
- ChannelInfo struct = basic metadata; Channel<T> wrapper carries
  name/avatar/banner + .content is the paginator of videos
- description is String (not Option), subscriber_count is Option<u64>

End state: strawApp Kotlin is ~UI + thin glue to strawcore. The Rust
core handles search / streamInfo / channel / channel_videos via UniFFI
suspend fns. Tokio + reqwest + rustls + rquickjs all packed in
libstrawcore.so (~6MB per ABI). APK 40MB total.
2026-05-24 09:11:14 -07:00
7ff5ac79e5 v0.1.0-U (vc=8): Phase U-1 + U-2 — Rust core + rustypipe search
NewPipeExtractor (Java) → strawcore (Rust) migration begins. Phase U:
- U-1: Rust toolchain + UniFFI smoke test
- U-2: rustypipe search via uniffi suspend fun, SearchViewModel swapped

What landed:
- rust/strawcore — UniFFI-exported Rust crate using proc-macros.
  Builds for arm64-v8a + armeabi-v7a + x86 + x86_64 via cargo-ndk.
  Tokio multi-thread runtime singleton drives rustypipe's async API.
- strawApp/build.gradle.kts — cargoBuildHost + cargoBuild + uniffiBindgen
  Gradle Exec tasks chained into the Android build. Generated Kotlin
  bindings land in src/main/java/uniffi/strawcore/ (gitignored).
- SearchViewModel.kt — calls uniffi.strawcore.search(query) directly.
  NewPipeExtractor still in deps for VideoDetail/Player/Channel paths;
  those move to Rust in U-3 / U-4.
- Build chain quirks beat:
  * cargo absolute path in Exec tasks (PATH wasn't propagating)
  * uniffi-bindgen needs UNSTRIPPED host .so — separate cargoBuildHost
    builds a debug-profile host lib to read metadata from
  * rustypipe rustls-tls-webpki-roots avoids the openssl-sys
    cross-compile tarpit
  * rquickjs-sys 'bindgen' feature opted in (no prebuilt Android
    bindings ship; crafting-table has libclang 14)
- crafting-table runtime install (until Dockerfile catches up):
  rustup + 4 Android targets + cargo-ndk + NDK r27c. Persists in
  /caches/cargo + /caches/android-sdk via the volume mount.

APK size: 22MB (U-1) → 37MB (U-2). libstrawcore.so 3-5MB per ABI carries
rustypipe + reqwest + tokio + rustls + rquickjs. NewPipeExtractor still
in for now (still drives detail + player + channel + feed), so the
Java half is doubled up. U-5 removes it.
2026-05-24 08:36:50 -07:00
Kayos
7894fe5a4d Straw phase M-2: MediaSession + audio focus + service skeleton
Wraps the existing Activity-owned ExoPlayer in a Media3 MediaSession. Side
effects:
- Lock-screen media controls (play/pause)
- System media notification with play/pause buttons
- Audio focus + ducking (other audio dims when straw plays)
- Bluetooth + headset hardware-button routing
- Plays nicely alongside other media apps' notifications

The ExoPlayer is also configured with AudioAttributes (USAGE_MEDIA +
CONTENT_TYPE_MOVIE) and handleAudioFocus=true so other apps can request
focus correctly (e.g., a phone call ducks/pauses straw).

Also scaffolded but NOT yet wired:
- PlaybackService extending MediaSessionService — the foundation for
  true background-after-Activity-kill audio. Manifest entry + deps
  added (media3-session 1.4.1, concurrent-futures-ktx 1.2.0). The
  Activity-to-Service migration via MediaController is M-3 work.
- POST_NOTIFICATIONS, FOREGROUND_SERVICE,
  FOREGROUND_SERVICE_MEDIA_PLAYBACK, WAKE_LOCK permissions in manifest.

For the user right now: lock the phone while a video plays — media
controls appear on the lock screen. Open another app — notification with
play/pause sits in the shade. Press a headset's play/pause — straw
responds. Pull the home screen — PiP keeps the video floating + audio
continues. Full screen-off-background-audio after killing the activity
arrives in M-3.
2026-05-23 20:46:47 -07:00
Kayos
ce3ba9afa2 Straw phase H: Settings screen + SponsorBlock category toggles
New: SettingsStore (SharedPreferences-lite, same pattern as HistoryStore)
exposes a Set<SbCategory> StateFlow.

7 SponsorBlock categories surfaced as toggle rows in a new Settings screen:
sponsor (on by default), selfpromo, intro, outro, interaction (reminders),
music_offtopic (talking in music videos), filler.

Wiring:
- StrawApp.onCreate: Settings.init(this)
- StrawHome: new Settings gear icon next to "straw v0.1.0" header. Tap
  routes to Screen.Settings.
- PlayerViewModel.resolve: reads Settings.get().sbCategories on resolve.
  Empty set = SponsorBlock skip disabled (no API call).

Material-icons-core dep added (1.7.5) for the Icons.Filled.Settings glyph.

Day-4 ideas: theme override, default audio-only playback, preferred quality,
clear history buttons.
2026-05-23 19:51:43 -07:00
Kayos
496ed30bda Sulkta day-2: search → detail → player → SponsorBlock + RYD
Phase A: NewPipeExtractor + OkHttp Downloader wired in. Search bar +
LazyColumn results. Tap = navigate to detail.

Phase B: VideoDetail screen — StreamInfo metadata + Return YouTube
Dislike chips + description.

Phase C: Media3 ExoPlayer in Compose. Resolves StreamInfo to best
playable: DASH MPD → HLS → combined progressive → merged
videoOnly+audio.

Phase D: SponsorBlock SHA-256 prefix lookup. 250ms position-poll loop
inside PlayerScreen — exoPlayer.seekTo(segment.end) when entering
a sponsor segment. Toast on skip.

Phase E: Verified live on Android 14 emulator. linus tech tips search
returns real results with thumbnails; tapped result opens detail;
hit Play → video plays through ExoPlayer.

Architecture: everything in :strawApp for now (not pushed into :shared
yet — KMP refactor is day-3). Pure-state nav (sealed Screen + stack,
no nav library).

Known polish gaps (day-3): RYD chips render empty on some videos,
description has raw HTML (markdown render needed), no Koin DI yet,
no persistence.

GPL-3.0-or-later per upstream NewPipe.
2026-05-23 19:22:52 -07:00
Kayos
ff4dc6f121 Sulkta day-1: straw — KMP/Compose YouTube client fork
Initial Sulkta fork of NewPipe with a new :strawApp module that ships a
clean Compose-based Android APK at applicationId com.sulkta.straw.

What's here:
- :strawApp — thin Android application shell, MaterialTheme + StrawHome
  Composable. Lives alongside legacy :app so we don't break upstream.
- buildSrc — STRAW_APPLICATION_ID/VERSION constants alongside the existing
  NEWPIPE_APPLICATION_ID_OLD/NEW.
- docs/sulkta — RECON.md (NewPipe codebase breakdown) + DECISIONS.md
  (stack + scope decisions).

NewPipe's :shared KMP scaffold is at 892 LOC and renders nothing; this
fork picks up there and races ahead. Day-1 ships a hello APK; day-2 wires
NewPipeExtractor + Media3 player + SponsorBlock + Return YouTube Dislike.

GPL-3.0-or-later per upstream.
2026-05-23 17:37:55 -07:00