Commit graph

4 commits

Author SHA1 Message Date
2afdcf3d5c vc=32 fix: drop SearchItem.uploader_avatar — not on StreamInfoItem
I confused StreamInfo (the big single-video struct, has
uploader_avatars: ImageSet) with StreamInfoItem (the card struct used
in search results / channel video lists / related streams — no
uploader_avatars field). cargoBuildHost caught it: E0609 no field
`uploader_avatars`.

Drop the field from SearchItem (and from the Kotlin construction
sites). For the subs feed and "more from this channel" we already
use the channel-level avatar from ChannelInfo.avatar, which is the
right granularity anyway (every video from one channel shares one
avatar). Per-card uploader avatars on search/related stay null until
strawcore-core extracts them on StreamInfoItem too.
2026-05-25 12:38:50 -07:00
544035b30c vc=32: subs feed — dates, watched filter, infinite scroll, avatar fallback
Subscription feed is now actually a feed instead of a teaser.

Rust (strawcore wrapper)
  Added upload_date_relative and uploader_avatar to SearchItem so
  Kotlin can see both. strawcore-core already extracts upload_date
  relative ("2 days ago") on every StreamInfoItem and uploader_avatars
  on most — we were just throwing them away in from_core. Fixed.

StreamItem
  uploadDateRelative + uploaderAvatar fields added. Every construction
  site (search/channel/detail/feed) plumbs them through.

SubscriptionFeedViewModel
  Per-channel cap 5 → 30. With 30 subs that's up to 900 items in
  memory; ConcurrentHashMap entries are small enough.
  Sort by parsed relative recency (RECENCY_RE on the "N <unit> ago"
  string, signed seconds-ago, tied items break by viewCount).
  Opportunistic avatar backfill: every successful channelInfo fetch
  updates the stored ChannelRef.avatar via Subscriptions.updateAvatar
  when strawcore returns a non-null avatar — fixes the "I just subbed
  to a channel and the chip has no icon" case where the channel header
  parser missed the avatar at subscribe time but the feed-fetch
  layout returns one.

SubsPane (StrawHome)
  Hide-watched FilterChip (session-sticky). Cross-references
  History.watches by 11-char YT video ID; filters out anything you've
  already watched. "All caught up — nothing unwatched" empty state.
  Infinite scroll: PAGE_SIZE = 20. derivedStateOf-gated snapshotFlow
  watches the LazyListState's lastVisibleItem index; when within 5
  items of the bottom, bumps visibleCount by 20. "Loading more..."
  spinner at the bottom while there's more to show.
  Visible-count resets to PAGE_SIZE when the underlying list shrinks
  (refresh dropped items, filter just engaged).
  FeedRow now shows: uploader · views · "3 days ago".

SubChip
  Lettered fallback when ch.avatar is null. PrimaryContainer-tinted
  circle with the first letter — no more broken-image placeholder
  while the feed-fetch backfills the real avatar.

SubscriptionsStore
  updateAvatar(url, avatar) for the backfill path. Atomic via
  updateAndGet, persists to SharedPreferences.
2026-05-25 12:34:02 -07:00
467a5f10fa Phase 7 — strawcore wrapper now bridges to Sulkta-Coop/strawcore-core
Replaces the rustypipe-backed extraction with calls into the new
NPE-port crate. The UniFFI surface Kotlin sees is unchanged:

  suspend fun search(query: String): List<SearchItem>
  suspend fun streamInfo(input: String): StreamInfo
  suspend fun channelInfo(input: String): ChannelInfo
  fun initLogging()  // also wires the strawcore-core Downloader
  fun helloFromRust(name: String): String

rust/strawcore/
  * Cargo.toml      — dropped rustypipe + rquickjs-sys direct dep;
                      added strawcore-core path dep (../../../strawcore)
  * src/error.rs    — From<strawcore_core::ExtractionError>, mapping
                      ContentUnavailable variants to typed
                      StrawcoreError cases (AgeRestricted, GeoRestricted,
                      Private, RequiresLogin) instead of bucketing all
                      to Extractor
  * src/runtime.rs  — Once-guarded ReqwestDownloader init via
                      NewPipe::init_full
  * src/search.rs   — search() spawn_blocks core search_extractor::search
                      against SearchFilter::Videos
  * src/stream.rs   — stream_info() resolves URL → video_id via
                      strawcore_core::linkhandler::stream, then
                      spawn_blocks core stream_extractor::stream_info,
                      then maps StreamInfo → wrapper DTOs (combined/
                      video_only/audio_only/dash/hls)
  * src/channel.rs  — channel_info() parses input via
                      strawcore_core::linkhandler::channel (handle /
                      custom-url / legacy-user resolution lives in
                      core), then spawn_blocks core channel::channel_info

Build verified: wrapper compiles linking strawcore-core, uniffi-bindgen
generates Kotlin bindings with the same suspend fun + data class
surface Kotlin already consumes. Android NDK cross-compile + APK + on-
device smoke pending (needs crafting-table container).

This commits onto rollback/vc18-back-to-NPE — the existing Kotlin code
still calls NewPipeExtractor directly. Switching the Kotlin side to
consume the rust wrapper is a separate cutover.
2026-05-24 17:29:23 -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