Compose for TV UI, generic + Dispatcharr sources, device-flow login, favorites, stream profiles, richer remote

- Compose for TV scaffold: D-pad app shell (Live/Einstellungen), channel list
  with focus handling, fullscreen player with auto-hiding overlay; zapping via
  channel/D-pad keys (closes #2)
- Generic source end-to-end: M3U + XMLTV configurable on the TV, Now/Next with
  progress in the list, channel cache (closes #3)
- OIDC device-flow login: server URL is the only input, issuer/client id come
  from the fork's status endpoint; QR + user code screen, silent refresh,
  logout (closes #4)
- Dispatcharr source via Bearer API: channels, groups, EPG grid; switchable
  against the generic source (closes #5)
- Per-user favorites: star via long-press, favorites filter, backend-synced
  (closes #6)
- Stream profile selection (Standard/Passthrough/audiofix/720p from the
  backend profile list), applied per stream URL (closes #7)
- Phone remote: Now/Next lines, favorites star + filter over the WebSocket
  protocol (closes #8)

Device verification pending (TV currently in use).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
be-nj
2026-08-25 01:35:48 +02:00
parent 358b4c6a47
commit 0cf22204d3
19 changed files with 1817 additions and 455 deletions

View File

@@ -1,236 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg">
<!-- Pairing screen -->
<FrameLayout
android:id="@+id/pairing_screen"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:gravity="center_horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_cast" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/app_name"
android:textAllCaps="true"
android:textColor="@color/fg"
android:textSize="16sp"
android:letterSpacing="0.22" />
</LinearLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="16dp"
android:background="@drawable/bg_qr_card">
<ImageView
android:id="@+id/qr_image"
android:layout_width="170dp"
android:layout_height="170dp"
android:importantForAccessibility="no" />
</FrameLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="@string/pair_title"
android:textColor="@color/fg"
android:textSize="26sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:maxWidth="440dp"
android:gravity="center"
android:text="@string/pair_subtitle"
android:textColor="@color/muted"
android:textSize="14sp"
android:lineSpacingMultiplier="1.3" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="36dp"
android:paddingEnd="36dp"
android:paddingBottom="26dp">
<View
android:layout_width="7dp"
android:layout_height="7dp"
android:background="@drawable/dot_accent" />
<TextView
android:id="@+id/pairing_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:text="@string/ready_to_pair"
android:textColor="@color/faint"
android:textSize="12sp" />
<TextView
android:id="@+id/pairing_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:textColor="@color/faint"
android:textSize="12sp" />
<View
android:layout_width="1dp"
android:layout_height="12dp"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:background="@color/line" />
<TextView
android:id="@+id/pairing_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/muted"
android:textSize="12sp" />
</LinearLayout>
</FrameLayout>
<!-- Player screen -->
<FrameLayout
android:id="@+id/player_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="@color/bg_deep">
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_gravity="bottom"
android:background="@drawable/scrim_bottom" />
<LinearLayout
android:id="@+id/device_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_marginTop="22dp"
android:layout_marginEnd="30dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="14dp"
android:paddingEnd="14dp"
android:paddingTop="7dp"
android:paddingBottom="7dp"
android:background="@drawable/bg_chip"
android:visibility="gone">
<TextView
android:id="@+id/device_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/muted"
android:textSize="11sp" />
<View
android:layout_width="5dp"
android:layout_height="5dp"
android:layout_marginStart="8dp"
android:background="@drawable/dot_accent" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical"
android:paddingStart="36dp"
android:paddingEnd="36dp"
android:paddingBottom="30dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<View
android:id="@+id/overlay_live_dot"
android:layout_width="6dp"
android:layout_height="6dp"
android:background="@drawable/dot_live" />
<TextView
android:id="@+id/overlay_live_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:textColor="@color/muted"
android:textSize="11sp"
android:letterSpacing="0.18" />
<TextView
android:id="@+id/overlay_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/muted"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:id="@+id/overlay_channel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/fg"
android:textSize="29sp" />
<View
android:id="@+id/overlay_progress"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="16dp"
android:background="@drawable/progress_line" />
</LinearLayout>
</FrameLayout>
</FrameLayout>
</FrameLayout>