Commit graph

295 commits

Author SHA1 Message Date
KhooLy
d88e4226da Wire AppleAddonStoreDataSource to a real Swift-driven state source
Adds AppleAddonStoreSnapshot/AppleInstalledAddonSnapshot and update()
so Swift can push real addon list/error/added-name state back into
the shared Addon Store screen, plus
FluxaApple.setAddonStoreActionHandler/updateAddonStore following the
existing handler-setter pattern.
2026-07-20 15:05:28 +03:00
KhooLy
6b218ba97f Track enabled/disabled addon state and respect it when fetching content
FluxaAppleAddonConfigurationStore gains a disabled-addon set and
enabledAddonUrls(); Home/Search/Discover/Detail now fetch from
enabledAddonUrls() instead of the raw configured list, so disabling an
addon in the store actually stops it from being queried.
2026-07-20 15:05:25 +03:00
KhooLy
2e95f6b48d Expose addon display metadata from AppleStremioBridge manifest parsing
Adds name/description/logo/version/configurable to
AppleAddonManifestSnapshot so the iOS Addon Store can show real addon
metadata instead of just catalog capability flags.
2026-07-20 15:05:23 +03:00
KhooLy
f72b6f1623 Wire Plugins settings screen to real networking on iOS
FluxaApplePluginsStartup routes ApplePluginsActionSnapshot actions
from the shared UI to FluxaApplePluginRepositoryManager and pushes
merged state back to Kotlin. FluxaIosApp constructs and registers it
alongside the other feature startups, restoring persisted repos on
launch.
2026-07-20 14:43:50 +03:00
KhooLy
c35dd1148f Wire ApplePluginsDataSource to a real Swift-driven state source
Adds ApplePluginsSnapshot and update() so Swift can push merged
repository/scraper/settings-sheet state back into the shared Compose
UI, plus FluxaApple.setPluginsActionHandler/updatePlugins following
the existing setSearchHandler/updateSearch pattern. Also fixes
saveScraperSettings to actually forward the settings values.
2026-07-20 14:43:48 +03:00
KhooLy
270d82d6cd Add FluxaApplePluginRepositoryManager owning a dedicated plugin engine
Mirrors Android's PluginRepositoryManager: owns its own headless
runtime instance, dispatches repo/scraper actions, persists state via
UserDefaults, and exposes scraper settings layout and execution.
2026-07-20 14:43:43 +03:00
KhooLy
2cfc819951 Add iOS headless-effect handler for fetchPluginManifest
Fetches plugin repository manifests over URLSession and parses them
via coreInvoke, matching Android's FluxaAndroidHeadlessEnvironment
fetchPluginManifest behavior.
2026-07-20 14:43:41 +03:00
KhooLy
cbbe4f488f Add Swift PluginHttpClient implementation for iOS plugin scrapers
Implements the generated PluginHttpClient callback interface
synchronously via URLSession, with an SSRF guard (scheme allow-list
plus resolved-address blocklist) mirroring Android's PluginNetGuard.
2026-07-20 14:43:39 +03:00
KhooLy
7d3456ec7b Regenerate real uniffi Swift bindings for the ios Cargo feature
Replaces the checked-in 6-function stub with actual uniffi-bindgen
output, exposing PluginHttpClient, PluginHttpRequest/Response,
executePluginScraper, getPluginScraperSettingsLayout, and coreInvoke
to Swift for the first time.
2026-07-20 14:43:34 +03:00
KhooLy
e0f211e5e1 Fix two pre-existing bugs blocking shared module iOS/tvOS compilation
@JvmSuppressWildcards (TraktModels.kt) and java.util.Locale (PlayerOverlayCards.kt)
are JVM-only and don't resolve under Kotlin/Native. Neither was needed:
the wildcards annotation has no runtime effect Gson relies on, and
uppercase(Locale.ROOT) has a locale-agnostic uppercase() equivalent in
the common stdlib. Verified :shared:compileKotlinIosSimulatorArm64 now
succeeds (previously failed on master too, unrelated to plugin work).
2026-07-20 03:48:16 +03:00
KhooLy
114f36a590 Unify CloudStream and Nuvio plugin management into a shared Plugins feature
Addons now only manages Stremio addons. CloudStream3 repo/plugin state
and the Android-only Nuvio scraper settings screen both move into a new
shared KMP feature (feature/plugins) with a real Android data source
combining PluginRepositoryManager and PluginManager, and an iOS stub
data source pending a follow-up Swift implementation.
2026-07-20 03:44:09 +03:00
KhooLy
cfd7a4b32b Enable rquickjs bindgen against the correct Apple SDK per target
rquickjs-sys has no prebuilt FFI bindings for iOS/tvOS targets, so
bindgen needs the matching SDK sysroot (iphoneos/iphonesimulator/
appletvos/appletvsimulator) and clang target triple per cross-compiled
target, or it silently uses the host's own headers and fails. Unverified
here — no Xcode/macOS available in this environment; needs confirming
on a real Mac before relying on it.
2026-07-20 03:24:44 +03:00
KhooLy
28501ff31b Redesign Plugins screen to match Addons screen visuals, rename from Scraper Plugins
Scrapers are now grouped under their owning repository instead of two
separate flat lists, and the screen reuses AddonStoreScreen's visual
language (icon-circle back button, bordered repo cards, icon buttons)
instead of its own bespoke plain-text styling. Adds a refreshRepository
action reusing the existing re-fetch dispatch.
2026-07-20 03:15:39 +03:00
KhooLy
1e776bbb8a Add per-scraper settings UI backed by onSettings() JS export
Wire executePluginScraper's updated scraper_id/scraper_settings_json
params through FluxaCoreUniFfi and PluginRepositoryManager, add
getSettingsLayout() to fetch and parse a scraper's onSettings() field
layout into small UI models, and add a gear-icon-triggered bottom
sheet (PluginScraperSettingsSheet) rendering header/info/text/select/
toggle fields, matching Nuvio's real settings schema.
2026-07-20 02:54:53 +03:00
KhooLy
b710320ce4 Persist plugin repositories and scraper overrides across app restarts
PluginRepositoryManager's headless engine instance was in-memory only, so
installed plugin repositories and disabled/configured scrapers vanished
on every process restart. Now persists repository URLs and per-scraper
enabled/settings overrides to SharedPreferences, and replays them as
headless actions on startup (relying on the Rust-side fix that makes a
repository re-fetch preserve existing overrides instead of resetting to
manifest defaults).

Also adds updateScraperSettings(), used by the persistence replay and by
the upcoming per-scraper settings UI.
2026-07-20 02:45:00 +03:00
KhooLy
102d6755f8 Add headers/quality/size/provider/seeders/peers to Stream model
fluxa_core already emitted these for plugin-sourced streams, but Gson
silently dropped them since Stream had no matching properties. Renamed
the existing getHeaders() to resolveHeaders() to avoid a JVM signature
clash with the new headers property, and merged it into the raw
behaviorHints-derived headers.
2026-07-20 02:42:52 +03:00
KhooLy
ec634a13ec Wire Nuvio-compatible plugin scraper runtime into Android app
Implements PluginHttpClient over OkHttp with SSRF guarding (PluginNetGuard),
wires the FetchPluginManifest effect, adds PluginRepositoryManager for
repository/scraper state, folds plugin-scraper streams into
StreamDiscoveryUseCase, and adds a Plugins settings screen.
2026-07-20 02:28:45 +03:00
KhooLy
67c96f86ad Export bindgen env vars for the Android NDK when building fluxa-core
fluxa-core's plugin-js-engine feature now generates rquickjs FFI
bindings via bindgen instead of relying on prebuilt binding files,
since rquickjs-sys ships none for any Android target. Export
LIBCLANG_PATH (from the NDK's own libclang) and
BINDGEN_EXTRA_CLANG_ARGS (NDK sysroot, matching clang resource-dir,
and the concrete clang target triple) alongside the existing
CC/AR/linker env vars in each buildFluxaCore<Abi> task, so bindgen
resolves Android headers instead of falling back to the host's.
2026-07-20 02:18:15 +03:00
KhooLy
fc005ba1df Consolidate raw OkHttp request/response glue into a shared HttpEffectExecutor
StremioAddonManifestClient, StremioAddonResourceClient, and the trailer
effect in FluxaAndroidHeadlessEnvironment each hand-rolled the same
Request.Builder -> execute -> read status/body -> catch pattern. One
shared executor now backs all three call sites.
2026-07-20 02:03:00 +03:00
KhooLy
f0cf10d757 Add an Upcoming home row split from Continue Watching (off by default)
New setting under Settings > Appearance > Home Screen: when enabled,
Continue Watching items whose next episode/season hasn't released yet
move into their own Upcoming row instead of sitting in Continue
Watching with nothing to resume.

Classification runs async per up-next series item (no active playback
progress): HomeContinueWatchingCoordinator resolves the specific
lastVideoId episode via the existing getSeasonEpisodes fetch, checks
its release date through the new isEpisodeReleased FFI call, and
caches the result by id:lastVideoId. Once known, it triggers a
dynamic-rows refresh so the split applies without blocking the
initial Home paint.

HomeCatalogFeedCoordinator/HomeDynamicRowsCoordinator partition the
Continue Watching list into continue_watching/upcoming HomeCategory
rows via the new isUpcoming lambda, gated by
UserProfile.upcomingRowEnabled (resolved through the Rust safe-prefs
struct like every other appearance toggle). HomeCategoryPolicy's
isContinueWatchingCategory()-gated behavior (card layout, action-row
treatment, title passthrough) now also covers the upcoming row so it
renders with the same horizontal episode-card treatment.
2026-07-20 00:06:38 +03:00
KhooLy
6f6378a915 Add a visible D-pad add-to-library action on Search results for TV
CatalogCard's onLongClick had no discoverable remote equivalent — a
D-pad user had no visible way to know holding a button might do
something. Add a small focusable '+' badge on each search result card
on TV that triggers the same PosterActionSheet action as long-press.
2026-07-19 23:32:17 +03:00
KhooLy
0f9d97adc7 Add focus rings and initial focus to Detail/Discover picker sheets
Detail's season-picker ModalBottomSheet and Discover's filter
ModalBottomSheet both had trigger chips and row items styled with
plain .clickable and no focus feedback, plus no initial focus when the
sheet opened. Add focus rings to trigger/rows and request focus on the
selected row on open, same fix already applied to TvAuthScreen.
Library's collection-rename AlertDialog already used stock Material3
widgets with adequate focus support; just add initial focus to its
text field.
2026-07-19 23:30:06 +03:00
KhooLy
f1413e71c9 Give CategoryResultsScreen TV-appropriate spacing
Reached from every 'see all' row on Home/Discover; had no deviceType
awareness at all. Add TV padding/type-scale/grid sizing in line with
the other Tv*Screen wrappers. Card focus rings come from the
CatalogCard fix already landed.
2026-07-19 23:27:14 +03:00
KhooLy
bc486ebd41 Add focus ring and initial focus to ProfileListScreen
ProfileGridItem/AddProfileGridItem had no focus indicator and the
screen (the sole content right after login) never requested initial
focus, so a D-pad had nothing to land on. Add a focus ring to both and
request focus on the first profile when the list appears.
2026-07-19 23:26:22 +03:00
KhooLy
b908423630 Add D-pad focus rings to AddonStoreScreen
The per-addon action row (configure/refresh/move-up/move-down/remove)
packed 5 Material IconButtons with no way to tell which was focused.
Replace with a shared AddonIconButton that shows a clear focus state,
and add the same treatment to the back button, repo cards, and
CloudStream plugin rows.
2026-07-19 23:25:02 +03:00
KhooLy
0508ab94cb Add D-pad focus rings across Settings row primitives
SettingsRows.kt had zero focus styling on any interactive element
(toggle switch aside): choice/nav/action/connection rows, the stepper
+/- buttons, ordered-toggle checkbox and reorder buttons, color
swatches, and the secret-field reveal toggle. Add a shared
settingsFocusRing() modifier and apply it across the row primitives so
the whole settings tree gets consistent focus feedback in one place,
plus fix the TV category rail row and accent-color swatches in
SettingsScreen.kt directly.
2026-07-19 23:22:55 +03:00
KhooLy
e3436d2979 Add D-pad focus rings to catalog cards, player controls, and track picker
CatalogCard.kt disabled default indication with nothing put back, so
every catalog grid on TV (Home, Search, Discover, Library,
CategoryResults, LibraryFolderDetail) was navigable but silently
invisible when focused. Add a border/scale focus ring at the shared
component level instead of duplicating it per-screen.

PlayerControlBtn/SeekIconButton/the play-pause circle in
PlayerControlsChrome.kt were focusable() on TV but never changed
appearance on focus. TrackItem in PlayerSidebarShell.kt (source/audio/
subtitle/episode picker) had the same problem. Both now show a clear
focus state.
2026-07-19 23:19:18 +03:00
KhooLy
9b75f68331 Hide nav chrome on Auth and ProfileList screens
showNavigationBar was hardcoded true with no per-destination gating, so
both the mobile bottom bar and the new TV sidebar rendered on top of
the pre-login Auth/ProfileList screens, where there's no session yet
to navigate Home/Library/etc into.
2026-07-19 21:00:57 +03:00
KhooLy
6896b9665b Add a D-pad-focusable TvAuthScreen for Android TV login
AuthScreen had no TV variant and no explicit focus handling, so a
remote's D-pad had nothing reliable to land on or move between during
login. TvAuthScreen mirrors the same AuthStage state machine but adds
visible focus rings on every interactive element, requests initial
focus on the primary action per stage, and centers the form in a
fixed-width column instead of mobile's edge-to-edge layout.
2026-07-19 20:53:47 +03:00
KhooLy
3dbb3942d3 Replace mobile bottom nav bar with a D-pad sidebar on Android TV
FluxaNavigationBar was a touch-oriented pill bar rendered unconditionally
for every device type, including TV, with no deviceType gate. Add
TvSidebarNav as a proper left-edge D-pad rail for TV and reserve
horizontal (not vertical) space for it; drop TvCatalogHomeScreen's
redundant top icon row now that the sidebar owns navigation.
2026-07-19 20:50:52 +03:00
KhooLy
c5b3657118 Fix TV app startup crash from null platform services
androidFluxaPlatformServices was only built for DeviceType.Mobile, but
AppRoutesHost always force-unwraps it, so the TV build crashed with an
NPE on every launch.
2026-07-19 20:39:05 +03:00
KhooLy
ac09f5c342 Push Liquid Glass further toward true lensed glass
Add a radial progressive blur (stronger in the center, tapering at
the edges) so the surface reads as a lensed bulge rather than a flat
uniform blur, layer a top-inner-glow-to-bottom-inner-shadow gradient
behind the icons for real inner depth, and brighten inactive
icons/labels to 78% white for readability.
2026-07-19 19:34:21 +03:00
KhooLy
b1b787f8a7 Tighten Liquid Glass separation, depth, and active-state clarity
Replace the single weak white tint with a proper dark scrim + faint
sheen (via HazeStyle's tints list) so busy content behind the bar
stops fighting with the icons, add a real drop shadow beneath the bar
for layered depth, brighten the top-edge highlight, give the selected
icon a two-layer accent-colored glow instead of a plain white blob,
and give the Home row extra bottom content inset (nav bar height + 20dp)
in Liquid Glass mode so the last row of text clears the bar instead of
sitting under it.
2026-07-19 19:29:17 +03:00
KhooLy
f4706f1c3d Refine Liquid Glass nav bar toward true iOS-style material
Lighter backdrop fill and overall effect alpha so more content bleeds
through, a vertical-gradient edge (white top highlight fading to a
dark bottom inner shadow) instead of a flat border, a soft radial
"lens" glow behind the selected icon, and brighter inactive icons
(68% white) for readability against the lighter glass.
2026-07-19 19:20:50 +03:00
KhooLy
e2f1350f75 Use real backdrop blur for Liquid Glass mode via Haze
Pulls in dev.chrisbanes.haze 1.7.2 (Android + iOS klibs, matching the
shared module's targets) and swaps the earlier translucency-only
fallback for genuine hardware-accelerated background blur: the screen
content is marked as a hazeSource, and the nav bar reads it through
hazeEffect with a tinted, noisy glass style.
2026-07-19 19:07:58 +03:00
KhooLy
0e3d57b7a5 Add Liquid Glass appearance mode for the bottom navigation bar
New opt-in Appearance toggle (default off), mirroring how amoledMode
is wired through UserProfile and both settings data sources. Enabling
it swaps the nav bar's flat fill for a translucent frosted surface
with a soft top-light gradient and a hairline glass-edge border.
2026-07-19 18:59:54 +03:00
KhooLy
7ad3aa9fac Merge IntroDB/AniSkip toggles into one Skip Intro/Outro/Recap setting
Both sources fed the same skip-segments pipeline and were always
toggled together in practice, so collapse them into a single
useSkipSegments flag on SettingsPlaybackUiModel. Both the Android and
Apple settings data sources still write/read the two underlying
useIntroDb/useAniSkip flags in lockstep, so the merge stays UI-level
and existing profiles keep working.
2026-07-19 18:53:35 +03:00
KhooLy
ca0f8e28b2 Update IMDb parents-guide API base URL to tiffara.com
imdbapi.dev moved its base URL; also drops the now-dead
ImdbApiService.create() factory since HomeViewModel already gets its
instance through Hilt DI in NetworkModule.
2026-07-19 18:48:54 +03:00
KhooLy
944484323a Add settings toggle to disable Content Warnings overlay
Adds contentWarningsEnabled to the playback settings model, defaulting
to true, and gates both the parents-guide fetch and the overlay
display in PlayerScreen behind it.
2026-07-19 18:48:46 +03:00
KhooLy
e9ccd2e358 Push actual episode number to AniList instead of watched-episode count
When marking a show COMPLETED on AniList, progress now comes from
parsing the season/episode locator off each watched episode's ID and
taking the max episode number, falling back to episodes.size only if
that parsing yields nothing. Previously it always pushed episodes.size,
which undercounts progress for shows with specials or merged
multi-season watch history.
2026-07-19 17:12:45 +03:00
KhooLy
f4ea52a890 Fetch MKV attachments in a single streamed read instead of two requests
fetchAttachmentsViaSeekHead previously issued a 16-byte header range
request, parsed the EBML element length, then issued a second range
request for the full attachments blob. Now it opens one range request
up to MAX_ATTACHMENTS_BYTES and streams the header plus payload off
the same connection, saving a round trip per playback start.
2026-07-19 17:12:41 +03:00
KhooLy
eaef1fb4eb Thread trailer URL from detail state into DetailUiModel
Adds trailerUrl to DetailUiState and DetailUiModel so the detail
screen's data source can pass the resolved trailer through, and
adds the missing HeroPageChanged branch to CatalogHomeStore's
no-op action list.
2026-07-19 17:12:37 +03:00
KhooLy
ad6d158f43 Route player HTTP traffic through Cronet for QUIC/connection migration
Adds Cronet (org.chromium.net:cronet-embedded + Google's cronet-okhttp
transport adapter) as an OkHttp network interceptor on the player's two
HTTP clients (cache pre-warm + main ExoPlayer data source). All existing
interceptor logic — header injection, Dolby Vision manifest rewriting,
localhost proxy timeouts — is untouched since Cronet only replaces the
final transport, not the OkHttp API surface.

This mainly benefits direct-URL/debrid streams (real TLS connections),
which can now survive network handoffs (wifi<->cellular) and lossy
connections without a full reconnect/rebuffer. Torrent-proxied streams
stay on loopback where this has no effect either way.

Falls back to plain OkHttp transparently if Cronet engine creation
fails for any reason (unsupported ABI, native lib issue, etc.) so
there's no regression risk.
2026-07-19 17:09:52 +03:00
KhooLy
35f9c67092 Reorganize Home/Detail appearance settings into drill-down sections
Home Screen and Detail Screen settings now split into dedicated
sub-pages (Hero Banner, Continue Watching, Navigation for Home;
Hero Banner, Episodes for Detail) instead of one long mixed page,
so a specific setting is reachable by name instead of scrolling.

Also removes the dead "Trailer on Hero" toggle in Detail Screen
settings, which persisted a value but was never read by any
consuming code — it just duplicated the real "Trailer on Detail
Hero" setting and caused exactly this kind of navigation confusion.
2026-07-19 15:58:20 +03:00
KhooLy
dfbae648cd Cache hero billboard pool to disk so it paints immediately on cold start 2026-07-19 15:44:41 +03:00
KhooLy
a77c73803c Reserve per-type slots in hero billboard pool so tab filters keep enough items 2026-07-19 15:37:03 +03:00
KhooLy
0b953b6247 Follow SeekHead to locate MKV chapters past the first Cluster
MkvChapterFetcher's fixed prefix scan missed Chapters on releases where
it sits past the first Cluster (e.g. large embedded attachments pushing
it ~10MB in on one observed torrent release). Wire up the new
parseMkvChaptersAtOffsetNative bridge and the seekOffset the Rust
parser now reports: when Chapters isn't in the initial head fetch,
follow the SeekHead-derived offset with one small targeted range fetch
instead. Shrink the initial fetch from a 4MB prefix guess down to 64KB
now that it only needs to cover the (always compact, near-the-front)
SeekHead rather than hoping Chapters itself falls within it.

Add unitTests.isReturnDefaultValues so android.util.Log calls in
Robolectric-less JVM unit tests don't throw, and a regression test
covering the SeekHead follow-up path end to end.
2026-07-19 03:02:23 +03:00
KhooLy
a858cadfd6 Migrate Android trailer resolution to fluxa-core and fix hero playback UI
- Route YouTube trailer resolution through the shared headless engine
  (headless_engine/trailer.rs) instead of a hand-rolled Innertube client
  in TrailerResolver, mirroring what fluxa-desktop already does. Wire
  the two HTTP effect kinds (fetchYoutubeTrailerWatchConfig/Player) as a
  generic passthrough in FluxaAndroidHeadlessEnvironment.
- Wire HeroTrailerVideoSurface's ExoPlayer through
  TrailerResolver.mediaDataSourceFactory() so trailer playback actually
  gets the browser UA + Range header YouTube's CDN requires — this was
  the root cause of the black-screen hero trailer.
- Fix AndroidCatalogHomeDataSource pinning the active billboard movie to
  index 0 of heroItems on every index change, which desynced the pager's
  page index from item identity and made forward swipes visually snap
  back to the previous item.
- Hide synopsis/metadata/play button and shrink the logo while a hero
  trailer plays, soften the bottom scrim so it doesn't black out most of
  the video, and add trailer subtitles (selection/normalize/parse) via
  the same fluxa-core functions fluxa-desktop uses, rendered as a
  position-synced overlay above the shrunk logo.
2026-07-19 02:30:59 +03:00
KhooLy
efdabb628b Fix MKV chapter fetch header/range handling and add regression test
MkvChapterFetcher sent bare stream headers instead of routing through
StreamRequestPolicy (default User-Agent, referer), which many origins
require to respond to a Range request at all — unlike mpv, which always
gets proper headers. Also bail out instead of buffering an unbounded
body when a server ignores the Range header and returns 200 with the
full file.

Add MkvChapterFetcherTest using MockWebServer, exercising the real path
end-to-end (headers, 206 handling, JNI EBML parsing) on the JVM without
an emulator.
2026-07-19 02:30:16 +03:00
KhooLy
1039bfa5d3 Pull Stremio's library as a timestamped watchlist and reconcile it
LibraryItem never declared removed/_mtime so those fields were
silently dropped by Gson before Rust ever saw them, even though
library_state.rs already worked on raw JSON and could have used them.
Adds the fields, StremioRepository.getLibraryWatchlistWithTimestamps,
and wires it into the same mergeWatchlist reconciliation the other
providers use -- but pull-only (reconcileWatchlistPullOnly), not the
full push-capable path.

Deliberately not pushing local watchlist changes to Stremio: that
would need a "toggle membership" write that preserves whatever
progress/state fields already exist on the remote item (a
read-modify-write), which isn't built and isn't safe to guess at
without being able to verify Stremio's actual datastore PUT semantics
(full-replace vs partial-patch) against a live account.
2026-07-18 20:14:09 +03:00