Commit graph

45 commits

Author SHA1 Message Date
KhooLy
56e2697dfa Fill missing and untranslated Turkish i18n strings
Add missing auto.connect key and translate 33 settings, player, and
notification strings that were still showing English in the Turkish locale.
2026-07-05 00:04:52 +03:00
KhooLy
4aa8a4f65a Ignore .gradle state dirs in included builds
/.gradle/ only matched the repo root, so build-logic/.gradle lock and
execution-history files were tracked and dirtied the status after every
build.
2026-07-03 23:45:20 +03:00
KhooLy
c3f97ad7bc Show episode name on Continue Watching cards
lastEpisodeName is saved as "S1, E3: Name" but the prefix-stripping
regex only matched "S1: E3" and "S1 E3" forms, never the comma form the
app itself writes. The unstripped title then tripped the duplicate-code
guard in continueWatchingEpisodeLabel, which discarded it and left the
bare "S1, E3". The regex now handles the comma form, and a title that
already carries the code is shown as-is instead of dropped.
2026-07-03 23:43:58 +03:00
KhooLy
852373a4c1 Fix libass fonts and glyph rendering to match mpv
Embedded MKV font attachments now reach libass reliably. The font scan
was disabled for the local torrent proxy, which is where all torrent
playback lives, so styled fansub tracks fell back to system fonts.

Font sourcing is now layered: a persistent PrefixCaptureBuffer harvests
attachments from the bytes ExoPlayer already downloads (the old capture
died with each ExtractorInput ExoPlayer recreated per load), and an HTTP
scan locates the Attachments element via the SeekHead and fetches it
exactly instead of guessing with an 8 MB prefix. The scan waits for the
player's own connection to open before touching the proxy, backs off
hard between retries, and aborts once fonts exist from any source.
Fonts that arrive after renderer creation recreate it and replay
buffered events. Truncated attachments are dropped instead of being fed
to FreeType half-parsed.

Two compositing bugs caused the sheared "fake italic" glyphs: coverage
rows are packed 4-byte aligned but ALPHA_8 copyPixelsFromBuffer consumes
bitmap rowBytes, so odd-width glyphs skewed one pixel per row; and the
glyph cache was keyed by the native ASS_Image bitmap pointer, which
libass reuses across frames, serving stale glyphs. Rows are repacked
when strides differ and the cache is keyed by content hash.

Startup cost is now near zero: renderer creation moved off ExoPlayer's
loader thread onto the libass HandlerThread, and XDG_CACHE_HOME points
at app storage so fontconfig's /system/fonts scan (~2 s) is cached and
subsequent inits take ~80 ms. libass diagnostics stream to logcat via
ass_set_message_cb.
2026-07-03 23:43:50 +03:00
KhooLy
3c9893d0b8 Rebuild libass renderer: PTS clock, SurfaceView, HandlerThread, ALPHA_8 glyphs
Five architectural changes:

1. PTS-driven clock: VideoFrameMetadataListener on ExoPlayer delivers the
   exact presentation timestamp of each video frame. LibassRenderThread
   drives rendering at that PTS rather than polling currentPosition.

2. Translucent SurfaceView: NativeLibassSubtitleView becomes a SurfaceView
   with PixelFormat.TRANSLUCENT and setZOrderMediaOverlay(true). Its surface
   is composited by the hardware layer between the video surface and the Compose
   window, with zero main-thread involvement during playback.

3. Single HandlerThread ownership: LibassRenderThread owns the renderer handle.
   All operations (render, addEvent, clearEvents, surface lifecycle, teardown)
   are Handler messages to this thread. The ReentrantLock is gone — no
   contention because there is no sharing.

4. ALPHA_8 glyph patches via lockHardwareCanvas: nativeRenderImages returns the
   ASS_Image list as flat int/byte arrays instead of blending into a full ARGB
   frame. Kotlin draws each glyph as a tiny ALPHA_8 bitmap on the hardware
   canvas with a tinted Paint. Glyph bitmaps are cached by libass bitmap pointer,
   so a static or moving subtitle re-draws cached textures at new positions and
   uploads nothing.

5. detect_change gating: nativeRenderImages returns -1 when detect_change == 0.
   Kotlin skips lockHardwareCanvas entirely — zero work when the frame is
   unchanged. Paused playback is fully idle.

LibassEventRelay routes setHeader, addEvent, clearEvents through the thread.
relay.activeRenderer remains a StateFlow<NativeLibassRenderer?> for the existing
PlayerPlaybackSurface observer. Tests updated to drain the HandlerThread between
async relay operations and direct render() calls.
2026-07-03 20:20:24 +03:00
KhooLy
c58d1cb0d7 Fix libass relay renderer never activating for HTTP streams
Pre-extraction is skipped for non-local streams so embeddedNativeAssTracks
is always empty, which meant embeddedSubtitle was always null, which meant
the relay renderer was never assigned as the active renderer. Relay renderer
should win whenever there is no external subtitle selected.
2026-07-03 19:55:43 +03:00
KhooLy
5254b5152f Miscellaneous fixes: null safety, stream quality label, hero trailer, Nuvio defaults
- HomeSearchFocusStateHolder: guard against null list fields in core state snapshot
  to avoid NPE on first-run or partial state
- StreamSourceUi: skip appending quality label when the filename already contains it
- MobileDetailHero: accept featuredTrailer parameter and fall back to it when no
  actively selected trailer exists, so the hero auto-plays on open
- SearchSharedComponents: SearchEmptyState now accepts an optional icon and centers
  its title text
- MobileSettingsCategoryDetail: switch from loadedCs3ApiNames to
  loadedCs3CatalogFeedOptions StateFlow for per-catalog feed management
- FluxaAndroidHeadlessEnvironment: import ScraperActor and related CS3 types;
  pass requestedVideoId from effect payload into stream loading
- build.gradle.kts: update Nuvio Supabase URL default and add publishable key default
2026-07-03 12:54:25 +03:00
KhooLy
0fcda08210 Replace avatar picker with photo file picker in profile edit screens
- Remove AvatarLibrary/AvatarProvider usage and the LazyRow character grid from
  both mobile and TV profile edit screens
- Wire ActivityResultContracts.GetContent for photo picking via system file chooser
- Add openSettings hook to CloudStream Plugin base class
- Add player_surface_crop_animator_tag and player_surface_crop_target_tag to ids.xml
  for animated crop-to-fill tracking in PlayerPlaybackSurface
2026-07-03 12:54:13 +03:00
KhooLy
e83ed4d308 Mobile bottom nav: add labels and outlined icon variants
- Add outlined icon variants (Home, Explore, DateRange, VideoLibrary, Settings)
  to FluxaIcons for the unselected state
- MobileBottomNavItem now carries a label string; the nav bar renders it beneath
  each icon using AppStrings so it respects the active language
- Slightly darken nav background and inactive tint for legibility
2026-07-03 12:54:05 +03:00
KhooLy
4020c3663b Add TvNavActions and top-bar layout support to TV screens
- Define TvNavActions data class and TvNavDestination enum in TvHomeNavRail
- Thread TvNavActions into Explore, Search, Watchlist, and Settings so those
  screens can render the nav rail or top bar themselves instead of being bare
- TvExploreScreen, TvSearchScreen, and TvSettingsComponents adapt their layout
  based on safeTvNavLayout == "top": rail gutter 126dp vs top-bar gutter 56dp,
  content top padding 40dp vs 108dp
- Settings screen: add "general" and "appearance" tabs, rename "downloads" to
  "content", reorder to match new tab structure; apply rail/top gutter
- MainActivity back-navigation: if activeProfile is null and profile list is
  empty, navigate to Login instead of looping on Home
2026-07-03 12:54:00 +03:00
KhooLy
f5afade800 Player: fix zoom/aspect ratio, ExoPlayer crop, and IPT PQ C2 tone-map wiring
- Account for pixelWidthHeightRatio when computing exoVideoAspectRatio so
  anamorphic streams fill correctly
- Replace gestureState.pinchBelowFitAccum with a snappedToFillThisGesture flag:
  one pinch-out snaps to fill, subsequent movement cycles the zoom overlay
- Clamp fillScale to MIN_MANUAL_FILL_SCALE (1.34) so landscape content always
  fully covers the screen before the crop overlay appears
- ExoPlayerEngine.setZoomed now sets VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
  vs SCALE_TO_FIT instead of being a no-op
- Wire iptPqc2UseHdr and iptPqc2PreDecide from resolved stream data into the
  engine request so the IPT PQ C2 tone-map effect receives its configuration
2026-07-03 12:53:17 +03:00
KhooLy
1acc05875d Player: auto-fallback through CloudStream links on playback error
- On ExoPlayer error during CS3 playback, try the next available stream URL
  automatically (round-robin, skipping already-failed URLs stored in
  failedAutoFallbackUrls) before showing the error overlay
- Stall watchdog passes the same fallback path so buffering timeouts also
  cycle to the next link on CS3 content
- Episode metadata line format: use "S1, E2: Title" separator and handle
  Video objects that already embed season/episode without needing a catalog fetch
2026-07-03 12:53:07 +03:00
KhooLy
b91c91f205 CloudStream per-catalog feeds and home billboard integration
- Add Cs3CatalogFeedDescriptor and per-catalog feed keys (cs3CatalogFeedKey) so
  each CS3 catalog gets its own home row instead of one per plugin
- Build CS3 metadata feed options from per-catalog descriptors in buildCs3MetadataFeedOptions
- HomeBillboardLoader now fetches CS3 feeds alongside stremio addon feeds for
  billboard pool population
- Stream.kt: send Cloudstream referer header lowercase ("referer") to match how
  Media3 passes custom request properties
- HomeContentFormatters: skip cs3: video IDs in parseShortVideoId to avoid
  incorrect episode line parsing
- HomeLayoutHelpers: include poster as fallback in mobileHeroArtworkCandidates
2026-07-03 12:53:00 +03:00
KhooLy
05083b3f0b Improve plugin auto-update: failure reporting, deduplication, separate notification IDs
- Return UpdateReport(updatedPlugins, failedPlugins) from auto-updater so failures
  are tracked separately from successes
- Show a distinct notification when one or more plugins fail to update, using
  separate notification IDs so the success and failure toasts don't stomp each other
- Add installKey() to Plugin for stable deduplication by (name, repositoryUrl) pair;
  use it in isPluginInstalled to avoid duplicate installs on re-run
- Add i18n keys notification.plugins_update_failed_title/body in en-US and tr-TR
2026-07-03 12:52:50 +03:00
KhooLy
585cafe266 Remove hardcoded avatar image library
Delete the bundled character avatar JPGs and the AvatarLibrary/AvatarProvider
Kotlin files that referenced them.
2026-07-03 12:51:30 +03:00
KhooLy
301f0f07b5 Add image cache keys to cast photos, trailer thumbnails, and similar item posters
CastMemberCard, TrailerCard, and SimilarContentCard now build ImageRequests with
memoryCacheKey and diskCacheKey so images survive across recompositions and
LazyRow recycling without being re-fetched.
2026-07-03 12:48:43 +03:00
KhooLy
cfc7e251a8 Reset TV episode list scroll to start on season change
When the user picks a different season on the TV detail screen, the episode
LazyRow now scrolls back to position 0 so the first episode of the new season
is in view instead of leaving the indicator wherever it was in the previous
season's list.
2026-07-03 12:46:35 +03:00
KhooLy
0e3882eff8 Add seek acceleration to TV seekbar on held D-pad
Consecutive same-direction presses ramp up the jump amount: first 2 presses use
the profile seek interval, presses 3-7 triple it, and presses 8+ sextuple it.
Direction change or focus loss resets the counter. This lets quick taps do fine
scrubbing while holding the key covers large spans fast.
2026-07-03 12:45:24 +03:00
KhooLy
88d7e22ec2 Add skeleton loading rows to home screens on both platforms
While the home catalog is loading and no rows are available yet, both the mobile
and TV home screens now show 4 animated skeleton shelf placeholders instead of a
blank screen. The pulse animation matches the explore screen skeleton.
2026-07-03 12:41:07 +03:00
KhooLy
462de853ed Wire blurUnwatchedEpisodes setting to TV episode cards
EpisodeCard gains a blurUnwatched parameter that matches the alpha logic already
used in mobile MobileEpisodeRow: 0.5f for unwatched episodes when blur is on,
0.45f for watched episodes when blur is off. TvDetailScreenContent propagates
the profile setting so both platforms behave consistently.
2026-07-03 12:36:03 +03:00
KhooLy
08d719ab9c Apply FluxaDisplay to mobile settings heading and label texts
Section labels, row titles, display names, watch-time counters, and section
headers now use FluxaDisplay instead of the default system sans-serif, aligning
settings visual hierarchy with the rest of the app.
2026-07-03 12:34:50 +03:00
KhooLy
0080c63a6b Extend FOUC fix to TV detail screen
TvDetailScreenContent now accepts initialMeta and uses it as a hero background
fallback (background → poster → initialMeta?.background → initialMeta?.poster)
so the card's artwork is visible immediately while the full detail loads.
2026-07-03 12:30:30 +03:00
KhooLy
fd3f074069 Restore TV HomeScreen scroll position and shelf focus on back navigation
Save vertical scroll position and focused row index in HomeViewModel on dispose.
Restore them on re-entry: scroll to the saved position and focus the previously
active shelf's first card instead of always jumping to the hero play button.
Per-shelf FocusRequesters replace the single firstShelfFocusRequester so any
row can be individually targeted after navigation returns.
2026-07-03 12:29:41 +03:00
KhooLy
3fbf498dd2 Add image cache keys to uncached AsyncImage calls in 3 files
TvDetailScreenContent: detail background now uses ImageRequest with
memoryCacheKey("detail-bg:url"). OfflineDownloadPages: folder poster and
episode artwork wrapped in ImageRequest with downloads-group/episode keys.
TvLibraryEditors: URL preview field and folder row wrapped with keyed requests.
2026-07-03 12:24:55 +03:00
KhooLy
21cbc12506 Split mobile HomeScreen.kt (1078 lines) into focused composable files
Extract row composables into MobileHomeSections (MobileHomeHeroItem, HomeUserCollectionRow,
HomeCategoryRow), card composables into MobileHomeCards (HomeCatalogCard,
MobileCollectionFolderCard, layout helpers), and bottom sheet into
MobileHomeContinueWatching. HomeScreen.kt retains only the top-level composable,
MobileHomeRowSpec, and spec builder functions.
2026-07-03 12:20:22 +03:00
KhooLy
423609a0e3 Split TvSettingsSections.kt (818 lines) into focused section files
Dissolve the monolithic sections file into TvSettingsAccountSection,
TvSettingsAppearanceSection, TvSettingsContentSection, TvSettingsPlaybackSection,
and TvSettingsSystemSection. TvSettingsPreviews retains the preview composables.
All section functions stay internal for same-module access from TvSettingsScreen.
2026-07-03 12:20:12 +03:00
KhooLy
331b989ce8 Migrate color literals to FluxaColors tokens across 23 files
Replace hex color literals with named FluxaColors constants in library, error, TV
library/detail/hero, offline, settings tiles, addon store, welcome, profile, mobile
detail/settings/library/addons/auth screens.
2026-07-03 12:20:05 +03:00
KhooLy
44683af3c1 Migrate tween literals to AnimDuration tokens across 14 files
Replace all raw tween(N) integer literals with named FluxaDimensions.AnimDuration
constants in player overlays, sidebar, source panel, profile components, parents
guide, login, explore, detail, library, source, search, and route transitions.
2026-07-03 12:19:54 +03:00
KhooLy
c9ecb5477a Expand FluxaColors and FluxaDimensions design token sets
Add 10 semantic color tokens (backgroundAmoled, backgroundHeroNavy, backgroundNavy,
backgroundNearBlack, surfaceDark, errorRed, successGreen, infoBlue, subtitleRed,
subtitleBlue) and 20+ AnimDuration constants covering blink, quick, scaleAlpha,
fadeIn, contentExpand, cardFocusScale, settingsExpand, heroSnap, parentsContainer,
nextEpisode, progressRing, sidebarSlide, heroReveal, ambientColor, loginPulse, marquee.
2026-07-03 12:19:44 +03:00
KhooLy
75ef192177 Fix six libass bugs: multi-track mixing, duration inference, font dir, render perf, thread safety, and subtitle matching
Multi-track relay (high): relay now tracks selectedTrackId driven by
updateTracks(). LibassInterceptingTrackOutput drops samples for non-selected
tracks and re-primes the header (via headerGeneration counter) when the
selected track switches. NativeLibassSubtitleOverlay gates relayRenderer on
embeddedSubtitle != null so disabling subs actually stops drawing.

Duration inference (high): pendingBody is flushed in onSeekFlush() with
lastInterEventMs capped to 5s, fixing the last-subtitle-never-shown problem.
Duration cap lowered from 60s to 30s to reduce lingering on sparse dialogue.

Font fallback (medium): LibassInjectingExtractorsFactory now accepts fontsDir
and threads it through to setHeader(), matching what the local/external path
already did. C++ nativeCreate uses fonts_dir for ass_set_fonts_dir and probes
for a NotoSans-Regular.ttf default_font instead of passing nullptr.

Render loop (medium): C++ nativeRender returns jint (1=has image, 0=no image).
When detect_change==0 and bitmap dimensions are unchanged it skips memset+blend
entirely and returns the cached last_had_image. Kotlin onDraw uses the int
result to decide whether to draw and switches to postInvalidateDelayed(100)
during subtitle-free periods instead of postInvalidateOnAnimation at 60 fps.

Thread safety (low): ensure_api() replaced bool+flag with std::call_once so
the first-call race between ExoPlayer's loader thread and main is eliminated.

Subtitle matching (low): selectedNativeAssSubtitle now tries label then URL
then (label+language) before falling back to language-only, preventing two
external subs in the same language from selecting the wrong one.
fetchSubtitleBytes switches to OkHttp and forwards StreamRequestPolicy headers
so addon URLs that require Referer/auth don't 403.
2026-07-03 11:53:57 +03:00
KhooLy
adf7700f7d Fix mpv/ExoPlayer audio passthrough, channel layout, AO, and DV track selection
mpv:
- Set ao=audiotrack explicitly so the AudioTrack output is guaranteed
  (avoids openSLES which lacks encoded passthrough)
- Add audio-channels=7.1,5.1,stereo so mpv negotiates multichannel
  instead of defaulting to auto-safe stereo downmix
- Build audio-spdif at init time by querying AudioManager output device
  encodings; only advertises AC3/E-AC3/DTS/DTS-HD/TrueHD formats the
  device actually reports

ExoPlayer:
- Guard setVideoEffects(emptyList()) behind a videoEffectsActive flag:
  only call it when IptPqc2ToneMapEffect was actually installed, never
  on a clean path — the empty-list call triggers the GL VideoFrameProcessor
  pipeline in media3 1.10.1 which tone-maps HDR even for an empty effect list
- detectPreferredVideoMimeType now accepts Context and checks display +
  decoder DV capability first; on fully DV-capable devices it returns
  VIDEO_DOLBY_VISION so DV variant tracks are preferred rather than
  deprioritized behind H.265/H.264
2026-07-03 03:51:49 +03:00
KhooLy
54819aad7b Neutralize FluxaColors surface tokens and point settings back at them 2026-07-02 01:20:40 +03:00
KhooLy
8f1500ff4c Restore neutral gray settings palette over blue-tinted surface tokens 2026-07-02 01:19:19 +03:00
KhooLy
348c4ad4bb Clean up mobile settings visuals and align them with design tokens 2026-07-02 01:15:47 +03:00
KhooLy
876e7bcecb Prefetch nested shelf posters and size-qualify TV image cache keys
Home vertical lists on both platforms now compose the first four cards of
upcoming rows ahead of scroll via LazyListPrefetchStrategy. TV card
requests move to the size-qualified memory cache key helper mobile already
used, so the same URL requested at poster and expanded sizes no longer
collides under one key; the shelf neighbor prewarm and expanded-poster
prefetch write the matching keys.
2026-07-02 01:01:55 +03:00
KhooLy
c9fdda4274 Unify mobile and TV player chrome through shared tokens
Scrim heights and alphas, seekbar track height, and the buffer/track
colors now come from FluxaDimensions.PlayerChrome and FluxaColors, so both
players render the same chrome: 160dp/0.72 top scrim, 230dp/0.86 bottom
scrim, 4dp track (TV was 3dp).
2026-07-02 00:52:14 +03:00
KhooLy
f4f233f59d Stagger TV hero content reveal and consolidate motion durations
Billboard rotations now play one orchestrated moment: logo, metadata,
description, and buttons fade and rise in sequence, driven by a single
Animatable read inside graphicsLayer so the stagger never recomposes the
panel. Ad-hoc tween literals across hero, shelves, cards, and the ambient
tint move to FluxaDimensions.AnimDuration tokens; the hero title fallback
also picks up the display face.
2026-07-02 00:51:01 +03:00
KhooLy
327e869488 Fade TV shelf rows out under the rail gutter
Rows now dissolve at the left edge instead of hard-clipping. The DstIn
fade needs offscreen compositing, so it only engages once the row has
scrolled; rows sitting at their start render with no extra cost.
2026-07-02 00:49:44 +03:00
KhooLy
f2bb2d902e Tint hero scrims with a color sampled from the backdrop
rememberAmbientHeroColor reuses the existing Palette extraction (LRU
cached) to pull the dark muted tone of the current hero artwork, blended
toward the app background and animated between billboard rotations. TV
bleeds the tint under the shelf area behind the LazyColumn; mobile gets a
glow band above the hero fold.
2026-07-02 00:48:17 +03:00
KhooLy
a2e85be74c Release expanded-poster trailer player when playback stops
Each once-focused card kept its ExoPlayer instance alive until the card
left composition, so scrolling a shelf accumulated idle decoders. Release
on stop caps live trailer players at one.
2026-07-02 00:46:36 +03:00
KhooLy
a32c01faba Upgrade TV card focus treatment to scale, accent ring, and glow
Replace the static 3.5dp focus border and duplicate overlay ring with the
tv-material Surface focus system: 1.05 focused scale (disabled when the
profile turns animations off), a 2.5dp accent border, and a soft black
glow. Shelf rows stop passing no-op positioning callbacks so cards without
expanded posters detach their layout listeners.
2026-07-02 00:46:01 +03:00
KhooLy
d31ed1017e Make cards skippable and stop focus-driven card rebuilds
Add a Compose stability configuration declaring Meta and UserProfile
stable (both are immutable data classes in the data module) so card
composables skip recomposition when shelf state changes. In TvMovieCard,
gate onGloballyPositioned behind expanded/focus consumers, keep the
modifier chain constant by drawing the focus border in transparent when
unfocused, stop keying the artwork request on focus state so D-pad moves
no longer rebuild ImageRequests unless a focus GIF exists, and drop
crossfade allocations. Continue-watching card scale now animates in a
graphicsLayer block instead of recomposing.
2026-07-02 00:44:22 +03:00
KhooLy
0e6835be31 Add Archivo variable display face and card type hierarchy
Bundle Archivo (OFL) as the brand display family with width/weight axes;
displays, titles, shelf row titles, and Top Ten numerals now render in
expanded Archivo. Card titles drop from Black/Bold to SemiBold with Medium
metadata so shelves get visible hierarchy.
2026-07-02 00:38:49 +03:00
KhooLy
663c1154f1 Consolidate UI palette into FluxaColors tokens
Replace scattered color literals (Netflix-red E50914/E53935 progress and
accent fallbacks, drifting dark surface tones) with a single FluxaColors
object; AppTheme and ThemeHelper now source from the same tokens. Accent
fallback unifies on the theme ember E85D3F.
2026-07-02 00:34:30 +03:00
KhooLy
9384e1257b Initial commit 2026-06-05 14:43:08 +03:00