The action, i18n strings, and app-level routing to ProfileList already
existed, but no row in the Settings hub ever called onSwitchProfiles,
so there was no way to reach it from the UI.
Raise SettingsGroupCard contrast with a subtle border, add hairline
row dividers within cards, tighten section header spacing, and
separate the profile row and destructive disconnect action into
their own cards instead of sharing space with unrelated rows.
StreamDiscoveryUseCase now bails out early on a null execution plan
instead of relying on per-field null-safe calls. HomeParentsGuideCoordinator
now imports ImdbApiService from its current package.
Notification title now shows the actual episode/show title with the
release kind as subtext, and season/episode text is generated per case
(episode with title, episode without, season-only) instead of one
generic template. Calendar artwork now prefers the episode-specific
poster before falling back to show-level artwork.
Reworks the player's settings sidebar and input controls, adjusts the
sidebar shell layout, and wires the updated sidebars into the playback
surface and screen content.
Surfaces which item is currently being imported (index/total/title)
under each import step on both mobile and TV auth screens, with a
checkmark for completed steps.
- AddonRepository: cache meta detail lookups with in-flight de-dup, and
race addons instead of fanning out to all of them unconditionally,
cutting redundant requests to slow/duplicate addon instances.
- HomeCatalogFeedCoordinator: publish each background catalog row as it
arrives instead of waiting for a whole batch, and support cancelling
in-flight remaining-catalog loads.
- HomeBillboardRuntime: add a pause for rotation/prefetch/trailer work.
- HomeViewModel: pause Home's background billboard/catalog work when
Discover becomes active so it stops competing for the same addons.
- AndroidDiscoverDataSource: skip redundant addon-list recomputation on
catalog-only filter changes, and move card mapping off the main thread.
- FluxaHeadlessEffectRunner/FluxaHeadlessAppRuntime: narrow the runtime
mutex to only guard Rust engine calls, not the network effects they
trigger, so one screen's dispatch can no longer block another's for
the duration of a slow effect chain.
- NetworkModule: raise the shared OkHttp dispatcher's per-host/total
request ceilings.
Add a topNavigationBar appearance toggle that, on TV and non-compact tablet
widths, moves the navigation destinations into a horizontal bar across the top
instead of the TV sidebar, tablet rail, or bottom bar. Thread the flag through
the settings contract, the Android and Apple settings data sources, the user
profile, and both language files, and render the shared FluxaTopNavBar with
focus restoration on TV.
The TV host forces the Expanded width class, which would otherwise flip the
detail screen into the two-pane tablet layout. Gate the two-pane path on a
non-TV device type so TV keeps its single-column detail.
Introduce an expect/actual TvHeroRow rendered as the first row of the TV home.
The Android actual uses the androidx.tv Carousel with a backdrop, scrim, and
title; iOS gets a no-op since it never renders the TV screens. Wire the shared
androidx.tv dependencies into the shared module's Android source set.
On the Expanded width class, render the detail screen as a fixed hero/metadata
column beside a scrolling episodes/related pane, and the library folder view
beside the library list. Narrower widths keep the single-column layouts. The
detail body and episodes/related items are factored into shared helpers so both
layouts render identical content.
On non-TV hosts at the Expanded width class, replace the bottom navigation bar
with a left navigation rail and inset screen content by its width, mirroring the
TV sidebar layout.
Introduce a runtime WindowWidthClass (Compact/Medium/Expanded) provided at
the app root and use it to drive responsive grid columns on Discover and
Library. Add initial focus, focusRestorer, and focusGroup to the TV home and
sidebar, apply focusRestorer to the shared Discover/Library grids, and
strengthen the focused card highlight.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Adds name/description/logo/version/configurable to
AppleAddonManifestSnapshot so the iOS Addon Store can show real addon
metadata instead of just catalog capability flags.
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.
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.
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.
Fetches plugin repository manifests over URLSession and parses them
via coreInvoke, matching Android's FluxaAndroidHeadlessEnvironment
fetchPluginManifest behavior.
Implements the generated PluginHttpClient callback interface
synchronously via URLSession, with an SSRF guard (scheme allow-list
plus resolved-address blocklist) mirroring Android's PluginNetGuard.
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.
@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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.