Commit graph

234 commits

Author SHA1 Message Date
KhooLy
89c8c90669 Add local mutation timestamps for watchlist and watched state
Lays the groundwork for timestamp-based conflict resolution against
Trakt/Simkl/Stremio (matching the pattern Nuvio already uses), by
giving watchlist entries and watched episodes an updatedAt column and
a soft-delete tombstone table so removals carry a comparable
timestamp instead of vanishing on hard delete. Progress already had
updatedAt; this brings watchlist/watched up to the same level.
2026-07-18 18:36:18 +03:00
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
92e7518d70 Fix CloudStream episode matching to avoid cross-season false positives
When a provider lists the requested season but not the requested
episode, fall through to no-match instead of an episode-only match
that could silently grab a different season's same-numbered episode.
2026-07-18 15:01:52 +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
ae3e135b6f Retire FluxaCoreNative's hand-written JNI in favor of core_invoke
Every stateless method on FluxaCoreNative now calls the corresponding
core_invoke route via FluxaCoreUniFfi instead of a hand-written
external fun; only the handle-lifecycle functions (headless engine,
app core state) keep their dedicated JNI bindings. NuvioCoreBridge and
ExternalLibraryClient's generic coreInvoke calls move onto the same
UniFFI path. Public method signatures are unchanged, so no other call
site needed to change.
2026-07-18 15:01:38 +03:00
KhooLy
c5df51f68a Relocate UniFFI codegen and bindings from app module to data module
FluxaCoreUniFfi.kt and the UniFFI-generated com.fluxa.core.uniffi
bindings only existed in the app Gradle module, but FluxaCoreNative.kt
(data module) needs to call into them and data cannot depend on app.
Moves the generateFluxaCoreUniFfiBindings task and the Kotlin facade
into data, which app already depends on.
2026-07-18 15:01:27 +03:00
KhooLy
276c4fb766 Move calendar business logic to fluxa-core via coreInvoke
Season-candidate selection, widget row formatting, and release
notification filtering now call the equivalent Rust calendar_plan
functions through core_invoke instead of duplicating that logic in
Kotlin.
2026-07-18 15:01:13 +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
39b66eadf2 Freeze last frame on screen while scrubbing instead of disabling live thumbnails
Restores the live seek used to capture preview thumbnails during
scrubbing, but hides its side effect: when a drag starts, the current
surface frame is captured via PixelCopy and drawn full-screen under the
controls until the drag ends. The real player can jump between buckets
for thumbnail capture without the visible video changing; the frame you
started dragging on stays on screen until release commits the seek.
2026-07-17 17:53:34 +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
86c169de48 Suppress auto-PiP when launching external player
Opening the external player fires ON_PAUSE on MainActivity, and
PlayerLifecycleEffect auto-entered PiP because the player screen was
still active and the profile PiP setting was on. Gate the auto-enter
behind a suppression flag set for the duration of the external launch,
cleared on activity result and ON_RESUME.
2026-07-17 16:50:46 +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
61e5b2bd52 Fix Discover crash on scroll from duplicate grid item keys
Addon catalogs can return overlapping items across paginated skip
offsets, so the discover results list could contain the same
type:id twice. LazyVerticalGrid requires unique keys and crashed
with IllegalArgumentException once a duplicate scrolled into view.
Dedupe results in the coordinator where they're assigned.
2026-07-17 16:03:47 +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
6cdb993478 Collapse Screen/AppNavigator into FluxaAppHost's existing nav state
FluxaAppHost already owns real navigation internally via FluxaAppState
(tab switching, detail selection, category browsing, profile editing,
source selection, notifications) - Screen/AppNavigator/AppRoutesHost's
12-variant sealed class and full backstack were solving a problem
FluxaAppHost had already solved. FluxaNavigationStore/FluxaRoute (the
type originally targeted for this) turned out to be orphaned scaffolding
with no real consumer.

Replaces the Android-side Screen stack with the handful of things
FluxaAppHost actually needs seeded from outside: current FluxaDestination
(+ a one-level previousDestination for the three non-clearing pushes:
search, addon store, connect-account), authStartOnNuvio, and a
PlayerLaunchRequest since Player isn't part of the shared UI yet. Deletes
AppNavigator.kt, AndroidFluxaNavigator.kt (confirmed dead - zero external
references), the Screen sealed class, and ~340 lines of AppRoutesHost's
Screen->FluxaDestination translation plus its dead legacy AnimatedContent
dispatch (Screen.Detail was never actually constructed anywhere outside
that dead navigator, confirming Detail navigation already flows entirely
through FluxaAppHost's internal state).

Verification gap: compiles clean and existing tests pass, but this is
the app's core navigation/back-button/PiP-lifecycle wiring and there is
no emulator available this session to click through it. Needs a manual
pass before shipping: cold launch with/without profiles, Home-Detail-
Player-back, hardware back from every top-level tab, PiP entry, connect-
account auth flow, profile switch, and download-item tap to Player.
2026-07-17 00:37:55 +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
7085c5c7bf Fix iOS Kotlin interop build
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
2026-07-16 16:22:41 +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
e5581918cd Migrate Meta/MetaDetail models from Gson to kotlinx.serialization
Gson is JVM-only and can't live in shared commonMain, which was the real
reason Meta/MetaDetail/Video/CastMember/DetailTrailer were stuck in the
Android-only app module. Ports the existing JsonElement-tree-based custom
parsing (flexible alternate-key extraction, free-form cast/trailer JSON
tolerance) to kotlinx.serialization's equivalent JsonObject/JsonElement API,
using @JsonNames for simple alternate keys and custom KSerializers for
CastMember/DetailTrailer/MetaDetail where the existing Gson deserializers did
manual extraction.

StremioAddonResourceClient now parses addon catalog/meta responses via
kotlinx.serialization (stremioJson) instead of Gson for these types; Gson
stays in place for Stream/SubtitleData, which are out of scope here.
StremioService.getMetaDetail now returns the raw response body instead of
relying on Retrofit's automatic (Gson-based) conversion, since the models it
returns no longer carry Gson annotations.

Verified on-device: addon catalog list parsing (Home rows), billboard, and
meta detail (title/year/runtime/description) all render correctly against
real addon responses.
2026-07-16 00:36:08 +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
67f98d3119 Remove dead legacy code identified in KMP migration audit
Deletes files with zero remaining references (leftovers from already-removed
screens: AddonStoreScreen, MobileCalendarScreen, the old library JSON
import/export UI, unused stream/theme helpers). Trims LibraryCollectionsJson.kt,
StreamSourceUi.kt, HomeContentFormatters.kt, StreamSourceSelectionPolicy.kt, and
LibraryModels.kt down to only their still-referenced declarations.
2026-07-16 00:05:01 +03:00
KhooLy
3fc3fb0b44 Prevent stale addon catalogs on home 2026-07-14 22:59:23 +03:00
KhooLy
16601ba329 Refresh shared addon state after changes 2026-07-14 22:55:35 +03:00
KhooLy
36f8671199 Preserve local Nuvio profile presentation 2026-07-14 22:50:34 +03:00
KhooLy
273fb1ef70 Sync addon changes to Nuvio 2026-07-14 22:42:19 +03:00
KhooLy
7c26c9d832 Refresh Nuvio after server recovery 2026-07-14 22:34:56 +03:00
KhooLy
83e2cbd98d Clear removed Nuvio addons on import 2026-07-14 22:32:54 +03:00
KhooLy
4a15336389 Replace Nuvio watchlist on import 2026-07-14 22:32:14 +03:00