Commit graph

305 commits

Author SHA1 Message Date
KhooLy
0860871297 Exclude the iOS-only plugin repository manager from tvOS, cache Rust on failure
Some checks failed
Apple / iOS simulator (push) Has been cancelled
Apple / tvOS simulator (push) Has been cancelled
Platform Verification / Android shared modules (push) Has been cancelled
FluxaApplePluginRepositoryManager.swift only has iOS callers and
references the iOS-only FluxaApplePluginsEffectHandler, so it fails to
compile for tvOS the same way the addon-store files already excluded
above do. Add it to the same exclude list.

Also set cache-on-failure on both Swatinem/rust-cache steps: since every
Apple CI run this session failed at a later step, the Rust build cache
was never saved, so each retry recompiled the whole dependency tree
(~15-25 min) from scratch instead of reusing what already built fine.
2026-07-20 18:05:43 +03:00
KhooLy
0611ad7b22 Exclude FluxaAppleAddonStoreManager.swift from the tvOS target
It imports FluxaShared, which the shared Gradle module never builds
for tvOS (tvOS stays native SwiftUI per the project's CMP/KMP
migration plan). The other four AppleCore files with the same
dependency are already excluded here; this one was added by the
recent Addon Store wiring work and missed the same exclusion.
2026-07-20 17:33:59 +03:00
KhooLy
3801e75b1a Pass seasonsCount to PlayerContentUiModel in the iOS playback presenter
Kotlin default parameter values aren't exposed through the generated
Swift bindings, so every PlayerContentUiModel field is required from
Swift regardless of its Kotlin-side default. This call site predates
the seasonsCount field and never got updated, which only surfaced now
that Apple CI's build actually reaches Swift compilation for this file.
2026-07-20 17:30:02 +03:00
KhooLy
97628a1fe2 Force SDKROOT per Rust target when building inside an Xcode script phase
The Xcode "Build Fluxa Rust Core" run-script phase runs this script
with SDKROOT already exported for whichever platform Xcode is currently
building (e.g. iphonesimulator for the FluxaIos scheme). That ambient
value leaks into cc's default sysroot for every target this script
loops over, including unrelated ones, so linking the aarch64-apple-tvos
build picked up an iphonesimulator libiconv and failed with "building
for tvOS, but linking in dylib built for iOS-simulator". Override
SDKROOT per target using the same SDK path already resolved for
bindgen, so each cargo build gets a sysroot matching its own target.
2026-07-20 17:06:10 +03:00
KhooLy
71e5d90b90 Set an iOS/tvOS deployment target when building fluxa_core per-target
build_rust_core never set IPHONEOS_DEPLOYMENT_TARGET/TVOS_DEPLOYMENT_TARGET,
so rustc fell back to its ancient default (iOS 10.0) when linking for
aarch64-apple-ios etc. That's too old for the rquickjs-sys objects, which
need ___chkstk_darwin support only available at a modern deployment
target, so linking fluxa_core failed with an undefined symbol.
build_streaming_engine already sets IPHONEOS_DEPLOYMENT_TARGET (default
18.5) for exactly this reason — mirror it here for both iOS and tvOS.
2026-07-20 16:36:37 +03:00
KhooLy
c0da06c05d Fix follow-up bug from the bindgen_env unbound-variable patch
The \${arr[@]:-} default-value form avoids bash 3.2's unbound-variable
error on an empty array, but on macOS's stock bash it still expands to
a single empty-string word, so `env "" cargo build ...` tried to exec
an empty command name and failed with exit 127. Build the cargo
invocation as an array and only wrap it with `env` when bindgen_env
actually has entries, avoiding any [@] expansion of an empty array.
2026-07-20 16:18:20 +03:00
KhooLy
782b977e08 Check out sibling fluxa-core repo in Platform Verification CI
Data module's Gradle build now depends on buildFluxaCoreHost (added
when UniFFI codegen moved from app to data module), which needs
../fluxa-core to exist next to the checkout. platform.yml never
checked that sibling repo out, unlike apple.yml which already does
this. Mirror apple.yml's checkout + symlink steps and workflow_dispatch
input.
2026-07-20 15:54:09 +03:00
KhooLy
33a88a8b86 Fix unbound-variable crash in the host Rust core build on macOS CI
macOS's stock /bin/bash is 3.2, which raises "unbound variable" under
set -u when expanding an empty array via \${arr[@]} even with the [@]
syntax. build_rust_core hits this on the host-only build call (no
--target flag), where bindgen_env stays empty. Use the \${arr[@]:-}
default-value form, the standard bash 3.2-safe workaround.
2026-07-20 15:49:51 +03:00
KhooLy
d5f3819ebe Fix shared UI boundary violations from the CMP player overlay migration
checkSharedUiBoundary was failing on master because the July 17 move of
player overlays into shared/commonMain kept imports of data/player domain
types (Meta, Stream, Video, IntroTimestamps, UserProfile, Chapter,
MediaTrack, TorrentStreamStatus, TrailerCue). Relocate the plain value
types into the shared-safe com.fluxa.app.shared.feature.player package,
and replace the remaining domain-object usages with small UI models
(StreamSourceUiModel, SkipSegmentUiModel, NextEpisodePreviewUiModel)
mapped at the Android call sites. Also drops the dead, UserProfile-only
QuickSettingsSidebar/TrackSidebar composables that had no callers.
2026-07-20 15:37:30 +03:00
KhooLy
5cb1bb75db Wire Addon Store screen to real networking on iOS
FluxaAppleAddonStoreManager fetches manifests over URLSession and
persists the local addon list plus enabled/disabled state via
FluxaAppleAddonConfigurationStore. FluxaAppleAddonStoreStartup routes
AppleAddonStoreActionSnapshot actions from the shared UI to it and
pushes merged state back to Kotlin. FluxaIosApp constructs and
registers it, restoring/refreshing on launch. Users can now actually
add, toggle, reorder, and remove Stremio addons on iOS.
2026-07-20 15:05:37 +03:00
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