Commit graph

145 commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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