Bootstrap Castarr from NodeCast prototype

Imported the native TV app (Kotlin, ExoPlayer, embedded remote server, QR
pairing) plus CONTEXT.md and ADRs 0001-0005. Rename, Compose for TV UI and
the Dispatcharr integration follow as tracked issues.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
be-nj
2026-08-25 00:09:00 +02:00
commit a11b5a68c0
44 changed files with 2418 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chip_bg" />
<stroke android:width="1dp" android:color="@color/chip_stroke" />
<corners android:radius="100dp" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/card" />
<corners android:radius="16dp" />
</shape>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/accent" />
</shape>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/live" />
</shape>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M3,6.5 A1.5,1.5 0 0 1 4.5,5 L19.5,5 A1.5,1.5 0 0 1 21,6.5 L21,16.5 A1.5,1.5 0 0 1 19.5,18 L4.5,18 A1.5,1.5 0 0 1 3,16.5 Z"
android:strokeColor="#5FD4C4"
android:strokeWidth="1.6"
android:strokeLineCap="round" />
<path
android:pathData="M8.5,21.5 L15.5,21.5"
android:strokeColor="#5FD4C4"
android:strokeWidth="1.6"
android:strokeLineCap="round" />
</vector>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="1.8"
android:scaleY="1.8"
android:pivotX="54"
android:pivotY="54">
<path
android:pathData="M42,48.5 A1.5,1.5 0 0 1 43.5,47 L64.5,47 A1.5,1.5 0 0 1 66,48.5 L66,59.5 A1.5,1.5 0 0 1 64.5,61 L43.5,61 A1.5,1.5 0 0 1 42,59.5 Z"
android:strokeColor="#5FD4C4"
android:strokeWidth="1.8"
android:strokeLineCap="round" />
<path
android:pathData="M50,64.5 L58,64.5"
android:strokeColor="#5FD4C4"
android:strokeWidth="1.8"
android:strokeLineCap="round" />
</group>
</vector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/accent" />
<corners android:radius="1dp" />
</shape>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient
android:angle="90"
android:startColor="#EB05060A"
android:endColor="#0005060A" />
</shape>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/bg" />
</shape>
</item>
<item android:gravity="center" android:width="80dp" android:height="80dp">
<vector
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M3,6.5 A1.5,1.5 0 0 1 4.5,5 L19.5,5 A1.5,1.5 0 0 1 21,6.5 L21,16.5 A1.5,1.5 0 0 1 19.5,18 L4.5,18 A1.5,1.5 0 0 1 3,16.5 Z"
android:strokeColor="#5FD4C4"
android:strokeWidth="1.6"
android:strokeLineCap="round" />
<path
android:pathData="M8.5,21.5 L15.5,21.5"
android:strokeColor="#5FD4C4"
android:strokeWidth="1.6"
android:strokeLineCap="round" />
</vector>
</item>
</layer-list>

Binary file not shown.

View File

@@ -0,0 +1,236 @@
<?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>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/bg" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pair_title">Mit dem Handy koppeln</string>
<string name="pair_subtitle">Richte die Kamera auf den Code — die Fernbedienung öffnet sich direkt im Browser.</string>
<string name="ready_to_pair">Bereit zum Koppeln</string>
<string name="paired_with">Gekoppelt mit %1$s</string>
<string name="pairing_code">Code %1$s</string>
<string name="no_network">Keine Netzwerkverbindung</string>
<string name="playing">Wird abgespielt</string>
<string name="paused">Pausiert</string>
<string name="buffering">Lädt…</string>
<string name="playback_error">Wiedergabefehler (%1$s)</string>
<string name="playlist_error">Playlist konnte nicht geladen werden</string>
</resources>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="bg">#0A0B0D</color>
<color name="bg_deep">#05060A</color>
<color name="fg">#F2F3F5</color>
<color name="muted">#9AA0A8</color>
<color name="faint">#6B717A</color>
<color name="accent">#5FD4C4</color>
<color name="card">#FBFCFD</color>
<color name="live">#E5484D</color>
<color name="line">#23262B</color>
<color name="chip_stroke">#14FFFFFF</color>
<color name="chip_bg">#8C0A0B0D</color>
</resources>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">NodeCast</string>
<string name="pair_title">Pair with your phone</string>
<string name="pair_subtitle">Point your camera at the code — the remote opens right in your browser.</string>
<string name="ready_to_pair">Ready to pair</string>
<string name="paired_with">Paired with %1$s</string>
<string name="pairing_code">Code %1$s</string>
<string name="no_network">No network connection</string>
<string name="live">LIVE</string>
<string name="playing">Playing</string>
<string name="paused">Paused</string>
<string name="buffering">Buffering…</string>
<string name="playback_error">Playback error (%1$s)</string>
<string name="playlist_error">Could not load playlist</string>
</resources>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.NodeCast" parent="android:Theme.Material.NoActionBar">
<item name="android:windowBackground">@color/bg</item>
<item name="android:colorBackground">@color/bg</item>
<item name="android:textColorPrimary">@color/fg</item>
<item name="android:textColorSecondary">@color/muted</item>
<item name="android:colorAccent">@color/accent</item>
<item name="android:fontFamily">@font/space_grotesk</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
</style>
</resources>