Commit graph

340 commits

Author SHA1 Message Date
KhooLy
afea67d02f fix: resolve FluxaCore/FluxaShared type identity mismatch after moving Apple snapshot DTOs
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
Kotlin/Native generates distinct Swift types for a class depending on
whether it's imported directly from its owning module or transitively
re-exported through another framework, so files consuming these DTOs
via FluxaShared's re-export of core no longer matched the FluxaCore
type Loader/ResourceLoader now return directly. Route Bootstrap and
HomeEffectHandler through FluxaCore consistently, and add an explicit
FluxaCore->FluxaShared stream conversion where DetailStartup crosses
back into FluxaApple.shared's Shared-typed API.
2026-07-28 19:42:59 +03:00
KhooLy
a0db322fed fix: move Apple catalog/detail/discover/search snapshot DTOs from shared to core
Xcode's embedAndSignAppleFrameworkForXcode task for :shared: is
intermittently disabled by the Kotlin Gradle Plugin's own IDE/index-build
detection (onlyIf 'Task is enabled' is false), silently skipping the
framework embed and leaving these types unavailable to Swift. core's
framework already embeds reliably for iOS (matching tvOS), so these plain
DTOs move there instead; the Compose-dependent DataSource wrappers stay in
shared, which already re-exports core.
2026-07-28 19:04:13 +03:00
KhooLy
b44ebec2d9 debug: add --info logging to the shared framework embed task to diagnose why Xcode's build skips it 2026-07-28 18:27:53 +03:00
KhooLy
ede8260d1b fix: force embedAndSignAppleFrameworkForXcode to rerun, avoiding a stale Gradle up-to-date skip 2026-07-28 17:39:32 +03:00
KhooLy
46d5e58d70 fix: use lowercase target-triple form for AR_ env vars so cc-rs finds llvm-ar 2026-07-28 15:51:00 +03:00
KhooLy
bd518a17c3 ci: update GitHub Actions to latest majors
Some checks are pending
Apple / iOS simulator (push) Waiting to run
Apple / tvOS simulator (push) Waiting to run
Platform Verification / Android shared modules (push) Waiting to run
2026-07-28 15:28:13 +03:00
KhooLy
5fefaf367a refactor: resolve discover catalog/genre selection via shared core plan
AndroidDiscoverDataSource duplicated catalog+genre resolution logic
in Kotlin instead of calling discoverSelectionPlan, the same shared
Rust function fluxa-desktop already relies on for this. Route Android
through it too: one implementation for both platforms, and it's more
robust than the Kotlin version since it treats whichever extra a
catalog lists first as the filterable dimension generically, not just
one literally named "genre".

Add DiscoverCatalogOption.extras (carrying each raw catalog extra:
name/options/default/isRequired) and CatalogExtra.default, both
needed for the plan call and previously missing from the addon
manifest models entirely -- the actual bug hunted down across this
and the two preceding core commits was that "default" was dropped at
manifest-parse time in Rust, so no amount of Kotlin plumbing could
have surfaced it.
2026-07-28 04:08:32 +03:00
KhooLy
eb53587a61 fix: auto-select a catalog's default genre when one is required
Catalogs with requiresGenre now pre-select defaultGenre (falling back
to the first available option) instead of leaving the dropdown
looking unselected while discover() silently ran without a genre --
results were already coming back filtered by the addon's own
server-side fallback, but the UI gave no indication why.
2026-07-28 03:40:04 +03:00
KhooLy
f97841c66a fix: populate discover genre dropdown without blocking catalog selection
The genre dropdown never appeared because loadDiscoverCatalogFilters
-- the only call that fetches real per-catalog genre data -- was
unreachable: the fast local catalog-options path resolved a
catalogKey on almost every call and returned early before ever
reaching it.

Fetch genres in the background right after applying the selected
catalog and kicking off results (matching the existing behavior for
that part exactly, so catalog selection stays instant), instead of
gating catalog selection behind the fetch. Guards on catalogKey
rather than full filter-object equality, since genre/type selections
made while the fetch is in flight shouldn't invalidate it.
2026-07-28 03:17:02 +03:00
KhooLy
b1dc915eab fix: stop episode selection from cancelling its own stream fetch
EpisodeSelected called dataSource.selectEpisode(episodeId), which
kicks off DetailViewModel.fetchStreamsForSelection, and then also
emitted a SelectSources navigation event that FluxaAppHost handles by
calling detailStore.loadSources(episodeId) -- which starts the exact
same fetch again. fetchStreamsForSelection cancels its previous job
on every call, so the second (navigation-triggered) fetch always
killed the first one before its addon requests could resolve, and the
episode's stream list came back empty even though the addons
genuinely had results (confirmed via logcat: StreamDiscovery reports
600 remote streams found, then the very next fetch attempt logs "NO
STREAMS found" for the same episode).

loadSources already does everything selectEpisode did (marks the
episode selected, then fetches), so the direct selectEpisode call is
redundant -- drop it and let the single navigation-driven fetch run
uncontested.
2026-07-28 03:02:22 +03:00
KhooLy
cfc03cba00 fix: sync Android navigation state with in-app destination changes
MainActivity kept its own currentDestination alongside FluxaAppHost's
internal appState.uiState.destination, with only a one-way sync
(external -> internal). Any purely-internal navigation (bottom nav
taps, "switch profiles" from Settings) moved appState's destination
without MainActivity's copy ever finding out. If a later external
navigation call (e.g. profile selection completing) happened to name
a destination MainActivity's stale copy already equaled, it was
treated as a no-op and silently dropped -- reproducible by going
Settings -> Switch profiles -> tapping a profile, which then did
nothing.

Add a callback that mirrors appState's destination back out to
MainActivity whenever it changes internally, and guard the existing
external->internal effect so it only re-applies on a genuine change
(avoiding a reset loop that would otherwise wipe editingProfile/
selectedDetail/etc. on every round trip).

Also account for showProfilePickerSettings in the screen transition
key, which was missing and left the profile picker settings screen
keyed identically to whatever destination was active underneath it.
2026-07-28 02:23:47 +03:00
KhooLy
8318822ce6 fix: stop avatar-picker sheet from jittering at scroll bounds
Forcing the sheet content to a fixed 85% height fraction while also
making it vertically scrollable fought with ModalBottomSheet's own
height/anchor calculations, causing a layout oscillation once the
user scrolled to the end. ModalBottomSheet already bounds its content
to the available window height, so a plain scrollable column is
enough.
2026-07-28 02:23:39 +03:00
KhooLy
7ae11226ca fix: percent-encode spaces in image URLs on Android and iOS
Avatar pack image URLs pulled from pack.json can contain literal
spaces (e.g. category folders like "Attack On Titan"). Coil's OkHttp
fetcher and iOS's NSURL.URLWithString both reject unencoded spaces
and fail silently with no placeholder, leaving the thumbnail blank
even though the URL works fine in a browser. Move the sanitizer into
commonMain so both platform image loaders share it.
2026-07-28 02:23:34 +03:00
KhooLy
e5938a8e38 fix: scrollable avatar sheet, shared avatar cache key, nav bar overlap
- Choose Image sheet had no scroll and no height cap, so once avatar
  pack content filled the screen there was no way to reach the rest.
  Cap it at 85% height and make it scrollable.
- The bottom nav bar avatar, profile edit avatar, and profile grid
  avatar each used a different Coil cache key for the same image, so
  the bottom bar always cold-loaded from network on every launch
  instead of reusing what was already cached. Unify on one key.
- "Manage Profiles" on the who's-watching screen used a fixed bottom
  padding that ignored the 3-button navigation bar and sat underneath
  it on devices without gesture nav. Add safeDrawing bottom inset
  padding.
2026-07-28 01:38:06 +03:00
KhooLy
b31acad509 feat: redesign profile edit screen with grouped sections
Restructure into SettingsGroupCard sections (name, PIN/biometric,
danger zone) instead of one flat column, move avatar picking into a
bottom sheet so the main form stays short, and show a dimmed default
avatar placeholder for pack thumbnails that fail to load instead of a
blank circle.
2026-07-28 01:28:46 +03:00
KhooLy
1bd1ea4644 fix: show avatar names in pack picker, fix blank thumbnails
Avatar image URLs pulled from pack.json can contain literal spaces
(e.g. category folders like "Attack On Titan"). Coil's OkHttp fetcher
rejects unencoded spaces and fails silently with no placeholder, so
the thumbnail just stayed blank even though the URL works fine in a
browser. Percent-encode spaces before handing the URL to Coil.

Also label each avatar in the pack picker grid with its name instead
of showing bare, unlabeled images.
2026-07-28 01:25:15 +03:00
KhooLy
c4060cf5fb feat: refresh avatar packs on app open and on demand
Avatar packs were a one-time snapshot from when they were added, so
new images the repo owner pushed later never showed up. Add a
per-pack refresh button in Profile Picker Settings, and re-run
discovery for every saved pack's repository once when the app starts.
2026-07-28 01:23:14 +03:00
KhooLy
a17144a416 feat: add profile picker avatar packs and background image (Android)
Let users customize the "who's watching" screen: add GitHub-hosted
avatar packs to pick profile pictures from, and set a background
image behind the profile grid. The Rust side (GitHub URL/tree/manifest
parsing and validation) already existed in fluxa_core but was never
wired to Kotlin; this adds the FFI wrappers, an OkHttp-driven GitHub
crawl, a small global picker-settings store, and the new/updated
Compose screens. Android only for now — iOS/desktop keep compiling via
default no-op ProfilePersistence methods.
2026-07-28 01:16:12 +03:00
KhooLy
cb22014f82 fix: restore missing switch-profiles row in Settings hub
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.
2026-07-28 01:16:04 +03:00
KhooLy
72f5010294 feat: improve settings UI visual hierarchy
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.
2026-07-28 00:48:28 +03:00
KhooLy
613a6899d0 feat: use core-computed calendar artwork priority
Prefers resolvedArtworkUrl from fluxa-core over the local
episodePoster/poster/meta-field chain so artwork selection matches
fluxa-desktop's calendar.
2026-07-28 00:38:47 +03:00
KhooLy
0654f2717f fix: guard null scraper plan and correct ImdbApiService import
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.
2026-07-28 00:26:23 +03:00
KhooLy
9cb5f4b811 fix: improve episode release notification text and artwork priority
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.
2026-07-28 00:26:17 +03:00
KhooLy
0ec2fe01d7 feat: rework player settings and source sidebars
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.
2026-07-28 00:26:12 +03:00
KhooLy
78569c8f15 feat: show per-item progress during Nuvio account import
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.
2026-07-28 00:26:07 +03:00
KhooLy
c030fd66b2 perf: fix Discover screen load and catalog switch latency
- 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.
2026-07-28 00:17:59 +03:00
KhooLy
b29cd3ebf4 refactor: split Android UI and core boundaries 2026-07-22 18:46:31 +03:00
KhooLy
e36a43bae5 Move shared app policies into fluxa core 2026-07-22 16:56:20 +03:00
KhooLy
441427d2d1 feat: improve Nuvio import and app experience 2026-07-22 01:07:52 +03:00
KhooLy
8e0969e67e Add a top navigation bar option for tablet and TV
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.
2026-07-21 15:03:40 +03:00
KhooLy
813dea5602 Keep the detail screen single-column 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.
2026-07-21 14:52:40 +03:00
KhooLy
437b6d5754 Add a TV hero carousel to the catalog home
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.
2026-07-21 14:50:37 +03:00
KhooLy
fea39a2186 Add two-pane detail and library layouts on expanded width
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.
2026-07-21 14:46:38 +03:00
KhooLy
9fe5e769a9 Show a navigation rail instead of the bottom bar on expanded width
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.
2026-07-21 14:43:28 +03:00
KhooLy
e558bb84af Add window width class and TV focus restoration
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.
2026-07-21 14:41:17 +03:00
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