Commit graph

94 commits

Author SHA1 Message Date
KhooLy
757d19e8f4 Improve calendar defaults and navigation icons 2026-07-18 18:04:41 +03:00
KhooLy
62d20e23c4 Refine mobile navigation and poster actions 2026-07-18 17:56:43 +03:00
KhooLy
909425202a Improve calendar library and catalog experiences 2026-07-18 16:52:38 +03:00
KhooLy
9a75c888f5 Add continue watching source preference and Nuvio account sync improvements
Adds a per-profile continue-watching source preference (Fluxa/Stremio/
Nuvio/Trakt/Simkl/AniList), a background worker to push playback
progress to Nuvio instead of pushing inline, dedup of mark-watched
pushes against already-watched episodes, and general Nuvio account
import/merge fixes.
2026-07-18 15:02:19 +03:00
KhooLy
ad06eb94f5 Add subtitle format labels and container track IDs; rework inline delay controls
Removes the standalone inline audio/subtitle delay adjust pill and
its state, and adds MediaTrack.subtitleFormatLabel/containerTrackId
so subtitle tracks can be labeled by codec (SRT/ASS/VTT/TTML/PGS/DVB/
VOBSUB).
2026-07-18 15:01:46 +03:00
KhooLy
0fa3ef723e Collapse bottom action row space while scrubbing
The row under the seekbar exited with fade and slide only, so its
layout space stayed reserved until the animation finished and then
snapped away. While scrubbing, the seekbar was therefore held up in
its buttons-visible position and the time and thumbnail preview
anchored above it floated high over empty space. The row now shrinks
vertically as it fades, taking its spacing with it, so the seekbar
settles to the bottom and the preview stays snug above it.
2026-07-17 17:59:52 +03:00
KhooLy
35ae8ea14a Fix adjust slider lag, drop fade-on-drag, and stop live seeking while scrubbing
Opacity sliders wrote the profile on every drag frame, which is what
made them stutter; they now hold a local live value that drives the
sample cue and only commit to the profile on release. With the panel
usable during drags, the fade-to-15% behavior is removed and the sample
cue moves lower (20dp from the bottom) instead.

The seek preview thumbnail system seeked the real player whenever a
drag paused over an uncached bucket, so scrubbing caused audible
jumps mid-drag. It now serves cached frames only, keeping the
background capture loop; the actual seek happens solely on seekbar
release, which the seekbar already implemented.
2026-07-17 17:50:08 +03:00
KhooLy
f9d4e664fe Keep subtitle preview visible under a compact top-right adjust card
The adjust card stacked its slider blocks two lines tall, making the
content roughly the full height of a landscape phone screen, so even
top-anchored it buried the sample cue. Slider items collapse to single
rows (title, slider, percent) and the card anchors top-right instead of
top-center, clearing the bottom-center subtitle zone both vertically
and horizontally.
2026-07-17 17:42:59 +03:00
KhooLy
01b85c0f1b Stop player menus leaking drags to volume gestures and make adjust page scroll
Drags on a sheet, card, or the speed popup were only consumed when a
child (slider, list) handled them; anything else bubbled up to the
player's volume/brightness drag layer, so swiping on the subtitle
adjust panel changed the volume. The shell root and the speed popup now
consume all unhandled drag gestures, while child scrollables and
sliders keep priority. The adjust page content also becomes vertically
scrollable since it can overflow the top-anchored card on short
landscape screens.
2026-07-17 17:40:38 +03:00
KhooLy
b4751da22e Move adjust entry to category headers and keep subtitles visible while adjusting
The adjust entry no longer scrolls with the track lists: each category
header (column label in the two-column card, the tab row in portrait)
carries a small tune icon. Audio's icon opens the inline delay pill
directly; the subtitles icon opens the adjust panel.

The adjust panel now anchors to the top of the screen with a near-zero
scrim so the bottom-center subtitle area stays visible, and while a
slider is being dragged the whole card fades to 15% opacity. A sample
cue styled with the live text/background/outline opacity values renders
at the subtitle position whenever the panel is open, so there is always
something to judge adjustments against.
2026-07-17 17:36:23 +03:00
KhooLy
d0207f50c6 Redesign player menus: anchored speed popup, combined tracks card, inline delay pill
Every player menu previously shared one full-chrome sidebar shell. The
shell now renders a translucent 24dp-radius card (0xE6101418, no
border) that fades in with a short upward drift, centered in landscape,
with no subtitle line and no close button; portrait keeps the bottom
sheet with its drag handle. Headers only render when a title or back
action exists.

Speed is no longer a sheet: it is a compact popup near the controls
that scales and fades in from its corner over a light scrim, listing
plain speed rows with the current one checked.

Audio and subtitles merge into a single Netflix-style two-column card
in landscape (portrait keeps the segmented tabs). Audio delay and
subtitle delay leave the card entirely: selecting them collapses the
menu to a floating pill over the video with -/value/+ controls so the
sync effect is visible live while adjusting. Subtitle styling keeps the
in-card adjust page.
2026-07-17 17:26:56 +03:00
KhooLy
0262acd528 Turn landscape player sheets into content-hugging floating cards
The landscape sheet stretched full height and flush to the screen edge,
so short menus like Audio or Speed left a huge empty void below their
rows. Side sheets are now 340dp floating cards (400dp on TV) rounded on
all corners, vertically centered with a 16dp edge margin, sized to
their content with a viewport-bounded max height. The slide-in
animation accounts for the margin, and the header is tightened with a
smaller, quieter close button.
2026-07-17 17:12:21 +03:00
KhooLy
5b153bfd3b Declutter player settings sheets
The sheets opened with an all-caps title, an instruction sentence, a
panel spanning almost half the screen, and two-line rows where every
speed carried a redundant Slower/Faster label. Titles are now title
case (Audio, Subtitles, Playback speed), the instruction line is gone,
the landscape sheet narrows from 46% to 38% width capped at 400dp,
rows tighten to 46dp with single-line speed entries (only 1x keeps a
Normal label), and mono/stereo channel counts render as words instead
of 1.0/2.0.
2026-07-17 17:07:59 +03:00
KhooLy
481aeb63e8 Restyle sidebar track rows with rounded selection highlight
Rows in the player sheets were flat full-bleed text with a 4dp inset;
the only selection cue was the trailing check. Rows are now rounded
12dp containers with a subtle white highlight behind the selected one,
matching ripple bounds, and the header subtitle is slightly larger.
2026-07-17 16:58:00 +03:00
KhooLy
7f93efbd41 Polish player sidebar open/close and unify speed menu
The sidebar previously entered with a fade plus a 44px nudge and
disappeared instantly on close. It now slides the full distance from
its edge (right for side sheets, bottom for bottom sheets) with the
scrim fading in sync, and close requests play the exit animation
before the panel is removed, including swipe-to-dismiss. Panel height
changes between tabs are animated instead of snapping.

The mobile speed menu drops its bordered chip grid in favor of the
same checkmarked rows used by the audio, subtitle, and source lists.
2026-07-17 16:55:54 +03:00
KhooLy
bdc4ab6b9a Redesign player controls chrome and sidebar layout
Reworks the player overlay chrome, sidebar shell, settings and
source sidebars, mark-segment sidebar, and mobile dialogs, plus
new dimension/icon tokens and i18n strings for the redesign.
2026-07-17 16:03:54 +03:00
KhooLy
83f452da15 Use spring animation for bottom nav icon scale
Swaps the flat tween for a medium-bouncy spring so the selected tab
icon overshoots slightly on scale-up instead of growing linearly.
2026-07-17 16:03:40 +03:00
KhooLy
8df4874b0a Fix hero indicator dots not tracking swipe position
The 5-dot sliding window re-centered on the current page every swipe,
so the highlighted dot's position within the window stayed constant
while paging through the middle of the list. Show one dot per item
instead since the hero caps at 8 items.
2026-07-17 16:03:25 +03:00
KhooLy
3440831ed1 Preserve per-tab scroll position across bottom nav switches
Wrap the screen AnimatedContent in a SaveableStateHolder keyed by
screen so each destination's LazyColumn/LazyList state survives being
switched away from and back to, instead of resetting to the top.
2026-07-17 06:18:44 +03:00
KhooLy
8f459a0f76 Redesign bottom nav bar and home screen scroll/hero UX
Bottom bar now overlays content edge-to-edge with a scrim background
and top hairline instead of reserving fixed space, gains labels,
FluxaIcons, selection animation, and a calendar badge for new
releases. Home screen's filter/notification/avatar bar is pinned and
fades in as the hero scrolls past instead of scrolling away with it.
Hero auto-swipe now pauses during user interaction, gained a Play
button, capped height, and windowed dot indicators. Row "view all"
now navigates to the category grid instead of silently loading more
into the row.
2026-07-17 06:17:18 +03:00
KhooLy
e773f3438b Redesign Settings navigation, IA, and visuals; fix Add-ons UX
Settings:
- Replace the single-level category state with a real back stack
  (lifted into FluxaAppState) so back navigation goes up one level
  instead of always jumping to the hub, with hardware back wired
  through MainActivity.
- Scroll position is now keyed per category instead of shared/leaking
  across pages, and nested pages show their own titles.
- Split Notifications and TMDB feature toggles out of Account into
  their own pages instead of an accordion and an overloaded
  TmdbAccountChanged action bundling unrelated fields.
- Downloads settings page no longer duplicates Library's live download
  list; it links out to it instead.
- Add a lightweight settings search (SettingsSearch.kt) over a static
  index of rows.
- TV renders Settings as a two-pane list-detail layout.
- Introduce SettingsGroupCard and restructure every settings page
  from a flat row list into grouped inset cards, restyle the search
  field and choice-picker dialog to match the app's black/white
  aesthetic instead of default Material styling, and merge the
  redundant avatar/account rows on the hub.
- Add-ons settings page now only launches the real add-ons screen
  directly (dropped the redundant "Manage Add-ons" middle page and
  the torrent preferences that lived there).

Add-ons store:
- Validate pasted manifest URLs by actually fetching them before
  installing, instead of accepting any string that pattern-matched
  as a manifest path; show an inline error on failure and a
  confirmation dialog on success.
- Replace the bare "+" icon button with a labeled Add button.

Also strips emoji baked into a few i18n strings (CloudStream repo/
plugin headers, premiere notification) per project convention.
2026-07-17 05:47:52 +03:00
KhooLy
d6232b9bd6 Fix episode tap not navigating to source selection
Tapping an episode card only updated the selected-episode state without
emitting a navigation event, so nothing visibly happened until Play was
pressed. DetailStore now emits SelectSources on EpisodeSelected, and
selectedEpisodeId is tracked via a StateFlow instead of a plain var so
the selection highlight updates immediately instead of lagging behind
an unrelated state emission.
2026-07-17 05:47:37 +03:00
KhooLy
b6fa1cbdb9 Show sources progressively and render full stream name/title in cards 2026-07-17 01:22:32 +03:00
KhooLy
d7d6822802 Redesign source selection screen with backdrop, addon chips, and parsed stream cards 2026-07-17 01:10:08 +03:00
KhooLy
e8dced3b20 Move player overlays, sidebars, and gesture input into shared CMP
Relocates skip-segment/transient overlays, the sidebar shell + track
list primitives, mark-segment/settings/track sidebars, and the
playerInputControls gesture modifier from app-module Android files
into shared/commonMain. Promotes coil3/coil3-compose to commonMain
(genuinely multiplatform; only coil3-network-okhttp stays
Android-only) so the overlay cards using AsyncImage can move too, and
promotes TorrentStreamStatus to player/commonMain as a plain data
holder.

Where a composable used to reach into UserProfile's JNI-backed
`safeXxx` properties (holdSpeed, holdToSpeedEnabled, language) or an
Android-only Locale API, it now takes the resolved primitive as a
parameter instead, with the app-side caller supplying it - keeping
the commonMain/androidMain boundary honest rather than papering over
it.

EpisodeSidebar (Hilt ViewModel-bound) and TrackSelectionState (direct
FluxaCoreNative JNI calls) stay Android-only, along with the actual
video surface, playback effects, and setup effects - those are the
next sub-steps in the player-extraction migration.
2026-07-17 00:06:49 +03:00
KhooLy
b9c2063bd4 Move TV/mobile player controls chrome into shared CMP
Relocates TvPlayerUIContent/MobilePlayerUIContent and their supporting
composables (seekbars, transport buttons, bottom action row) from
app-module Android files into shared/commonMain as
PlayerControlsChrome.kt. Both already consumed only lightweight params
(PlayerContentUiModel, Chapter, primitives, callbacks); the one real
platform coupling was the seek-thumbnail capture (SurfaceView/ExoPlayer
frame grab), which now happens at the PlayerOverlays.kt call site on
the Android side and gets passed down as a plain ImageBitmap instead of
being computed inside the shared composables.

First sub-step of the player-extraction migration step; overlays,
sidebars, and setup/effects follow as separate passes.
2026-07-16 23:27:49 +03:00
KhooLy
fc6bc620b3 Move data/player/domain logic into commonMain KMP source sets
Sweeps addon repositories, watchlist/profile storage, Trakt/Nuvio
sync, discovery models, and player policy classes out of Android-only
packages into data/commonMain and player/commonMain, with thin
androidMain wrappers where platform APIs are still required.
2026-07-16 23:00:28 +03:00
KhooLy
bd780396d1 Complete shared KMP and CMP migration 2026-07-16 15:50:35 +03:00
KhooLy
658b3ce3e3 KMP/CMP migration: purge legacy Android screens, route TV through shared FluxaApp
Continue the shared CMP/KMP migration: remove the legacy app/ui/catalog
Screen.kt files now superseded by shared/commonMain equivalents (Detail,
Search, Settings, Login, Profile, Watchlist, AddonStore, and the full
legacy Tv* screen set), and thread a DeviceType through FluxaApp/
FluxaAppHost so Android TV routes Home/Search/Discover/Calendar/Library
through shared destinations instead of falling into the removed legacy
composables (which previously hit an unreachable error() case).

Adds TvCatalogHomeScreen wiring plus new TvSearchScreen/TvDiscoverScreen/
TvCalendarScreen/TvLibraryScreen as thin TV-safe wrappers around the
existing shared screens. Removes the orphaned shared player/PlayerScreen.kt,
which had no call sites since the player stays platform-native by design.
2026-07-16 01:17:27 +03:00
KhooLy
4780549655 Move HomeBillboardRanking/StateHolder to shared KMP now that Meta is shared
Both were blocked solely on Meta living in the Android-only app module,
which the previous commit fixed. Split ContentIdentity's two pure functions
(billboardKey, normalizedBillboardTitle) into a new shared
ContentIdentityTitle.kt, since the rest of ContentIdentity still calls
FluxaCoreNative (JNI-only, not yet exposed to shared code).
2026-07-16 00:41:46 +03:00
KhooLy
d4c9ae6edd Move zero-dependency utility files from app module to shared KMP
AndroidMetadataRules.kt (renamed MetadataRules.kt), PlayerEngineSnapshot.kt, and
FluxaIcons.kt had no Android-specific dependencies once traced (pure Regex/
String logic, @Immutable data classes, and material-icons lookups already
resolvable in commonMain). Dropped their `internal` modifiers since Kotlin's
`internal` is module-scoped, not package-scoped, and app-module callers now
sit in a different Gradle module than the shared declarations.
2026-07-16 00:09:59 +03:00
KhooLy
20476761a9 Migrate shared UI and Apple playback to KMP 2026-07-14 18:14:18 +03:00
KhooLy
a9f6e65b36 Migrate shared Apple UI bridges to KMP 2026-07-14 16:55:16 +03:00
KhooLy
08da9029ae Show 3 lines of hero banner synopsis instead of 2 2026-07-14 15:30:36 +03:00
KhooLy
13da42567f Fix hero dots/synopsis spacing, catalog row arrow, bottom nav polish
Hero: pagination dots and each slide's title/badges/description were
both independently bottom-aligned in the same Box, so the dots sat
right on top of (or overlapping) the last line of the synopsis
instead of below it with a gap. Increased the slide content's bottom
inset from 20dp to 44dp so it stops short of the dots row.

Catalog rows: "View All" text replaced with a right-arrow icon,
matching the reference's icon-only affordance.

MobileBottomNav: dropped the top hairline divider and switched to a
flat pure-black background (was a near-black AMOLED-conditional
color), bumped icon size 26->28dp and vertical padding 10->16dp for
more breathing room - closer to the reference's cleaner, more open
spacing.

Compile-verified on shared commonMain, iosMain, and both Android
flavors.
2026-07-14 15:16:03 +03:00
KhooLy
9fb1fad5cb Simplify Home hero overlay bar and native bottom nav
Home hero: dropped the Play button (whole hero is already clickable),
made the age-rating badge plain text instead of a filled accent pill
so all metadata reads consistently, and merged the tabs row + bell/
avatar row into one - Home/Series/Movies on the left, notifications +
avatar on the right, both overlaid on the hero image together instead
of stacked as two separate rows.

MobileBottomNav (Android's native bottom bar): removed the Settings
tab entirely - it was actually rendering as a profile-avatar-with-
quick-switch-menu, which is now redundant with the Home hero's avatar
(wired to the same Settings destination). Bottom nav is Home/Discover/
Calendar/Library only. Also dropped the text labels and the selected-
state background pill per direction - just icons now, filled variant
when selected vs outline otherwise, matching the "make icon filled
when clicked, that's it" ask exactly.

Compile-verified on shared commonMain, iosMain, and both Android
flavors.
2026-07-14 15:10:56 +03:00
KhooLy
48f2488ee0 Overlay Home top bar/tabs on hero, center hero content, drop bottom-nav Profile
The bell+avatar row and Home/Series/Movies tabs were rendered as a
separate solid-background section stacked above the hero - reference
image has them overlaid directly on the hero image, scrolling away
together with it. Moved FluxaHomeOverlayBar into the same Box as
FluxaHomeHero with a top-down scrim gradient for legibility over
varying artwork, instead of a fixed opaque header.

FluxaNavigationBar (the shared bottom nav, used on iOS since Android
renders its own native MobileBottomNav instead) was iterating every
FluxaDestination including ProfileList/AddonStore/Auth, none of which
belong in a persistent tab row - they're already reached other ways
(the new Home avatar, Settings' manage-addons button, the login flow).
Introduced FluxaBottomNavDestinations restricted to the six real
top-level screens.

Hero content is now center-aligned (logo, title fallback, badges,
description) instead of left-aligned, description line-height
tightened, and runtime is now shown in the badge row for movies -
CatalogItemUiModel didn't carry runtimeLabel before, only Detail's
model did, despite Meta.runtime being available all along.

Compile-verified on shared commonMain, iosMain, and both Android
flavors.
2026-07-14 15:02:59 +03:00
KhooLy
46323a5043 Add Home top bar with real content filter, notifications list screen
No logo/wordmark anywhere (per explicit direction) - just a
notification bell and the active profile's avatar top-right, with a
Home/Series/Movies tab row underneath.

The tabs aren't decorative: HomeViewModel already had a working
currentFilter/setFilter("all"/"movie"/"series") mechanism used
internally for billboard filtering, just never exposed through the
shared UI layer. Added CatalogAction.FilterChanged and
CatalogHomeDataSource.setFilter() to wire it through properly -
AndroidCatalogHomeDataSource now threads currentFilter into
orderHomeCategories() too, which already supported a filter param
that was previously always called with the "all" default.

Notification bell opens a new NotificationsScreen, modeled as an
overlay on FluxaAppState (same pattern as the Detail overlay - a
showNotifications flag, openNotifications()/closeNotifications(), no
new FluxaDestination so it doesn't leak into the enum-driven bottom
nav). It reuses CalendarUiState/CalendarDataSource directly - no new
data source - and groups the existing flat release list into Coming
Soon / New / Later sections using kotlinx-datetime (added as a new
shared dependency; date arithmetic on ISO date strings needs real
calendar math, not string comparison, and there's no JVM-only
java.time available in commonMain). Tapping a notification reuses the
existing CalendarAction.ItemSelected handling to open Detail.

Compile-verified on shared commonMain, iosMain, and both Android
flavors. Visual result and date-bucketing logic unverified against a
running app.
2026-07-14 14:54:39 +03:00
KhooLy
816b9063aa Polish Detail screen, redesign Home hero, add screen transitions
Detail screen fixes from review:
- Resume button content is now actually centered (outer Column lacked
  horizontalAlignment).
- Download buttons (primary + per-episode) get a download icon.
- Season selector is now a visible pill with a dropdown-arrow icon
  instead of plain unstyled text, and opens a ModalBottomSheet listing
  seasons instead of an inline expand - easier to tell it's tappable
  and consistent with the "bottom sheet, not dropdown" request.
- Episode thumbnails are bigger (was fixed 120dp, now split evenly
  with the text column) and synopsis moved to a full-width line below
  the thumbnail+title row instead of being squeezed into the narrow
  text column beside the thumbnail.
- Hero renders the title logo (MetaDetail.logo, now exposed as
  DetailUiModel.logoUrl... already existed, just unused) when
  available, falling back to text title otherwise.

Home hero: same logo-over-text treatment, plus an age-rating badge,
season count, and a description line - all sourced from Meta fields
(description/ageRating/seasonsCount) that were already fetched but
not exposed on CatalogItemUiModel before now.

Screen transitions: FluxaApp's main content switch (destination
changes, opening/closing Detail, entering profile edit) was an
instant when-block swap with zero animation. Wrapped it in
AnimatedContent keyed on a computed screen identity, with a fade+scale
enter and fade exit - the "no animation when navigating" gap.
Per-screen composables switched from Modifier.weight(1f) (needs
ColumnScope, no longer valid once nested inside AnimatedContent's
content lambda) to Modifier.fillMaxSize(), with weight(1f) moved to
the AnimatedContent itself.

Not addressed, scoped out: HBO Max's top bar (app logo, notification
bell, profile avatar) and category tab row (Home/Series/Movies/...)
on the Home screen reference image - Fluxa's Home has no matching
data model (no per-category content-type filter, no notification
system) and the existing bottom FluxaNavigationBar already serves
destination-switching, so adding a duplicate top tab row felt like
UI without backing functionality rather than a real gap.

Compile-verified on shared commonMain, iosMain, and both Android
flavors. Visual result unverified - no way to render Compose UI in
this environment.
2026-07-14 14:38:22 +03:00
KhooLy
00172e9320 Redesign shared Detail screen to match streaming-app reference layouts
Full-bleed tall hero (poster/backdrop) with title, type eyebrow, and
metadata (age rating badge, year, season count or runtime) overlaid
directly on the image via a bottom gradient scrim, matching a
Netflix/HBO Max-style detail layout instead of the earlier flat
sectioned one.

Adds a proper close/back affordance and share icon to the shared
Detail screen for the first time - it previously had no way to
dismiss itself from within the UI. Threaded onBack through
FluxaApp/FluxaAppHost/AppRoutesHost: the in-Home overlay now clears
itself locally (FluxaAppState.clearDetail()), while the standalone
mobile route pops the nav stack via navigateBackSafely - reusing the
same mobileDetailScreen-null guard pattern already established for
onDetailNavigationEvent, so the overlay and route cases don't collide.

Play becomes Resume when there's saved progress (shows "Resume S1 E2"
using the resume episode's season/number when resolvable), with a
Restart button underneath that dispatches the new
DetailAction.Play(fromStart = true) to explicitly bypass resume -
DetailNavigationLogic.forPlay/forStream now take a fromStart flag
that forces progress to 0 regardless of saved position.

Extends DetailUiModel with castNames and ageRating (from MetaDetail,
already fetched, just not exposed to the shared UI model before) and
DetailEpisodeUiModel with runtimeLabel (from Video.episodeRuntime).

Compile-verified on shared commonMain, iosMain, and both Android
flavors. Visual result not verified against the reference images in a
running app - no way to render Compose UI in this environment.
2026-07-14 14:23:28 +03:00
KhooLy
909906fab3 Build real Stremio login for iOS Auth, Keychain-backed session storage
Fixes AppleAuthDataSource: continueWithNuvio/continueWithStremio/
continueWithoutAccount were incorrectly routed through Swift
notifications - Android handles all three as pure local UI-state
transitions (no network), so this matches that instead of adding
unnecessary indirection.

submit() for the Credentials stage now posts a real request that
FluxaAppleAuthStartup.swift picks up: FluxaAppleStremioAuthClient
mirrors StremioService.kt's api.strem.io/api/login and /api/register
endpoints via URLSession, and the resulting session (id/email/authKey)
is persisted through FluxaAppleAuthStore using the Keychain generic-
password API (kSecClassGenericPassword, accessible-after-first-unlock),
not UserDefaults - this is the first credential-bearing data iOS
persists, unlike the existing UserDefaults-backed local stores.

Nuvio login/import is out of scope here: Android's version pulls
profile/addons/library/progress/history/collections through
NuvioAccountImportCoordinator, which needs library and addon-store
integration iOS doesn't have yet. The Nuvio stage transition works
(continueWithNuvio), but submitting Nuvio credentials currently has no
Swift listener - same "reachable but inert" scope already established
for AddonStore.

The error-state message on failed Stremio login is a hardcoded English
string rather than routed through AppStrings, since calling Kotlin's
AppStrings.t() from pure Swift needs framework-export verification I
can't perform without Xcode.

Caveat: this Swift code (Keychain access, URLSession networking) is
unverified - no Xcode/macOS toolchain available in this environment.
Only the Kotlin side (AppleAuthDataSource, all Gradle targets) is
compile-checked. Needs a real Xcode build and device/simulator test
before shipping.
2026-07-14 13:56:30 +03:00
KhooLy
311968075d Unify TV and mobile Detail navigation decisions onto DetailNavigationLogic
Extracts the "which stream/episode plays, is it cs3, how much resume
progress" decision into a pure DetailNavigationLogic object in
commonMain. DetailStore (used by mobile's shared CMP Detail screen)
now delegates to it instead of carrying its own copy.

DetailRoute.kt (Android TV's legacy Detail route - mobile no longer
reaches this file after the earlier Detail route migration) now calls
the same DetailNavigationLogic functions instead of its own hand-
rolled duplicate of the same branching, so both platforms make this
decision through one code path. TvDetailScreenContent's rendering is
untouched - it still reads DetailViewModel directly, since it needs
richer domain data (MetaDetail/Video/Stream) than the lightweight
shared Detail UI models carry.

Preserves the movie-vs-series distinction from the original TV logic:
Play button on a movie always targets a null video id (no accidental
resume-progress carryover), matching the legacy behavior exactly.
2026-07-14 13:51:34 +03:00
KhooLy
f27bb23837 Fix Detail resume-progress and duplicate-navigation bugs from Phase 1
Two real bugs surfaced while scoping the TV Detail work:

1. DetailNavigationEvent didn't carry resume progress, so
   AppRoutesHost's mobile handler applied mobileDetailScreen's
   initialProgress unconditionally to whatever stream/episode got
   played - including a different episode than the one that was
   actually paused. DetailStore now computes progress the same way
   the legacy DetailRoute.kt always did: only resume if the target
   video id matches the saved one, otherwise start at 0. Fixed the
   underlying resume/lastVideoId resolution in AndroidDetailDataSource
   too, which wasn't honoring the DetailRequestUiModel's own
   lastVideoId/initialProgress overrides (screen.lastVideoId ??
   savedPlayback in the legacy code) - it only ever looked at
   savedPlayback.

2. onDetailNavigationEvent fired unconditionally on the shared
   FluxaAppHost instance, which is reused for both the in-Home Detail
   overlay and the standalone Screen.Detail route. Clicking Play in
   the overlay already had a working navigation path via
   onSharedPlayRequested (a separate, pre-existing callback) - Phase 1
   added a second, redundant navigateTo() call on top of it for every
   overlay Play/stream click. Guarded the handler to only act when
   mobileDetailScreen is non-null (i.e. actually in the standalone
   route), restoring the overlay to its original single-navigation
   behavior.
2026-07-14 13:46:26 +03:00
KhooLy
153bbd4452 Wire iOS Settings/AddonStore/Auth into AppleFluxaPlatformServices
AppleSettingsDataSource is a real, fully local implementation backed
by NSUserDefaults - all ~50 SettingsUiState fields (general,
appearance, playback, subtitles, advanced, addons, downloads, system,
TMDB account toggles) persist across launches and round-trip through
update methods immediately, with no backend dependency since Settings
is pure preference storage. Content-feed toggle/reorder actions and
sync-disconnect stay no-ops since they depend on addon catalog/sync
integration iOS doesn't have yet.

AppleAddonStoreDataSource and AppleAuthDataSource are compile-only
bridges: real UI-state handling (input text, form fields, detected
input type) plus NSNotificationCenter action dispatch following the
established Apple*DataSource pattern, but the notifications currently
have no Swift-side listener. Unlike Detail/Library/Discover/Calendar,
Android's AddonStore/Auth data sources call direct Kotlin services
(PluginManager, StremioRepository, AppContainer.authService) with no
Rust-headless or existing Swift-networking equivalent to bridge to -
building real login/addon-manifest networking for iOS is a separate,
larger effort involving actual security/product decisions (session
storage, Keychain use) that shouldn't be guessed at.

AppleFluxaPlatformServices now implements FluxaSettingsServices,
FluxaAddonStoreServices, FluxaAuthServices, making the shared
Settings/AddonStore/Auth CMP screens reachable via FluxaAppHost on
iOS. Compile-verified across shared (iosMain + commonMain) and both
Android flavors.
2026-07-14 13:32:59 +03:00
KhooLy
d169ecb9cb Fix pre-existing iOS compile errors in Discover/Library/Profile data sources
These three files were already broken in the working tree, written
against older shapes of DiscoverDataSource/LibraryDataSource/
ProfileDataSource before those contracts evolved (loadMore() added to
Discover; LibraryUiState restructured from a flat watchlist into
planned/completed/favorites/collections/downloadGroups; Profile's
settings fields split out into the separate SettingsDataSource/
SettingsUiState feature, leaving ProfileDataSource with pin/biometric/
save-profile methods only).

AppleDiscoverDataSource: added loadMore() posting the current filters
as a load-more notification.

AppleLibraryDataSource: updateJson() now decodes the current
LibraryUiState shape; added createCollection/renameCollection/
deleteCollection/cancelDownload following the existing notification-
dispatch pattern.

AppleProfileDataSource: rewritten against the current ProfileDataSource
interface (no more SettingsUiState) - single default Apple profile
backed by NSUserDefaults for name/avatar/pin/biometric flag, matching
the local-store pattern already used by FluxaAppleLibraryStore.swift.

:shared:compileKotlinIosSimulatorArm64 now builds clean.
2026-07-14 13:26:18 +03:00
KhooLy
a383bbe815 Fix AppleDetailDataSource for extended DetailDataSource interface
The Phase 1 DetailContracts extension (season/episode/streams/
downloads) added five new abstract members to DetailDataSource that
AppleDetailDataSource didn't implement, breaking the iOS build. Adds
NSNotificationCenter dispatch for each, following the same
postNotificationName pattern the existing loadDetail/toggleWatchlist
methods use, plus the missing runtimeLabel field in the JSON decode.
2026-07-14 13:24:08 +03:00
KhooLy
16e8db04c1 Extend shared Catalog Home contract for TV: billboard, row metadata
Adds billboard/hero state, per-row cardLayout/isActionRow/topTenEnabled
flags, and an ItemFocused action to CatalogHomeUiState, keeping
CatalogItemUiModel itself unchanged (still no domain objects - Meta
stays out of anything a LazyRow/LazyColumn renders, since Stream/Meta
carry mutable fields that make Compose unable to skip recomposition).

AndroidCatalogHomeDataSource now populates these from HomeViewModel's
existing billboard/category state, and exposes resolveMeta(id, type)
as an Android-only boundary helper so navigation code can reconstruct
a full Meta for Screen.Detail/Screen.Player without it ever touching
the UI layer - same resolve-at-the-boundary pattern used for streams
in AndroidDetailDataSource.resolveStream().

Groundwork for migrating the Android TV Home screen off HomeViewModel
onto the same shared CatalogHomeStore mobile already uses; the TV
Compose screen itself is not yet rewired.
2026-07-14 13:14:03 +03:00
KhooLy
db10acd3b2 Route Screen.Detail through shared CMP Detail screen on Android mobile
Adds season/episode picker and sources list to the shared Detail
composable, switches FluxaAppState's selectedDetail to carry a
DetailRequestUiModel directly (instead of a full CatalogItemUiModel)
so the standalone Screen.Detail route can seed it without going
through a catalog item first, and threads a detailRequest +
onDetailNavigationEvent pair through FluxaAppHost.

AppRoutesHost now builds a DetailRequestUiModel from Screen.Detail and
hands the shared Detail screen's navigation decisions (play a
specific stream vs. go pick sources, based on cs3: id prefixes) back
to Screen.Player / Screen.Sources, reconstructing the full Stream
object via AndroidDetailDataSource.resolveStream() so headers/
subtitles aren't lost. TV still uses the legacy DetailRoute for now.
2026-07-14 13:06:39 +03:00
KhooLy
edf4a9d7a3 Extend shared Detail contract with season/episode/streams/downloads
Adds season/episode picking, stream listing/filtering, and download
actions to the shared Detail feature (DetailContracts, DetailStore),
and wires AndroidDetailDataSource to the existing DetailViewModel to
back them. Navigation decisions (play directly vs. go to source
selection, based on cs3: id prefixes) now live in DetailStore and are
exposed via a DetailNavigationEvent flow for the route layer to
consume.

First slice of migrating the standalone Detail route off the legacy
Android-only DetailScreen onto the shared CMP Detail screen.
2026-07-14 12:59:22 +03:00
KhooLy
0f588b67e7 Fix Playback icon, enlarge Settings row icons, fix bottom-bar overlap
Swaps the Playback icon from a circled play button to a video/on-demand
icon, and bumps every Settings row icon (nav rows, brand action rows) to a
larger size for better legibility.

Adds bottom content padding (120dp, matching Home/Discover) to the shared
Settings and Library screens so their content isn't hidden behind the
mobile bottom navigation bar, which neither screen accounted for.
2026-07-14 01:48:16 +03:00