Rewamped major aspects of the UI, to fit the official Youtube UI a bit more 😄 - #5986
Open
UmmmAGoodName wants to merge 22 commits into
Open
Rewamped major aspects of the UI, to fit the official Youtube UI a bit more 😄 #5986UmmmAGoodName wants to merge 22 commits into
UmmmAGoodName wants to merge 22 commits into
Conversation
… progress bar Stability: - Cap and back off the automatic ExoPlayer engine-restart loop so a persistent error (bad network, wrong clock) can't retry forever once a second (VideoLoaderController). - Dispose ChannelPresenter's search/sort subscriptions instead of dropping them in unused locals. - Cancel BrowseFragment's delayed empty-state check on view teardown, and guard getSelectedHeaderId() against no selection (-1). - Guard VideoGroupObjectAdapter.removeAuthor() against an empty group. - Cache NavigateTitleView's delayed icon-refresh Runnables so repeated posts reschedule instead of stacking, clean them up on detach, and cap the icon-load retry loop instead of polling forever. UI: - Give video card duration/live/new badges a rounded semi-transparent "pill" background instead of a flat rectangle, matching the official YouTube app, and preserve that shape when the badge color changes. - Use the existing red progress-bar drawable for the default (Teal) and Blue color schemes' watched-progress indicator, matching YouTube's red progress bar (already used by the Red/Grey schemes).
VideoCardPresenter was always calling setBadgeColor() with an opaque color (@color/black for the regular case), which overwrote the semi-transparent pill background added for card_badge_pill.xml. Use the semi-transparent card_badge_background color for the default badge case so duration/shorts/etc. badges render as an actual translucent pill; LIVE/NEW/upcoming badges keep their solid dark_red.
Bump the duration/badge pill to ~50% opacity (was ~70%) and a larger corner radius so the rounded, semi-transparent look is unmistakable even over a dark thumbnail corner, instead of reading as solid black.
- VideoCardPresenter set the exact same text color on both the title and
the metadata line (channel * views * time ago), so they had identical
brightness. Give the metadata line a dimmer color in both the default
and selected states so the title reads as primary, YouTube style.
- Brighten/enlarge the shelf row titles ("Recommended", "Shorts") and use
a medium-weight font instead of the default dim, regular-weight text,
without touching the unrelated sidebar header style.
The sidebar (Home/Shorts/Trending/... nav) previously only dimmed/ brightened whole rows via alpha with no distinct highlight for the currently active item, unlike the official app's white rounded "pill" behind the focused nav entry with inverted (dark) icon/text. - icon_header_item.xml: give the row a rounded-pill background (initially transparent) and breathing-room padding. - IconHeaderItemPresenter: fade the pill in/out and crossfade the label and built-in icon color between white and near-black as the row's select level changes, mirroring the existing alpha animation. Remote icons (e.g. a pinned channel's own avatar) are explicitly left untinted since flattening them to a silhouette would make them unrecognizable.
The title/metadata strip under each thumbnail sat on a distinctly teal box (card_default_background_dark, #004B53) in the default color scheme. Official YouTube has no visible box there - text sits directly on the dark shelf background. Use the same shelf_background_dark color so the strip blends in, matching the Grey/Red schemes which already use a near-black card background close to their own shelf color.
The pill background was applied to the full-width row container, so it rendered as a wide bar spanning the entire sidebar instead of a content- hugging capsule around the icon+label, unlike the official app. Move the background/padding onto an inner wrap_content wrapper so the highlight sizes itself to the content.
Leanback's RowHeaderPresenter only drives the pill/highlight off raw view focus, so once the user moves into the video grid, no sidebar item stayed highlighted at all - not even the section actually being viewed, unlike the official app where the open section stays visually distinct. - IconHeaderItemPresenter now tracks an explicit isActive flag per row (does this row's section match BrowsePresenter's current section?) and combines it with the focus-driven select level, so the pill/text/icon highlight persists for the active section regardless of focus. - BrowseFragment re-binds the sidebar rows whenever the active section changes (header click, section-focus callback, or programmatic selectSection) so every row re-evaluates which one is now active.
setSelected() runs on every card during scrolling, and it was doing three findViewById() calls (info_field, title_text, content_text) on every single focus change. ComplexImageCardView now caches those references once in init() and exposes setInfoAreaBackgroundColor/setTitleTextColor/ setContentTextColor so VideoCardPresenter and the card's own marquee/ lines-num/scroll-speed methods reuse them instead of re-querying the view tree every time. Also removes a similar redundant lookup in IconHeaderItemPresenter's onBindViewHolder that I introduced earlier this session - IconViewHolder already caches the icon/label views from onCreateViewHolder.
- ChannelCardPresenter (Subscriptions/Channels grid): cache channel_card_wrapper/channel_title/channel_image in a custom ViewHolder instead of re-running findViewById on every bind, and clear the Glide request on unbind so a stale thumbnail can't flash into a recycled card before its new image loads. - UriBackgroundManager: lazily cache the video_surface view lookup instead of re-querying it on every debounced background update while scrolling. Cached lazily (not in the constructor) because this manager is built before setContentView() runs, so caching eagerly would permanently cache null. - NavigateTitleView: tag each loadIcon() call with a token so a stale retry chain from an earlier call (e.g. onAccountChanged firing again while a previous chain is still waiting on layout) can't win a race and overwrite a newer icon load. - ComplexImageView: cache the pending "finish old preview player" cleanup Runnable so a quick stop-then-start (fast re-focus during scrolling) runs it immediately instead of leaving the old and new preview players briefly alive at the same time.
Replaces reliance on the system IME popup for typing search queries with a docked QWERTY keyboard, similar to the official YouTube app, instead of following Google's stock Leanback template (a known, long-standing gap called out in this project's own README FAQ). - OnScreenKeyboardView: a widget wrapping a static QWERTY grid (view_onscreen_keyboard*.xml). Character keys need no per-key wiring - whatever text a key displays is the character it produces. Control keys (space/backspace/clear/search) are wired via ids. - lb_search_fragment.xml (app-level override of the vendored Leanback layout): docks the keyboard below the search bar on the left, and shifts the results frame right to make room. Root stays a FrameLayout since SearchSupportFragment.onCreateView() casts it directly. - SearchSupportFragment: wires keyboard key presses into the existing setSearchQuery(query, submit)/getSearchBarText() pipeline that already drives tag suggestions and results, so typing via the keyboard behaves identically to typing via the system IME. The system keyboard path is left intact (not disabled) as a fallback if the search field is still directly focused, e.g. for a Bluetooth keyboard or accessibility tooling.
- Search input box was a light/white filled pill (a leftover from the stock Leanback template); switched it to a dark translucent "glass" box with a subtle border, matching YouTube's search field. Overrode the search text/hint colors to solid/translucent white so they read clearly against the new dark box (the previous colors were tuned for a light background). - Search suggestion "tag" chips were flat rectangles; gave them a rounded pill shape like YouTube's suggestion chips, preserving the shape when the chip's color changes (focused vs default) the same way the video card badge fix did earlier this session.
…wh' into claude/stability-youtube-ui-50mkwh
The real YouTube TV app recently promoted Subscriptions and Library access (history, playlists, your videos) right below Home, instead of burying them after all the content-category rows (Shorts, Trending, Kids, Sports, Live, Gaming, News, Music). Reorder SidebarService's default section list the same way: Home, then Subscriptions/History/ Playlists/My Videos/Channels, then the content categories, then Settings last. Only affects fresh installs / accounts with no persisted sidebar order - existing users' customized or already-persisted order is untouched, since SidebarService only seeds from this default list when there's no saved state yet.
First increment of the player screen redesign toward YouTube's new look: - Play/pause/rewind/fast-forward/skip were left-aligned as a group; centered them as a standalone cluster instead. - The secondary action buttons (thumbs up/down, subtitles, share, etc.) now sit inside a floating translucent "pill" capsule instead of a plain row, matching YouTube's grouped floating controls (SmartTube has many more secondary actions than YouTube's minimal set, so the pill holds more buttons here, but the visual style matches). - Lightened the black scrim behind the controls overlay from 90% to 70% opacity, matching YouTube's move to obscure less of the video.
Previously the title/subtitle rendered inline with the bottom control row (via PlaybackTransportRowPresenter's description presenter). Added a new persistent title/subtitle view at the top-left of the screen (top_title_dock in lb_playback_fragment.xml) and hid the old inline block so it doesn't show twice. VideoPlayerGlue.setTopTitleViews(dock, title, subtitle) wires the new views in; its existing setTitle()/setSubtitle() overrides keep them in sync with whatever the row's own title/subtitle are set to, and its setControlsVisibility() override fades the new title in/out together with the rest of the controls overlay, the same way it always did. PlaybackFragment.initializeTopTitle() calls this once right after the glue is created, mirroring the existing initializeGlobalClock()/ initializeGlobalEndingTime() pattern in the same file.
Play/pause was rendered identically to every other transport button (rewind, fast-forward, skip, repeat, etc.), only gaining a focus ring when navigated to - no visual priority over its neighbors, unlike the official app where play/pause is a large, persistent white circle with a dark icon. ControlButtonPresenterSelector now special-cases PlaybackControlsRow. PlayPauseAction: sets a solid white circle as the button's background (layered behind the existing focus-ripple src, so focus feedback for every button, including this one, is unaffected) and tints its icon dark for contrast. Explicitly reset for every other action, both on bind and unbind, since ViewHolders are recycled across different actions as the row rebinds.
SeekBar.calculateSegments() caches segment rectangle positions using getWidth() at the time setSegments() is called, but onSizeChanged() never recomputed them on resize (that call was commented out) -- if the view's width wasn't final yet the first time segments were set, they'd be stuck at stale/zero-width positions forever. Store the last segments list and recompute it from onSizeChanged() too, and clear mSeekBarRectangles before repopulating (previously only done on the null/clear path) so recalculating doesn't pile up duplicates.
The magnifying-glass badge inside the search bar was hidden (src="@null", visibility="gone"), leaving the box with no icon at all and the mic/ settings/submit orbs floating disconnected around empty text. Restore it using leanback's bundled lb_ic_in_app_search icon, tinted to match the search text color so it reads correctly on the new dark box.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A batch of UI work aimed at closing the gap between SmartTube and the official YouTube TV app, plus a set of stability fixes picked up along the way. Grouped by area below.
Stability
VideoLoaderController)ChannelPresenter,NavigateTitleView)findViewByIdlookups in card/sidebar focus hot paths instead of re-querying on every bind/focus changeHome/Browse
Search
Player
Testing
Built and side-loaded (
installStbetaDebug) after each change, checking focus navigation, card hover-preview, and playback on-device.There might be some issues i've not caught in my testing, but please do point them out :)