[Help Wanted] feat: add cover flow to player#653
Conversation
|
@eddyizm please add a help wanted flag to this PR. I hope somebody can help attaching the queue to this new UI. |
This is exactly the same as before, but with an MVVM architecture patter with the following flow: Fragment -> RecyclerView -> Adapter -> ViewModel -> Repository -> Glide (data fetch). Here comes the fun part. setupCoverFlow (ran in the fragment when the UI is built), must be fed with the coverArt id recollected in the repository (all the way down on the MVVM chain). I stopped here because I have a confusion on the Repository -> Glide MVVM chain. The image fetch is done on the fragment, so the repository must read the queue itself to get the coverArt id?
|
With the new scaffolding, this is how // Fragment is built (player UI), then this is run
public void onViewCreated(@NonNull View root, @Nullable Bundle savedInstanceState) {
super.onViewCreated(root, savedInstanceState);
playerCoverFlow = root.findViewById(R.id.player_cover_flow);
CoverRepository repository = new MediaBrowserCoverRepository(mediaBrowserListenableFuture);
CoverFlowViewModelFactory factory = new CoverFlowViewModelFactory(repository);
viewModel = new ViewModelProvider(this, factory)
.get(CoverFlowViewModel.class);
viewModel.getCovers().observe(getViewLifecycleOwner(), this::setupCoverFlow);
}
// Last line of the previous code hooks to this function to populate covers when data arrives
private void setupCoverFlow(@NonNull List<Cover> covers) {
if (covers.isEmpty()) return;
CoverFlowAdapter adapter = new CoverFlowAdapter(
requireContext(),
covers,
(cover, imageView) -> {
String coverArtId = cover.getCoverArtId();
if (coverArtId != null) {
CustomGlideRequest.Builder
.from(requireContext(),
coverArtId,
CustomGlideRequest.ResourceType.Song)
.build()
.into(imageView);
}
});
playerCoverFlow.setAdapter(adapter);
playerCoverFlow.setLayoutManager(
new LinearLayoutManager(requireContext(),
LinearLayoutManager.HORIZONTAL,
false));
playerCoverFlow.addItemDecoration(UIUtil.horizontalSpacing(32));
playerCoverFlow.addOnScrollListener(UIUtil.scaleOnScroll());
UIUtil.centerAndSnapRecyclerView(playerCoverFlow);
}I followed Android's MVVM architecture pattern (which other parts of this app already follow), summarized as: The snippet above corresponds to public List<Cover> getCovers() throws Exception {
MediaBrowser mediaBrowser = mediaBrowserFuture.get();
MediaMetadata metadata = mediaBrowser.getMediaMetadata(); // <- Unused var
List<String> urls = Arrays.asList(
"https://images.dog.ceo/breeds/affenpinscher/n02110627_11858.jpg",
"https://images.dog.ceo/breeds/hound-english/n02089973_811.jpg",
"https://images.dog.ceo/breeds/shiba/shiba-14.jpg"
);
List<Cover> covers = new ArrayList<>();
for (String url : urls) {
covers.add(new Cover(url, null));
}
return covers;
}Those hardcoded URL's are the mockup that feed the recycler view. The If an URL is not provided and a coverArtId is provided instead, I don't know which one of them will be useful, but the one that stays will be sent to the Repository file. What's next?Find out how to extract either the An alternative path I just discovered is to not create the MVVM arch model for CoverFlow and instead hook to the existent MVVM from I actually like more this approach, but I took the long path to understand how that even works in the first place. |
Addresses #454.
This is a UI implementation of Cover Flow, it hardcodes external URLs to showcase how it works.
UI showcase
recording_20260510_141954.mp4
The lyrics button acts as a swapper between the old ViewPager2 (page 0 is album cover, page 1 is lyrics view) and the new RecyclerView (dog pictures). What's particular about the later, aside from the nice visual re-dimension effect, is that it snaps in place on each horizontal scroll rather than drifting through the entire list at once.
What's left to do to complete the feature is to sync the RecyclerView elements with the current queue. Both lists must go synced and on each swap the currently playing song must change to next/prev and move forward/back the queue.