App v0.2.27 and gateway 0.1.23

Skip Intro from Emby's own chapter markers, trickplay seek previews from
BIF files, a server-composed home hero ranked on Radarr/Sonarr dates and
review scores, and My Alerts as its own page behind the user picker.

Related titles now degrade at every step instead of returning empty, and
the "+" is back on Manage users so a second viewer can be added from the
launcher.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ponzischeme89
2026-08-07 10:44:17 +12:00
co-authored by Claude Opus 5
parent 4a4df7a73c
commit 80c304d86b
62 changed files with 6095 additions and 255 deletions
@@ -27,6 +27,11 @@
appears once matters more than one that appears on every press. -->
<include layout="@layout/player_seek_indicator" />
<!-- The offer to skip the opening titles. Declared here so the banners and overlays
below cover it: it lives in the bottom-end corner and belongs to the first two
minutes of an episode, where none of them has anything to say yet. -->
<include layout="@layout/player_skip_intro" />
<!-- The ten-minute cue. Declared before the next-up banner so that banner covers it
if the two ever coincide: what comes next matters more than how long is left. -->
<include layout="@layout/player_time_remaining" />
@@ -17,44 +17,71 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/time_remaining_cue_background"
android:gravity="center_vertical"
android:orientation="horizontal"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingStart="18dp"
android:paddingTop="12dp"
android:paddingEnd="22dp"
android:paddingBottom="12dp">
<!-- The frame the skip will land on, from Emby's preview thumbnails. It is gone
rather than blank when a title has none, or before the first one arrives: the
chip below it is a complete answer on its own, and a thumbnail-shaped hole
over somebody's film says only that something is missing.
The height is what settles the size, and it is a compromise with the source:
Emby generates these 320px wide, so 120dp is about a third larger than native
on a 1080p set — big enough to read a scene across a room, and not so big that
the upscaling is what the viewer notices. The width here is only the 16:9
placeholder the chip is laid out at before the frames' real shape is known. -->
<ImageView
android:id="@+id/player_seek_glyph"
android:layout_width="26dp"
android:layout_height="26dp"
android:id="@+id/player_seek_preview"
android:layout_width="214dp"
android:layout_height="120dp"
android:layout_marginBottom="12dp"
android:background="#FF000000"
android:contentDescription="@null"
android:scaleType="fitCenter"
android:src="@drawable/ic_player_forward" />
android:visibility="gone" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:orientation="vertical">
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/player_seek_amount"
<ImageView
android:id="@+id/player_seek_glyph"
android:layout_width="26dp"
android:layout_height="26dp"
android:contentDescription="@null"
android:scaleType="fitCenter"
android:src="@drawable/ic_player_forward" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFFFF"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Forward 30 seconds" />
android:layout_marginStart="14dp"
android:orientation="vertical">
<TextView
android:id="@+id/player_seek_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="#99FFFFFF"
android:textSize="13sp"
tools:text="12:34 / 1:45:00" />
<TextView
android:id="@+id/player_seek_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFFFF"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Forward 30 seconds" />
<TextView
android:id="@+id/player_seek_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="#99FFFFFF"
android:textSize="13sp"
tools:text="12:34 / 1:45:00" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- The offer to jump past an episode's opening titles.
It sits over somebody's film, so it is one control and nothing else: no panel, no
scrim, no explanation. The bottom-end corner is where every television client puts
this, which is most of why it needs no label saying what it is — and it clears the
transport row's own 48dp inset and 72dp strip, so raising the controls does not put
the two on top of each other.
The whole view is gone rather than invisible while it has nothing to offer, because a
button-shaped hole in the corner of a film says only that something is missing. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/player_skip_intro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="48dp"
android:layout_marginBottom="112dp"
android:visibility="gone">
<!-- The row is what is focusable, not the pieces in it: a remote has one idea of
"this", and two focusable children would be two stops on the way past a single
button. Focusable at all, unlike the timing cues and the alert bar beside it,
because this one is meant to be pressed and focus is the only way a remote can say
so. PlayerActivity gives it focus as it appears and hands focus back to the video
when it goes. -->
<LinearLayout
android:id="@+id/player_skip_intro_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/next_up_primary_button"
android:focusable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="14dp"
android:paddingTop="11dp"
android:paddingEnd="24dp"
android:paddingBottom="11dp">
<!-- How long is left to press it. duplicateParentState is load-bearing: the pill
inverts to white on focus, and without the parent's state reaching this the
ring would keep drawing white on white. -->
<com.ponzischeme89.memby.ui.player.SkipIntroCountdownView
android:id="@+id/player_skip_intro_countdown"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="12dp"
android:duplicateParentState="true" />
<TextView
android:id="@+id/player_skip_intro_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:text="@string/player_skip_intro"
android:textColor="@color/next_up_button_text"
android:textSize="16sp"
android:textStyle="bold"
tools:text="Skip intro" />
</LinearLayout>
</FrameLayout>