refactor(home): restructure the feed pipeline, fix pagination stalls, and add a cards-per-row setting - #1040
Merged
Merged
Conversation
…two giant functions
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.
Summary
Splits the 2,166-line home view model and the 651-line screen into focused files, moves the feed's pure logic out where it can be tested, and puts seed and related-graph retrieval behind an injected collaborator. On top of that it fixes the feed silently refusing to paginate, and adds a cards-per-row setting for the home grid.
Related issue
Closes #765
Closes #855
Closes #925
Change type
Validation
./gradlew :app:assembleGithubDebug./gradlew :app:testGithubDebugUnitTestAlso run and passing:
./gradlew spotlessCheck,:app:compileGithubDebugKotlin,:app:compileFossDebugKotlin.Test device and Android version: Xiaomi POCO X6 Pro, HyperOS 3.0.9.0, Android 16
The refactor commits are intended to be output-identical; the two visual changes and the two
behaviour changes are called out below. No performance claim is made anywhere in this PR — the
structural changes were not profiled.
Screenshots or recordings
Risk and compatibility
Fixes
A load-more page that appended nothing (every candidate already on screen) set
allowRestart = falseand gave up. Nothing was appended, so the feed length was unchanged, so the last-visible index
could not change, so the index-only viewport flow had nothing to re-emit — the queue could never
re-arm.
hasMorePagesis never set false on the Flow-feed path either, so the end-of-feed footerdid not show: the page just ended silently. Two fixes: an empty page now retries up to 3 times
with backoff (each attempt advances the discovery-query cursor and rotates seeds, so it asks for
different content), and the viewport flow now emits on
isScrollInProgressas well as the index,so a further scroll attempt re-arms it.
(Auto / 1 / 2 / 3) under Settings → Content display → Home layout, shown only in Grid mode since
List is one per row by definition. Auto is the default and keeps today's behaviour.
<700dpbucket and got a singlefull-width poster. Auto now gives 2 columns from 600dp up; below 600dp and every wider breakpoint
are unchanged.
LaunchedEffectin the feed grid calledenrichChannelMetadataIfMissing(videoId),whose first line was a linear scan of the whole video list — re-running for every item on every
scroll recycle. It now takes the
Videothe call site already holds.had silently lost its
home_video_cardtest tag, so instrumentation only ever saw the first1–4 cards.
FlowNavigation: aninlinePlayerHeightthat read two per-frame player animationvalues and was never used anywhere.
viewModel.initialize(context)inHomeScreen—FlowAppalready calls itwith the application context before the NavHost composes.
Behaviour changes worth knowing about
uiStatemoved fromSharingStarted.EagerlytoWhileSubscribed(5_000). The dedup pass overthe feed no longer runs on every background append while Home is off-screen.
ViewHistorysingleton — a lazy one and a nullable one set ininitialize(). Seed building usedthe nullable one and returned an empty list when it was still null, which on a cold start could
leave the related lane empty because
initstartsloadFlowFeedbeforeFlowAppcallsinitialize(). With constructor injection it is never null, so cold-start related seeds nowpopulate.
so at 3 columns the old fixed index would have started it after one card and left holes in the row
above. Unchanged at every Auto breakpoint (1 / 2 / 3 / 4).
FlowEmptyStatefor the feed-disabled state andFlowErrorStatefor the error state. These look different from the local versions they replace:a larger icon container,
bodyLargetitle, and a filled button instead of a text button. Theretry label is unchanged (
retryandaction_retryare both "Retry").FlowFeedProgressinstead of a bareCircularProgressIndicator.ContinueWatchingShelfmoved out ofui/components/VideoCard.ktinto a newui/components/home/, along with its two private helpers. It had exactly one consumer.Color.Redin the Shorts shelf is now a named theme token wired to the existingYouTubeRedliteral.
Color.Redis0xFFFF0000, so this is pixel-identical.HomeFeedCacheRepositoryHilt provider, matching the existing@Provides @Singletondelegate pattern. Four
getInstance()/ manual-construction sites in the view model are gone;instance identity is preserved.
home_feed_columns, defaultAUTO. DataStore only — no Room schema change.values/strings.xml. Other locales are left to Weblate.Videowith a local helper rather than the sharedVideoHistoryEntry.toVideo()mapper: that one carriesisShort, whichFlowNavigationbrancheson, so reusing it would have rerouted a resumed short to the Shorts player.
Known limitation
HomeViewModel.ktis 2,166 → ~1,270 lines, still over the 600-line ceiling. Everything thatseparates cleanly has been extracted; what remains is one responsibility — loading and paging the
feed. Splitting it further would put the shared discovery-query cursor (
discoveryQueries+currentQueryIndex, advanced by wave 1, wave 2 and load-more alike) across a class boundary, whichis worse than a long file. Moving that cursor into its own owner is the honest prerequisite and is
left for a follow-up.
The cards-per-row setting is scoped to the home feed, so at 600–700dp Home shows 2 columns while
Subscriptions still shows 1. Extending it is a follow-up.