build: change of package name

This commit is contained in:
antonio 2023-06-17 15:30:23 +02:00
parent 49afdbe4eb
commit b76a38cb30
274 changed files with 1981 additions and 2161 deletions

View file

@ -0,0 +1,186 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentAlbumCatalogueBinding;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.AlbumCatalogueViewModel;
@OptIn(markerClass = UnstableApi.class)
public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
private static final String TAG = "ArtistCatalogueFragment";
private FragmentAlbumCatalogueBinding bind;
private MainActivity activity;
private AlbumCatalogueViewModel albumCatalogueViewModel;
private AlbumCatalogueAdapter albumAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
initData();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentAlbumCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
initAppBar();
initAlbumCatalogueView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initData() {
albumCatalogueViewModel = new ViewModelProvider(requireActivity()).get(AlbumCatalogueViewModel.class);
albumCatalogueViewModel.loadAlbums(500);
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.album_catalogue_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void initAlbumCatalogueView() {
bind.albumCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.albumCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.albumCatalogueRecyclerView.setHasFixedSize(true);
albumAdapter = new AlbumCatalogueAdapter(this);
albumAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.albumCatalogueRecyclerView.setAdapter(albumAdapter);
albumCatalogueViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> albumAdapter.setItems(albums));
bind.albumCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.albumListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_album_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
albumAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_album_sort_name) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_NAME);
return true;
} else if (menuItem.getItemId() == R.id.menu_album_sort_artist) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_ARTIST);
return true;
} else if (menuItem.getItemId() == R.id.menu_album_sort_year) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_YEAR);
return true;
} else if (menuItem.getItemId() == R.id.menu_album_sort_random) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_RANDOM);
return true;
}
return false;
});
popup.show();
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
hideKeyboard(requireView());
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,122 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentAlbumListPageBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumListPageViewModel;
@OptIn(markerClass = UnstableApi.class)
public class AlbumListPageFragment extends Fragment implements ClickCallback {
private FragmentAlbumListPageBinding bind;
private MainActivity activity;
private AlbumListPageViewModel albumListPageViewModel;
private AlbumHorizontalAdapter albumHorizontalAdapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentAlbumListPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
albumListPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumListPageViewModel.class);
init();
initAppBar();
initAlbumListView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
if (requireArguments().getString(Constants.ALBUM_RECENTLY_PLAYED) != null) {
albumListPageViewModel.title = Constants.ALBUM_RECENTLY_PLAYED;
bind.pageTitleLabel.setText(R.string.album_list_page_recently_played);
} else if (requireArguments().getString(Constants.ALBUM_MOST_PLAYED) != null) {
albumListPageViewModel.title = Constants.ALBUM_MOST_PLAYED;
bind.pageTitleLabel.setText(R.string.album_list_page_most_played);
} else if (requireArguments().getString(Constants.ALBUM_RECENTLY_ADDED) != null) {
albumListPageViewModel.title = Constants.ALBUM_RECENTLY_ADDED;
bind.pageTitleLabel.setText(R.string.album_list_page_recently_added);
} else if (requireArguments().getString(Constants.ALBUM_STARRED) != null) {
albumListPageViewModel.title = Constants.ALBUM_STARRED;
bind.pageTitleLabel.setText(R.string.album_list_page_starred);
} else if (requireArguments().getString(Constants.ALBUM_NEW_RELEASES) != null) {
albumListPageViewModel.title = Constants.ALBUM_NEW_RELEASES;
bind.pageTitleLabel.setText(R.string.album_list_page_new_releases);
} else if (requireArguments().getString(Constants.ALBUM_DOWNLOADED) != null) {
albumListPageViewModel.title = Constants.ALBUM_DOWNLOADED;
bind.pageTitleLabel.setText(R.string.album_list_page_downloaded);
} else if (requireArguments().getParcelable(Constants.ARTIST_OBJECT) != null) {
albumListPageViewModel.artist = requireArguments().getParcelable(Constants.ARTIST_OBJECT);
albumListPageViewModel.title = Constants.ALBUM_FROM_ARTIST;
bind.pageTitleLabel.setText(MusicUtil.getReadableString(albumListPageViewModel.artist.getName()));
}
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.album_list_page_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void initAlbumListView() {
bind.albumListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.albumListRecyclerView.setHasFixedSize(true);
albumHorizontalAdapter = new AlbumHorizontalAdapter(
this,
(albumListPageViewModel.title.equals(Constants.ALBUM_DOWNLOADED) || albumListPageViewModel.title.equals(Constants.ALBUM_FROM_ARTIST))
);
bind.albumListRecyclerView.setAdapter(albumHorizontalAdapter);
albumListPageViewModel.getAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> albumHorizontalAdapter.setItems(albums));
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,208 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentAlbumPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;
@UnstableApi
public class AlbumPageFragment extends Fragment implements ClickCallback {
private FragmentAlbumPageBinding bind;
private MainActivity activity;
private AlbumPageViewModel albumPageViewModel;
private SongHorizontalAdapter songHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.album_page_menu, menu);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentAlbumPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class);
init();
initAppBar();
initAlbumInfoTextButton();
initMusicButton();
initBackCover();
initSongsView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_download_album) {
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownloads(songs),
songs.stream().map(Download::new).collect(Collectors.toList())
);
});
return true;
}
return false;
}
private void init() {
albumPageViewModel.setAlbum(requireArguments().getParcelable(Constants.ALBUM_OBJECT));
}
private void initAppBar() {
activity.setSupportActionBar(bind.animToolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.animToolbar.setTitle(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getName()));
bind.albumNameLabel.setText(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getName()));
bind.albumArtistLabel.setText(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getArtist()));
bind.albumReleaseYearLabel.setText(albumPageViewModel.getAlbum().getYear() != 0 ? String.valueOf(albumPageViewModel.getAlbum().getYear()) : "");
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
Objects.requireNonNull(bind.animToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initAlbumInfoTextButton() {
bind.albumArtistLabel.setOnClickListener(v -> albumPageViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artist);
activity.navController.navigate(R.id.action_albumPageFragment_to_artistPageFragment, bundle);
} else
Toast.makeText(requireContext(), getString(R.string.album_error_retrieving_artist), Toast.LENGTH_SHORT).show();
}));
}
private void initMusicButton() {
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null && !songs.isEmpty()) {
bind.albumPagePlayButton.setOnClickListener(v -> {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
});
bind.albumPageShuffleButton.setOnClickListener(v -> {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
});
}
if (bind != null && songs.isEmpty()) {
bind.albumPagePlayButton.setEnabled(false);
bind.albumPageShuffleButton.setEnabled(false);
}
});
}
private void initBackCover() {
CustomGlideRequest.Builder
.from(requireContext(), albumPageViewModel.getAlbum().getCoverArtId())
.build()
.into(bind.albumCoverImageView);
}
private void initSongsView() {
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, false);
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,179 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentArtistCatalogueBinding;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.ArtistCatalogueViewModel;
@UnstableApi
public class ArtistCatalogueFragment extends Fragment implements ClickCallback {
private static final String TAG = "ArtistCatalogueFragment";
private FragmentArtistCatalogueBinding bind;
private MainActivity activity;
private ArtistCatalogueViewModel artistCatalogueViewModel;
private ArtistCatalogueAdapter artistAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
initData();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentArtistCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
initAppBar();
initArtistCatalogueView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initData() {
artistCatalogueViewModel = new ViewModelProvider(requireActivity()).get(ArtistCatalogueViewModel.class);
artistCatalogueViewModel.loadArtists();
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.artistInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.artist_catalogue_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void initArtistCatalogueView() {
bind.artistCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.artistCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.artistCatalogueRecyclerView.setHasFixedSize(true);
artistAdapter = new ArtistCatalogueAdapter(this);
artistAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.artistCatalogueRecyclerView.setAdapter(artistAdapter);
artistCatalogueViewModel.getArtistList().observe(getViewLifecycleOwner(), artistList -> artistAdapter.setItems(artistList));
bind.artistCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.artistListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_artist_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
artistAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_artist_sort_name) {
artistAdapter.sort(Constants.ARTIST_ORDER_BY_NAME);
return true;
} else if (menuItem.getItemId() == R.id.menu_artist_sort_random) {
artistAdapter.sort(Constants.ARTIST_ORDER_BY_RANDOM);
return true;
}
return false;
});
popup.show();
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
hideKeyboard(requireView());
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,101 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentArtistListPageBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.ArtistListPageViewModel;
@UnstableApi
public class ArtistListPageFragment extends Fragment implements ClickCallback {
private FragmentArtistListPageBinding bind;
private MainActivity activity;
private ArtistListPageViewModel artistListPageViewModel;
private ArtistHorizontalAdapter artistHorizontalAdapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentArtistListPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
artistListPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistListPageViewModel.class);
init();
initAppBar();
initArtistListView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
if (requireArguments().getString(Constants.ARTIST_STARRED) != null) {
artistListPageViewModel.title = Constants.ARTIST_STARRED;
bind.pageTitleLabel.setText(R.string.artist_list_page_starred);
} else if (requireArguments().getString(Constants.ARTIST_DOWNLOADED) != null) {
artistListPageViewModel.title = Constants.ARTIST_DOWNLOADED;
bind.pageTitleLabel.setText(R.string.artist_list_page_downloaded);
}
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.artistInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.artist_list_page_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void initArtistListView() {
bind.artistListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.artistListRecyclerView.setHasFixedSize(true);
artistHorizontalAdapter = new ArtistHorizontalAdapter(this);
bind.artistListRecyclerView.setAdapter(artistHorizontalAdapter);
artistListPageViewModel.getArtistList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> artistHorizontalAdapter.setItems(artists));
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumListPageFragment, bundle);
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,260 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentArtistPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumArtistPageOrSimilarAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistSimilarAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.ArtistPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class ArtistPageFragment extends Fragment implements ClickCallback {
private FragmentArtistPageBinding bind;
private MainActivity activity;
private ArtistPageViewModel artistPageViewModel;
private SongHorizontalAdapter songHorizontalAdapter;
private AlbumArtistPageOrSimilarAdapter albumArtistPageOrSimilarAdapter;
private ArtistSimilarAdapter artistSimilarAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentArtistPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
init();
initAppBar();
initArtistInfo();
initPlayButtons();
initTopSongsView();
initAlbumsView();
initSimilarArtistsView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
artistPageViewModel.setArtist(requireArguments().getParcelable(Constants.ARTIST_OBJECT));
bind.mostStreamedSongTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.MEDIA_BY_ARTIST, Constants.MEDIA_BY_ARTIST);
bundle.putParcelable(Constants.ARTIST_OBJECT, artistPageViewModel.getArtist());
activity.navController.navigate(R.id.action_artistPageFragment_to_songListPageFragment, bundle);
});
}
private void initAppBar() {
activity.setSupportActionBar(bind.animToolbar);
if (activity.getSupportActionBar() != null)
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
bind.collapsingToolbar.setTitle(MusicUtil.getReadableString(artistPageViewModel.getArtist().getName()));
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.collapsingToolbar.setExpandedTitleColor(getResources().getColor(R.color.white, null));
}
private void initArtistInfo() {
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artistInfo -> {
if(artistInfo == null) {
if (bind != null) bind.artistPageBioPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageBioSector.setVisibility(View.GONE);
} else {
String normalizedBio = MusicUtil.forceReadableString(artistInfo.getBiography());
if (bind != null)
bind.artistPageBioSector.setVisibility(!normalizedBio.trim().isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE);
if (getContext() != null && bind != null) CustomGlideRequest.Builder
.from(requireContext(), artistPageViewModel.getArtist().getId())
.build()
.into(bind.artistBackdropImageView);
if (bind != null) bind.bioTextView.setText(normalizedBio);
if (bind != null) bind.bioMoreTextViewClickable.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(artistInfo.getLastFmUrl()));
startActivity(intent);
});
if (bind != null) bind.artistPageBioPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.artistPageBioSector.setVisibility(View.VISIBLE);
}
});
}
private void initPlayButtons() {
bind.artistPageShuffleButton.setOnClickListener(v -> {
artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
} else {
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
}
});
});
bind.artistPageRadioButton.setOnClickListener(v -> {
artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
} else {
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_radio), Toast.LENGTH_SHORT).show();
}
});
});
}
private void initTopSongsView() {
bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null) bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageTopSongsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.artistPageTopSongsSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
songHorizontalAdapter.setItems(songs);
}
});
}
private void initAlbumsView() {
bind.albumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
albumArtistPageOrSimilarAdapter = new AlbumArtistPageOrSimilarAdapter(this);
bind.albumsRecyclerView.setAdapter(albumArtistPageOrSimilarAdapter);
artistPageViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.artistPageAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
albumArtistPageOrSimilarAdapter.setItems(albums);
}
});
CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
albumSnapHelper.attachToRecyclerView(bind.albumsRecyclerView);
}
private void initSimilarArtistsView() {
bind.similarArtistsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.similarArtistsRecyclerView.setHasFixedSize(true);
artistSimilarAdapter = new ArtistSimilarAdapter(this);
bind.similarArtistsRecyclerView.setAdapter(artistSimilarAdapter);
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artist -> {
if (artist == null) {
if (bind != null) bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.similarArtistSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.similarArtistSector.setVisibility(!artist.getSimilarArtists().isEmpty() ? View.VISIBLE : View.GONE);
artistSimilarAdapter.setItems(artist.getSimilarArtists());
}
});
CustomLinearSnapHelper similarArtistSnapHelper = new CustomLinearSnapHelper();
similarArtistSnapHelper.attachToRecyclerView(bind.similarArtistsRecyclerView);
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,172 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Artist;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.Objects;
@UnstableApi
public class DirectoryFragment extends Fragment implements ClickCallback {
private static final String TAG = "DirectoryFragment";
private FragmentDirectoryBinding bind;
private MainActivity activity;
private DirectoryViewModel directoryViewModel;
private MusicDirectoryAdapter musicDirectoryAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentDirectoryBinding.inflate(inflater, container, false);
View view = bind.getRoot();
directoryViewModel = new ViewModelProvider(requireActivity()).get(DirectoryViewModel.class);
initAppBar();
initButtons();
initDirectoryListView();
init();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
Artist artist = getArguments().getParcelable(Constants.MUSIC_INDEX_OBJECT);
if (artist != null) {
directoryViewModel.setMusicDirectoryId(artist.getId());
directoryViewModel.setMusicDirectoryName(artist.getName());
}
directoryViewModel.loadMusicDirectory(getViewLifecycleOwner());
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
if (bind != null)
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
if (bind != null)
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.directoryInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
if (directory != null) {
bind.toolbar.setTitle(directory.getName());
}
});
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
if (directory != null) {
bind.directoryTitleLabel.setText(directory.getName());
}
});
}
private void initButtons() {
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
if (directory != null && directory.getParentId() != null && !Objects.equals(directory.getParentId(), "-1")) {
bind.directoryBackImageView.setVisibility(View.VISIBLE);
} else {
bind.directoryBackImageView.setVisibility(View.GONE);
}
});
bind.directoryBackImageView.setOnClickListener(v -> directoryViewModel.goBack());
}
private void initDirectoryListView() {
bind.directoryRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.directoryRecyclerView.setHasFixedSize(true);
musicDirectoryAdapter = new MusicDirectoryAdapter(this);
bind.directoryRecyclerView.setAdapter(musicDirectoryAdapter);
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
if (directory != null) {
musicDirectoryAdapter.setItems(directory.getChildren());
} else {
musicDirectoryAdapter.setItems(Collections.emptyList());
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
}
@Override
public void onMusicDirectoryClick(Bundle bundle) {
Child child = bundle.getParcelable(Constants.MUSIC_DIRECTORY_OBJECT);
if (child != null) {
directoryViewModel.setMusicDirectoryId(child.getId());
directoryViewModel.setMusicDirectoryName(child.getTitle());
}
}
}

View file

@ -0,0 +1,170 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Objects;
@UnstableApi
public class DownloadFragment extends Fragment implements ClickCallback {
private FragmentDownloadBinding bind;
private MainActivity activity;
private DownloadViewModel downloadViewModel;
private DownloadHorizontalAdapter downloadHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentDownloadBinding.inflate(inflater, container, false);
View view = bind.getRoot();
downloadViewModel = new ViewModelProvider(requireActivity()).get(DownloadViewModel.class);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initDownloadedSongView();
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
activity.setBottomNavigationBarVisibility(true);
activity.setBottomSheetVisibility(true);
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_downloadFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_downloadFragment_to_settingsFragment);
return true;
}
return false;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initDownloadedSongView() {
bind.downloadedTracksRecyclerView.setHasFixedSize(true);
downloadHorizontalAdapter = new DownloadHorizontalAdapter(this);
bind.downloadedTracksRecyclerView.setAdapter(downloadHorizontalAdapter);
downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs != null) {
if (songs.isEmpty()) {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
bind.downloadDownloadedTracksSector.setVisibility(View.GONE);
}
} else {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.GONE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.GONE);
bind.downloadDownloadedTracksSector.setVisibility(View.VISIBLE);
bind.downloadedTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
downloadHorizontalAdapter.setItems(songs);
}
}
if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE);
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,106 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentFilterBinding;
import com.cappielloantonio.tempo.subsonic.models.Genre;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.FilterViewModel;
import com.google.android.material.chip.Chip;
@OptIn(markerClass = UnstableApi.class)
public class FilterFragment extends Fragment {
private static final String TAG = "FilterFragment";
private MainActivity activity;
private FragmentFilterBinding bind;
private FilterViewModel filterViewModel;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentFilterBinding.inflate(inflater, container, false);
View view = bind.getRoot();
filterViewModel = new ViewModelProvider(requireActivity()).get(FilterViewModel.class);
init();
initAppBar();
setFilterChips();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
Bundle bundle = new Bundle();
bundle.putString(Constants.MEDIA_BY_GENRES, Constants.MEDIA_BY_GENRES);
bundle.putStringArrayList("filters_list", filterViewModel.getFilters());
bundle.putStringArrayList("filter_name_list", filterViewModel.getFilterNames());
bind.finishFilteringTextViewClickable.setOnClickListener(v -> {
if (filterViewModel.getFilters().size() > 1)
activity.navController.navigate(R.id.action_filterFragment_to_songListPageFragment, bundle);
else
Toast.makeText(requireContext(), getString(R.string.filter_info_selection), Toast.LENGTH_SHORT).show();
});
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.genreFilterInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.filter_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void setFilterChips() {
filterViewModel.getGenreList().observe(getViewLifecycleOwner(), genres -> {
bind.loadingProgressBar.setVisibility(View.GONE);
bind.filterContainer.setVisibility(View.VISIBLE);
for (Genre genre : genres) {
Chip chip = (Chip) requireActivity().getLayoutInflater().inflate(R.layout.chip_search_filter_genre, null, false);
chip.setText(MusicUtil.getReadableString(genre.getGenre()));
chip.setChecked(filterViewModel.getFilters().contains(genre.getGenre()));
chip.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked)
filterViewModel.addFilter(genre.getGenre(), buttonView.getText().toString());
else
filterViewModel.removeFilter(genre.getGenre(), buttonView.getText().toString());
});
bind.filtersChipsGroup.addView(chip);
}
});
}
}

View file

@ -0,0 +1,172 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentGenreCatalogueBinding;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.GenreCatalogueAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.GenreCatalogueViewModel;
@OptIn(markerClass = UnstableApi.class)
public class GenreCatalogueFragment extends Fragment implements ClickCallback {
private FragmentGenreCatalogueBinding bind;
private MainActivity activity;
private GenreCatalogueViewModel genreCatalogueViewModel;
private GenreCatalogueAdapter genreCatalogueAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentGenreCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
genreCatalogueViewModel = new ViewModelProvider(requireActivity()).get(GenreCatalogueViewModel.class);
init();
initAppBar();
initGenreCatalogueView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
bind.filterGenresTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_genreCatalogueFragment_to_filterFragment));
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.genreInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.genre_catalogue_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void initGenreCatalogueView() {
bind.genreCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.genreCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 16, false));
bind.genreCatalogueRecyclerView.setHasFixedSize(true);
genreCatalogueAdapter = new GenreCatalogueAdapter(this);
genreCatalogueAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.genreCatalogueRecyclerView.setAdapter(genreCatalogueAdapter);
genreCatalogueViewModel.getGenreList().observe(getViewLifecycleOwner(), genres -> genreCatalogueAdapter.setItems(genres));
bind.genreCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.genreListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_genre_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
genreCatalogueAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_genre_sort_name) {
genreCatalogueAdapter.sort(Constants.GENRE_ORDER_BY_NAME);
return true;
} else if (menuItem.getItemId() == R.id.menu_genre_sort_random) {
genreCatalogueAdapter.sort(Constants.GENRE_ORDER_BY_RANDOM);
return true;
}
return false;
});
popup.show();
}
@Override
public void onGenreClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
hideKeyboard(requireView());
}
}

View file

@ -0,0 +1,117 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentHomeBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.fragment.pager.HomePager;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.Objects;
@UnstableApi
public class HomeFragment extends Fragment {
private static final String TAG = "HomeFragment";
private FragmentHomeBinding bind;
private MainActivity activity;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeBinding.inflate(inflater, container, false);
return bind.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initHomePager();
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(true);
activity.setBottomSheetVisibility(true);
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_homeFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_homeFragment_to_settingsFragment);
return true;
}
return false;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initHomePager() {
HomePager pager = new HomePager(this);
pager.addFragment(new HomeTabMusicFragment(), "Music", R.drawable.ic_home);
if (Preferences.isPodcastSectionVisible())
pager.addFragment(new HomeTabPodcastFragment(), "Podcast", R.drawable.ic_graphic_eq);
if (Preferences.isRadioSectionVisible())
pager.addFragment(new HomeTabRadioFragment(), "Radio", R.drawable.ic_play_for_work);
bind.homeViewPager.setAdapter(pager);
bind.homeViewPager.setUserInputEnabled(false);
new TabLayoutMediator(bind.homeTabLayout, bind.homeViewPager,
(tab, position) -> {
tab.setText(pager.getPageTitle(position));
// tab.setIcon(pager.getPageIcon(position));
}
).attach();
bind.homeTabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
}
}

View file

@ -0,0 +1,715 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.PagerSnapHelper;
import androidx.recyclerview.widget.SnapHelper;
import androidx.viewpager2.widget.ViewPager2;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentHomeTabMusicBinding;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.helper.recyclerview.DotsIndicatorDecoration;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.DownloaderManager;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.DiscoverSongAdapter;
import com.cappielloantonio.tempo.ui.adapter.GridTrackAdapter;
import com.cappielloantonio.tempo.ui.adapter.SimilarTrackAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.YearAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
@UnstableApi
public class HomeTabMusicFragment extends Fragment implements ClickCallback {
private static final String TAG = "HomeFragment";
private FragmentHomeTabMusicBinding bind;
private MainActivity activity;
private HomeViewModel homeViewModel;
private DiscoverSongAdapter discoverSongAdapter;
private SimilarTrackAdapter similarMusicAdapter;
private ArtistAdapter radioArtistAdapter;
private ArtistAdapter bestOfArtistAdapter;
private SongHorizontalAdapter starredSongAdapter;
private AlbumHorizontalAdapter starredAlbumAdapter;
private ArtistHorizontalAdapter starredArtistAdapter;
private AlbumAdapter recentlyAddedAlbumAdapter;
private AlbumAdapter recentlyPlayedAlbumAdapter;
private AlbumAdapter mostPlayedAlbumAdapter;
private AlbumHorizontalAdapter newReleasesAlbumAdapter;
private YearAdapter yearAdapter;
private GridTrackAdapter gridTrackAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeTabMusicBinding.inflate(inflater, container, false);
View view = bind.getRoot();
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
init();
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initSyncStarredView();
initDiscoverSongSlideView();
initSimilarSongView();
initArtistRadio();
initArtistBestOf();
initStarredTracksView();
initStarredAlbumsView();
initStarredArtistsView();
initMostPlayedAlbumView();
initRecentPlayedAlbumView();
initNewReleasesView();
initYearSongView();
initRecentAddedAlbumView();
initGridView();
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
bind.discoveryTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshDiscoverySongSample(getViewLifecycleOwner());
return true;
});
bind.similarTracksTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshSimilarSongSample(getViewLifecycleOwner());
return true;
});
bind.radioArtistTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshRadioArtistSample(getViewLifecycleOwner());
return true;
});
bind.bestOfArtistTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshBestOfArtist(getViewLifecycleOwner());
return true;
});
bind.starredTracksTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.MEDIA_STARRED, Constants.MEDIA_STARRED);
activity.navController.navigate(R.id.action_homeFragment_to_songListPageFragment, bundle);
});
bind.starredAlbumsTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.ALBUM_STARRED, Constants.ALBUM_STARRED);
activity.navController.navigate(R.id.action_homeFragment_to_albumListPageFragment, bundle);
});
bind.starredArtistsTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.ARTIST_STARRED, Constants.ARTIST_STARRED);
activity.navController.navigate(R.id.action_homeFragment_to_artistListPageFragment, bundle);
});
bind.recentlyAddedAlbumsTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.ALBUM_RECENTLY_ADDED, Constants.ALBUM_RECENTLY_ADDED);
activity.navController.navigate(R.id.action_homeFragment_to_albumListPageFragment, bundle);
});
bind.recentlyPlayedAlbumsTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.ALBUM_RECENTLY_PLAYED, Constants.ALBUM_RECENTLY_PLAYED);
activity.navController.navigate(R.id.action_homeFragment_to_albumListPageFragment, bundle);
});
bind.mostPlayedAlbumsTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.ALBUM_MOST_PLAYED, Constants.ALBUM_MOST_PLAYED);
activity.navController.navigate(R.id.action_homeFragment_to_albumListPageFragment, bundle);
});
bind.starredTracksTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshStarredTracks(getViewLifecycleOwner());
return true;
});
bind.starredAlbumsTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshStarredAlbums(getViewLifecycleOwner());
return true;
});
bind.starredArtistsTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshStarredArtists(getViewLifecycleOwner());
return true;
});
bind.recentlyPlayedAlbumsTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshRecentlyPlayedAlbumList(getViewLifecycleOwner());
return true;
});
bind.mostPlayedAlbumsTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshMostPlayedAlbums(getViewLifecycleOwner());
return true;
});
bind.recentlyAddedAlbumsTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshMostRecentlyAddedAlbums(getViewLifecycleOwner());
return true;
});
}
private void initSyncStarredView() {
if (Preferences.isStarredSyncEnabled()) {
homeViewModel.getAllStarredTracks().observeForever(new Observer<List<Child>>() {
@Override
public void onChanged(List<Child> songs) {
if (songs != null) {
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
List<String> toSync = new ArrayList<>();
for (Child song : songs) {
if (!manager.isDownloaded(song.getId())) {
toSync.add(song.getTitle());
}
}
if (!toSync.isEmpty()) {
bind.homeSyncStarredCard.setVisibility(View.VISIBLE);
bind.homeSyncStarredTracksToSync.setText(String.join(", ", toSync));
}
}
homeViewModel.getAllStarredTracks().removeObserver(this);
}
});
}
bind.homeSyncStarredCancel.setOnClickListener(v -> bind.homeSyncStarredCard.setVisibility(View.GONE));
bind.homeSyncStarredDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
homeViewModel.getAllStarredTracks().observeForever(new Observer<List<Child>>() {
@Override
public void onChanged(List<Child> songs) {
if (songs != null) {
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
for (Child song : songs) {
if (!manager.isDownloaded(song.getId())) {
manager.download(MappingUtil.mapDownload(song), new Download(song));
}
}
}
homeViewModel.getAllStarredTracks().removeObserver(this);
bind.homeSyncStarredCard.setVisibility(View.GONE);
}
});
}
});
}
private void initDiscoverSongSlideView() {
bind.discoverSongViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
discoverSongAdapter = new DiscoverSongAdapter(this);
bind.discoverSongViewPager.setAdapter(discoverSongAdapter);
bind.discoverSongViewPager.setOffscreenPageLimit(1);
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null) bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeDiscoverSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeDiscoverSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
discoverSongAdapter.setItems(songs);
}
});
setSlideViewOffset(bind.discoverSongViewPager, 20, 16);
}
private void initSimilarSongView() {
bind.similarTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.similarTracksRecyclerView.setHasFixedSize(true);
similarMusicAdapter = new SimilarTrackAdapter(this);
bind.similarTracksRecyclerView.setAdapter(similarMusicAdapter);
homeViewModel.getStarredTracksSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null) bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeSimilarTracksSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeSimilarTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
similarMusicAdapter.setItems(songs);
}
});
CustomLinearSnapHelper similarSongSnapHelper = new CustomLinearSnapHelper();
similarSongSnapHelper.attachToRecyclerView(bind.similarTracksRecyclerView);
}
private void initArtistBestOf() {
bind.bestOfArtistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.bestOfArtistRecyclerView.setHasFixedSize(true);
bestOfArtistAdapter = new ArtistAdapter(this, false, true);
bind.bestOfArtistRecyclerView.setAdapter(bestOfArtistAdapter);
homeViewModel.getBestOfArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null) bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeBestOfArtistSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeBestOfArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
bestOfArtistAdapter.setItems(artists);
}
});
CustomLinearSnapHelper artistBestOfSnapHelper = new CustomLinearSnapHelper();
artistBestOfSnapHelper.attachToRecyclerView(bind.bestOfArtistRecyclerView);
}
private void initArtistRadio() {
bind.radioArtistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.radioArtistRecyclerView.setHasFixedSize(true);
radioArtistAdapter = new ArtistAdapter(this, true, false);
bind.radioArtistRecyclerView.setAdapter(radioArtistAdapter);
homeViewModel.getStarredArtistsSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null) bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRadioArtistSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeRadioArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.afterRadioArtistDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
radioArtistAdapter.setItems(artists);
}
});
CustomLinearSnapHelper artistRadioSnapHelper = new CustomLinearSnapHelper();
artistRadioSnapHelper.attachToRecyclerView(bind.radioArtistRecyclerView);
}
private void initGridView() {
bind.gridTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
bind.gridTracksRecyclerView.addItemDecoration(new GridItemDecoration(3, 8, false));
bind.gridTracksRecyclerView.setHasFixedSize(true);
gridTrackAdapter = new GridTrackAdapter(this);
bind.gridTracksRecyclerView.setAdapter(gridTrackAdapter);
homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
if (chronologies == null || chronologies.size() == 0) {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
if (bind != null) bind.afterGridDivider.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.VISIBLE);
if (bind != null) bind.afterGridDivider.setVisibility(View.VISIBLE);
gridTrackAdapter.setItems(chronologies);
}
});
}
private void initStarredTracksView() {
bind.starredTracksRecyclerView.setHasFixedSize(true);
starredSongAdapter = new SongHorizontalAdapter(this, true);
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null) bind.starredTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredTracksSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.starredTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.starredTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.starredTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(songs.size(), 5), GridLayoutManager.HORIZONTAL, false));
starredSongAdapter.setItems(songs);
}
});
SnapHelper starredTrackSnapHelper = new PagerSnapHelper();
starredTrackSnapHelper.attachToRecyclerView(bind.starredTracksRecyclerView);
bind.starredTracksRecyclerView.addItemDecoration(
new DotsIndicatorDecoration(
getResources().getDimensionPixelSize(R.dimen.radius),
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
getResources().getDimensionPixelSize(R.dimen.dots_height),
requireContext().getResources().getColor(R.color.titleTextColor, null),
requireContext().getResources().getColor(R.color.titleTextColor, null))
);
}
private void initStarredAlbumsView() {
bind.starredAlbumsRecyclerView.setHasFixedSize(true);
starredAlbumAdapter = new AlbumHorizontalAdapter(this, false);
bind.starredAlbumsRecyclerView.setAdapter(starredAlbumAdapter);
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.starredAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.starredAlbumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
starredAlbumAdapter.setItems(albums);
}
});
SnapHelper starredAlbumSnapHelper = new PagerSnapHelper();
starredAlbumSnapHelper.attachToRecyclerView(bind.starredAlbumsRecyclerView);
bind.starredAlbumsRecyclerView.addItemDecoration(
new DotsIndicatorDecoration(
getResources().getDimensionPixelSize(R.dimen.radius),
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
getResources().getDimensionPixelSize(R.dimen.dots_height),
requireContext().getResources().getColor(R.color.titleTextColor, null),
requireContext().getResources().getColor(R.color.titleTextColor, null))
);
}
private void initStarredArtistsView() {
bind.starredArtistsRecyclerView.setHasFixedSize(true);
starredArtistAdapter = new ArtistHorizontalAdapter(this);
bind.starredArtistsRecyclerView.setAdapter(starredArtistAdapter);
homeViewModel.getStarredArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null) bind.starredArtistsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredArtistsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.starredArtistsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.starredArtistsSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.afterFavoritesDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.starredArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(artists.size(), 5), GridLayoutManager.HORIZONTAL, false));
starredArtistAdapter.setItems(artists);
}
});
SnapHelper starredArtistSnapHelper = new PagerSnapHelper();
starredArtistSnapHelper.attachToRecyclerView(bind.starredArtistsRecyclerView);
bind.starredArtistsRecyclerView.addItemDecoration(
new DotsIndicatorDecoration(
getResources().getDimensionPixelSize(R.dimen.radius),
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
getResources().getDimensionPixelSize(R.dimen.dots_height),
requireContext().getResources().getColor(R.color.titleTextColor, null),
requireContext().getResources().getColor(R.color.titleTextColor, null))
);
}
private void initNewReleasesView() {
bind.newReleasesRecyclerView.setHasFixedSize(true);
newReleasesAlbumAdapter = new AlbumHorizontalAdapter(this, false);
bind.newReleasesRecyclerView.setAdapter(newReleasesAlbumAdapter);
homeViewModel.getRecentlyReleasedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeNewReleasesSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeNewReleasesSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.newReleasesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
newReleasesAlbumAdapter.setItems(albums);
}
});
SnapHelper newReleasesSnapHelper = new PagerSnapHelper();
newReleasesSnapHelper.attachToRecyclerView(bind.newReleasesRecyclerView);
bind.newReleasesRecyclerView.addItemDecoration(
new DotsIndicatorDecoration(
getResources().getDimensionPixelSize(R.dimen.radius),
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
getResources().getDimensionPixelSize(R.dimen.dots_height),
requireContext().getResources().getColor(R.color.titleTextColor, null),
requireContext().getResources().getColor(R.color.titleTextColor, null))
);
}
private void initYearSongView() {
bind.yearsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.yearsRecyclerView.setHasFixedSize(true);
yearAdapter = new YearAdapter(this);
bind.yearsRecyclerView.setAdapter(yearAdapter);
homeViewModel.getYearList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), years -> {
if (years == null) {
if (bind != null) bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeFlashbackSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeFlashbackSector.setVisibility(!years.isEmpty() ? View.VISIBLE : View.GONE);
yearAdapter.setItems(years);
}
});
CustomLinearSnapHelper yearSnapHelper = new CustomLinearSnapHelper();
yearSnapHelper.attachToRecyclerView(bind.yearsRecyclerView);
}
private void initMostPlayedAlbumView() {
bind.mostPlayedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.mostPlayedAlbumsRecyclerView.setHasFixedSize(true);
mostPlayedAlbumAdapter = new AlbumAdapter(this);
bind.mostPlayedAlbumsRecyclerView.setAdapter(mostPlayedAlbumAdapter);
homeViewModel.getMostPlayedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeMostPlayedAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeMostPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
// if (albums.size() < 5) reorder();
mostPlayedAlbumAdapter.setItems(albums);
}
});
CustomLinearSnapHelper mostPlayedAlbumSnapHelper = new CustomLinearSnapHelper();
mostPlayedAlbumSnapHelper.attachToRecyclerView(bind.mostPlayedAlbumsRecyclerView);
}
private void initRecentPlayedAlbumView() {
bind.recentlyPlayedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.recentlyPlayedAlbumsRecyclerView.setHasFixedSize(true);
recentlyPlayedAlbumAdapter = new AlbumAdapter(this);
bind.recentlyPlayedAlbumsRecyclerView.setAdapter(recentlyPlayedAlbumAdapter);
homeViewModel.getRecentlyPlayedAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRecentlyPlayedAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeRecentlyPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
recentlyPlayedAlbumAdapter.setItems(albums);
}
});
CustomLinearSnapHelper recentPlayedAlbumSnapHelper = new CustomLinearSnapHelper();
recentPlayedAlbumSnapHelper.attachToRecyclerView(bind.recentlyPlayedAlbumsRecyclerView);
}
private void initRecentAddedAlbumView() {
bind.recentlyAddedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.recentlyAddedAlbumsRecyclerView.setHasFixedSize(true);
recentlyAddedAlbumAdapter = new AlbumAdapter(this);
bind.recentlyAddedAlbumsRecyclerView.setAdapter(recentlyAddedAlbumAdapter);
homeViewModel.getMostRecentlyAddedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRecentlyAddedAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeRecentlyAddedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
recentlyAddedAlbumAdapter.setItems(albums);
}
});
CustomLinearSnapHelper recentAddedAlbumSnapHelper = new CustomLinearSnapHelper();
recentAddedAlbumSnapHelper.attachToRecyclerView(bind.recentlyAddedAlbumsRecyclerView);
}
private void setSlideViewOffset(ViewPager2 viewPager, float pageOffset, float pageMargin) {
viewPager.setPageTransformer((page, position) -> {
float myOffset = position * -(2 * pageOffset + pageMargin);
if (viewPager.getOrientation() == ViewPager2.ORIENTATION_HORIZONTAL) {
if (ViewCompat.getLayoutDirection(viewPager) == ViewCompat.LAYOUT_DIRECTION_RTL) {
page.setTranslationX(-myOffset);
} else {
page.setTranslationX(myOffset);
}
} else {
page.setTranslationY(myOffset);
}
});
}
public void reorder() {
if (bind != null) {
// bind.homeLinearLayoutContainer.removeAllViews();
// bind.homeLinearLayoutContainer.addView(bind.homeDiscoverSector);
// bind.homeLinearLayoutContainer.addView(bind.homeSimilarTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRadioArtistSector);
// bind.homeLinearLayoutContainer.addView(bind.homeGridTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.starredTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.starredAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.starredArtistsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyAddedAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeFlashbackSector);
// bind.homeLinearLayoutContainer.addView(bind.homeMostPlayedAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyPlayedAlbumsSector);
}
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
if (bundle.containsKey(Constants.MEDIA_MIX)) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelable(Constants.TRACK_OBJECT));
activity.setBottomSheetInPeek(true);
if (mediaBrowserListenableFuture != null) {
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.TRACK_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
if (songs != null && songs.size() > 0) {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
}
});
}
} else if (bundle.containsKey(Constants.MEDIA_CHRONOLOGY)) {
List<Child> media = bundle.getParcelableArrayList(Constants.TRACKS_OBJECT);
MediaManager.startQueue(mediaBrowserListenableFuture, media, bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
} else {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
@Override
public void onArtistClick(Bundle bundle) {
if (bundle.containsKey(Constants.MEDIA_MIX) && bundle.getBoolean(Constants.MEDIA_MIX)) {
Snackbar.make(requireView(), R.string.artist_adapter_radio_station_starting, Snackbar.LENGTH_LONG)
.setAnchorView(activity.bind.playerBottomSheet)
.show();
if (mediaBrowserListenableFuture != null) {
homeViewModel.getArtistInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.ARTIST_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}
});
}
} else if (bundle.containsKey(Constants.MEDIA_BEST_OF) && bundle.getBoolean(Constants.MEDIA_BEST_OF)) {
if (mediaBrowserListenableFuture != null) {
homeViewModel.getArtistBestOf(getViewLifecycleOwner(), bundle.getParcelable(Constants.ARTIST_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}
});
}
} else {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
}
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
@Override
public void onYearClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
}
}

View file

@ -0,0 +1,188 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentHomeTabPodcastBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.interfaces.PodcastCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.PodcastChannelHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.PodcastEpisodeAdapter;
import com.cappielloantonio.tempo.ui.dialog.PodcastChannelEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.PodcastViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Objects;
import java.util.stream.Collectors;
@UnstableApi
public class HomeTabPodcastFragment extends Fragment implements ClickCallback, PodcastCallback {
private static final String TAG = "HomeTabPodcastFragment";
private FragmentHomeTabPodcastBinding bind;
private MainActivity activity;
private PodcastViewModel podcastViewModel;
private PodcastEpisodeAdapter podcastEpisodeAdapter;
private PodcastChannelHorizontalAdapter podcastChannelHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeTabPodcastBinding.inflate(inflater, container, false);
View view = bind.getRoot();
podcastViewModel = new ViewModelProvider(requireActivity()).get(PodcastViewModel.class);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
initPodcastView();
initNewestPodcastsView();
initPodcastChannelsView();
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
bind.podcastChannelsPreTextView.setOnClickListener(v -> {
PodcastChannelEditorDialog dialog = new PodcastChannelEditorDialog(this);
dialog.show(activity.getSupportFragmentManager(), null);
});
bind.podcastChannelsTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_homeFragment_to_podcastChannelCatalogueFragment));
bind.hideSectionButton.setOnClickListener(v -> Preferences.setPodcastSectionHidden());
}
private void initPodcastView() {
podcastViewModel.getPodcastChannels(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), podcastChannels -> {
if (podcastChannels == null) {
if (bind != null) bind.homePodcastChannelsSector.setVisibility(View.GONE);
if (bind != null) bind.emptyPodcastLayout.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homePodcastChannelsSector.setVisibility(!podcastChannels.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.emptyPodcastLayout.setVisibility(podcastChannels.isEmpty() ? View.VISIBLE : View.GONE);
}
});
}
private void initPodcastChannelsView() {
bind.podcastChannelsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
podcastChannelHorizontalAdapter = new PodcastChannelHorizontalAdapter(this);
bind.podcastChannelsRecyclerView.setAdapter(podcastChannelHorizontalAdapter);
podcastViewModel.getPodcastChannels(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), podcastChannels -> {
if (podcastChannels == null) {
if (bind != null) bind.homePodcastChannelsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homePodcastChannelsSector.setVisibility(!podcastChannels.isEmpty() ? View.VISIBLE : View.GONE);
podcastChannelHorizontalAdapter.setItems(podcastChannels);
}
});
}
private void initNewestPodcastsView() {
bind.newestPodcastsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.newestPodcastsRecyclerView.addItemDecoration(UIUtil.getDividerItemDecoration(requireContext()));
podcastEpisodeAdapter = new PodcastEpisodeAdapter(this);
bind.newestPodcastsRecyclerView.setAdapter(podcastEpisodeAdapter);
podcastViewModel.getNewestPodcastEpisodes(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), podcastEpisodes -> {
if (podcastEpisodes == null) {
if (bind != null) bind.homeNewestPodcastsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeNewestPodcastsSector.setVisibility(!podcastEpisodes.isEmpty() ? View.VISIBLE : View.GONE);
podcastEpisodeAdapter.setItems(podcastEpisodes.stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList()));
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onPodcastEpisodeClick(Bundle bundle) {
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
activity.setBottomSheetInPeek(true);
}
@Override
public void onPodcastEpisodeLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
}
@Override
public void onPodcastChannelClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastChannelPageFragment, bundle);
}
@Override
public void onPodcastChannelLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastChannelBottomSheetDialog, bundle);
}
@Override
public void onDismiss() {
new Handler().postDelayed(() -> {
if (podcastViewModel != null) podcastViewModel.refreshPodcastChannels(getViewLifecycleOwner());
if (podcastViewModel != null) podcastViewModel.refreshNewestPodcastEpisodes(getViewLifecycleOwner());
}, 1000);
}
}

View file

@ -0,0 +1,146 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.databinding.FragmentHomeTabRadioBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.interfaces.RadioCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.InternetRadioStationAdapter;
import com.cappielloantonio.tempo.ui.dialog.RadioEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.RadioViewModel;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class HomeTabRadioFragment extends Fragment implements ClickCallback, RadioCallback {
private static final String TAG = "HomeTabRadioFragment";
private FragmentHomeTabRadioBinding bind;
private MainActivity activity;
private RadioViewModel radioViewModel;
private InternetRadioStationAdapter internetRadioStationAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeTabRadioBinding.inflate(inflater, container, false);
View view = bind.getRoot();
radioViewModel = new ViewModelProvider(requireActivity()).get(RadioViewModel.class);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
initRadioStationView();
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
bind.internetRadioStationPreTextView.setOnClickListener(v -> {
RadioEditorDialog dialog = new RadioEditorDialog(this);
dialog.show(activity.getSupportFragmentManager(), null);
});
bind.internetRadioStationTitleTextView.setOnLongClickListener(v -> {
radioViewModel.getInternetRadioStations(getViewLifecycleOwner());
return true;
});
bind.hideSectionButton.setOnClickListener(v -> Preferences.setRadioSectionHidden());
}
private void initRadioStationView() {
bind.internetRadioStationRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.internetRadioStationRecyclerView.setHasFixedSize(true);
internetRadioStationAdapter = new InternetRadioStationAdapter(this);
bind.internetRadioStationRecyclerView.setAdapter(internetRadioStationAdapter);
radioViewModel.getInternetRadioStations(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), internetRadioStations -> {
if (internetRadioStations == null) {
if (bind != null) bind.homeRadioStationSector.setVisibility(View.GONE);
if (bind != null) bind.emptyRadioStationLayout.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeRadioStationSector.setVisibility(!internetRadioStations.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.emptyRadioStationLayout.setVisibility(internetRadioStations.isEmpty() ? View.VISIBLE : View.GONE);
internetRadioStationAdapter.setItems(internetRadioStations);
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onInternetRadioStationClick(Bundle bundle) {
MediaManager.startRadio(mediaBrowserListenableFuture, bundle.getParcelable(Constants.INTERNET_RADIO_STATION_OBJECT));
activity.setBottomSheetInPeek(true);
}
@Override
public void onInternetRadioStationLongClick(Bundle bundle) {
RadioEditorDialog dialog = new RadioEditorDialog(() -> radioViewModel.getInternetRadioStations(getViewLifecycleOwner()));
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
}
@Override
public void onDismiss() {
new Handler().postDelayed(() -> {
if (radioViewModel != null)
radioViewModel.refreshInternetRadioStations(getViewLifecycleOwner());
}, 1000);
}
}

View file

@ -0,0 +1,105 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentIndexBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.MusicIndexAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.IndexUtil;
import com.cappielloantonio.tempo.viewmodel.IndexViewModel;
@UnstableApi
public class IndexFragment extends Fragment implements ClickCallback {
private static final String TAG = "IndexFragment";
private FragmentIndexBinding bind;
private MainActivity activity;
private IndexViewModel indexViewModel;
private MusicIndexAdapter musicIndexAdapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentIndexBinding.inflate(inflater, container, false);
View view = bind.getRoot();
indexViewModel = new ViewModelProvider(requireActivity()).get(IndexViewModel.class);
initAppBar();
initDirectoryListView();
init();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
MusicFolder musicFolder = getArguments().getParcelable(Constants.MUSIC_FOLDER_OBJECT);
if (musicFolder != null) {
indexViewModel.setMusicFolder(musicFolder);
bind.indexTitleLabel.setText(musicFolder.getName());
}
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
if (bind != null)
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
if (bind != null)
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.indexInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(indexViewModel.getMusicFolderName());
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void initDirectoryListView() {
bind.indexRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.indexRecyclerView.setHasFixedSize(true);
musicIndexAdapter = new MusicIndexAdapter(this);
bind.indexRecyclerView.setAdapter(musicIndexAdapter);
indexViewModel.getIndexes().observe(getViewLifecycleOwner(), indexes -> {
if (indexes != null) {
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
}
});
}
@Override
public void onMusicIndexClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
}
}

View file

@ -0,0 +1,17 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import com.cappielloantonio.tempo.R;
public class LandingFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing, container, false);
}
}

View file

@ -0,0 +1,308 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.GenreAdapter;
import com.cappielloantonio.tempo.ui.adapter.MusicFolderAdapter;
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
import com.google.android.gms.cast.framework.CastButtonFactory;
import java.util.Objects;
@UnstableApi
public class LibraryFragment extends Fragment implements ClickCallback {
private static final String TAG = "LibraryFragment";
private FragmentLibraryBinding bind;
private MainActivity activity;
private LibraryViewModel libraryViewModel;
private MusicFolderAdapter musicFolderAdapter;
private AlbumAdapter albumAdapter;
private ArtistAdapter artistAdapter;
private GenreAdapter genreAdapter;
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentLibraryBinding.inflate(inflater, container, false);
View view = bind.getRoot();
libraryViewModel = new ViewModelProvider(requireActivity()).get(LibraryViewModel.class);
init();
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initMusicFolderView();
initAlbumView();
initArtistView();
initGenreView();
initPlaylistSlideView();
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(true);
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_libraryFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_libraryFragment_to_settingsFragment);
return true;
}
return false;
}
private void init() {
bind.albumCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_albumCatalogueFragment));
bind.artistCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_artistCatalogueFragment));
bind.genreCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_genreCatalogueFragment));
bind.playlistCatalogueTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.PLAYLIST_ALL, Constants.PLAYLIST_ALL);
activity.navController.navigate(R.id.action_libraryFragment_to_playlistCatalogueFragment, bundle);
});
bind.albumCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshAlbumSample(getViewLifecycleOwner());
return true;
});
bind.artistCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshArtistSample(getViewLifecycleOwner());
return true;
});
bind.genreCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshGenreSample(getViewLifecycleOwner());
return true;
});
bind.playlistCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner());
return true;
});
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initMusicFolderView() {
bind.musicFolderRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.musicFolderRecyclerView.setHasFixedSize(true);
musicFolderAdapter = new MusicFolderAdapter(this);
bind.musicFolderRecyclerView.setAdapter(musicFolderAdapter);
libraryViewModel.getMusicFolders(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), musicFolders -> {
if (musicFolders == null) {
if (bind != null)
bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryMusicFolderSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryMusicFolderSector.setVisibility(!musicFolders.isEmpty() ? View.VISIBLE : View.GONE);
musicFolderAdapter.setItems(musicFolders);
}
});
}
private void initAlbumView() {
bind.albumRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.albumRecyclerView.setHasFixedSize(true);
albumAdapter = new AlbumAdapter(this);
bind.albumRecyclerView.setAdapter(albumAdapter);
libraryViewModel.getAlbumSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryAlbumSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryAlbumSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
albumAdapter.setItems(albums);
}
});
CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
albumSnapHelper.attachToRecyclerView(bind.albumRecyclerView);
}
private void initArtistView() {
bind.artistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.artistRecyclerView.setHasFixedSize(true);
artistAdapter = new ArtistAdapter(this, false, false);
bind.artistRecyclerView.setAdapter(artistAdapter);
libraryViewModel.getArtistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null)
bind.libraryArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryArtistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
artistAdapter.setItems(artists);
}
});
CustomLinearSnapHelper artistSnapHelper = new CustomLinearSnapHelper();
artistSnapHelper.attachToRecyclerView(bind.artistRecyclerView);
}
private void initGenreView() {
bind.genreRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3, GridLayoutManager.HORIZONTAL, false));
bind.genreRecyclerView.setHasFixedSize(true);
genreAdapter = new GenreAdapter(this);
bind.genreRecyclerView.setAdapter(genreAdapter);
libraryViewModel.getGenreSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), genres -> {
if (genres == null) {
if (bind != null)
bind.libraryGenrePlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryGenresSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.libraryGenrePlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryGenresSector.setVisibility(!genres.isEmpty() ? View.VISIBLE : View.GONE);
genreAdapter.setItems(genres);
}
});
CustomLinearSnapHelper genreSnapHelper = new CustomLinearSnapHelper();
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
}
private void initPlaylistSlideView() {
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistRecyclerView.setHasFixedSize(true);
playlistHorizontalAdapter = new PlaylistHorizontalAdapter(this);
bind.playlistRecyclerView.setAdapter(playlistHorizontalAdapter);
libraryViewModel.getPlaylistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), playlists -> {
if (playlists == null) {
if (bind != null)
bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryPlaylistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryPlaylistSector.setVisibility(!playlists.isEmpty() ? View.VISIBLE : View.GONE);
playlistHorizontalAdapter.setItems(playlists);
}
});
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
@Override
public void onGenreClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
}
@Override
public void onPlaylistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.playlistPageFragment, bundle);
}
@Override
public void onPlaylistLongClick(Bundle bundle) {
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
}
@Override
public void onMusicFolderClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.indexFragment, bundle);
}
}

View file

@ -0,0 +1,165 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.ui.adapter.ServerAdapter;
import com.cappielloantonio.tempo.databinding.FragmentLoginBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.interfaces.SystemCallback;
import com.cappielloantonio.tempo.model.Server;
import com.cappielloantonio.tempo.repository.SystemRepository;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.ServerSignupDialog;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.LoginViewModel;
@UnstableApi
public class LoginFragment extends Fragment implements ClickCallback {
private static final String TAG = "LoginFragment";
private FragmentLoginBinding bind;
private MainActivity activity;
private LoginViewModel loginViewModel;
private ServerAdapter serverAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.login_page_menu, menu);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class);
bind = FragmentLoginBinding.inflate(inflater, container, false);
View view = bind.getRoot();
initAppBar();
initServerListView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.serverInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.login_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void initServerListView() {
bind.serverListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.serverListRecyclerView.setHasFixedSize(true);
serverAdapter = new ServerAdapter(this);
bind.serverListRecyclerView.setAdapter(serverAdapter);
loginViewModel.getServerList().observe(getViewLifecycleOwner(), servers -> {
if (servers.size() > 0) {
if (bind != null) bind.noServerAddedTextView.setVisibility(View.GONE);
if (bind != null) bind.serverListRecyclerView.setVisibility(View.VISIBLE);
serverAdapter.setItems(servers);
} else {
if (bind != null) bind.noServerAddedTextView.setVisibility(View.VISIBLE);
if (bind != null) bind.serverListRecyclerView.setVisibility(View.GONE);
}
});
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_add) {
ServerSignupDialog dialog = new ServerSignupDialog();
dialog.show(activity.getSupportFragmentManager(), null);
return true;
}
return false;
}
@Override
public void onServerClick(Bundle bundle) {
Server server = bundle.getParcelable("server_object");
saveServerPreference(server.getServerId(), server.getAddress(), server.getUsername(), server.getPassword(), server.isLowSecurity());
SystemRepository systemRepository = new SystemRepository();
systemRepository.checkUserCredential(new SystemCallback() {
@Override
public void onError(Exception exception) {
resetServerPreference();
Toast.makeText(requireContext(), exception.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onSuccess(String password, String token, String salt) {
activity.goFromLogin();
}
});
}
@Override
public void onServerLongClick(Bundle bundle) {
ServerSignupDialog dialog = new ServerSignupDialog();
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
}
private void saveServerPreference(String serverId, String server, String user, String password, boolean isLowSecurity) {
Preferences.setServerId(serverId);
Preferences.setServer(server);
Preferences.setUser(user);
Preferences.setPassword(password);
Preferences.setLowSecurity(isLowSecurity);
App.getSubsonicClientInstance(true);
}
private void resetServerPreference() {
Preferences.setServerId(null);
Preferences.setServer(null);
Preferences.setUser(null);
Preferences.setPassword(null);
Preferences.setToken(null);
Preferences.setSalt(null);
Preferences.setLowSecurity(false);
App.getSubsonicClientInstance(true);
}
}

View file

@ -0,0 +1,290 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
import androidx.viewpager2.widget.ViewPager2;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPlayerBottomSheetBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerVerticalPager;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.stream.IntStream;
@OptIn(markerClass = UnstableApi.class)
public class PlayerBottomSheetFragment extends Fragment {
private FragmentPlayerBottomSheetBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private Handler progressBarHandler;
private Runnable progressBarRunnable;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
bind = FragmentPlayerBottomSheetBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
customizeBottomSheetBackground();
initViewPager();
setHeaderBookmarksButton();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
bindMediaController();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void customizeBottomSheetBackground() {
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 2));
}
private void initViewPager() {
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setAdapter(new PlayerControllerVerticalPager(this));
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaController.releaseFuture(mediaBrowserListenableFuture);
}
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
setMediaControllerListener(mediaBrowser);
} catch (Exception e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void setMediaControllerListener(MediaBrowser mediaBrowser) {
defineProgressBarHandler(mediaBrowser);
setMediaControllerUI(mediaBrowser);
setMetadata(mediaBrowser.getMediaMetadata());
setContentDuration(mediaBrowser.getContentDuration());
setPlayingState(mediaBrowser.isPlaying());
setHeaderMediaController();
setHeaderNextButtonState(mediaBrowser.hasNextMediaItem());
mediaBrowser.addListener(new Player.Listener() {
@Override
public void onMediaMetadataChanged(@NonNull MediaMetadata mediaMetadata) {
setMediaControllerUI(mediaBrowser);
setMetadata(mediaMetadata);
setContentDuration(mediaBrowser.getContentDuration());
}
@Override
public void onIsPlayingChanged(boolean isPlaying) {
setPlayingState(isPlaying);
}
@Override
public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
Player.Listener.super.onSkipSilenceEnabledChanged(skipSilenceEnabled);
}
@Override
public void onEvents(Player player, Player.Events events) {
setHeaderNextButtonState(mediaBrowser.hasNextMediaItem());
}
});
}
private void setMetadata(MediaMetadata mediaMetadata) {
if (mediaMetadata.extras != null) {
playerBottomSheetViewModel.setLiveMedia(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("id"));
playerBottomSheetViewModel.setLiveArtist(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("artistId"));
bind.playerHeaderLayout.playerHeaderMediaTitleLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("title")));
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("artist")));
CustomGlideRequest.Builder
.from(requireContext(), mediaMetadata.extras.getString("coverArtId"))
.build()
.into(bind.playerHeaderLayout.playerHeaderMediaCoverImage);
}
}
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
if (mediaBrowser.getMediaMetadata().extras != null) {
switch (mediaBrowser.getMediaMetadata().extras.getString("type", Constants.MEDIA_TYPE_MUSIC)) {
case Constants.MEDIA_TYPE_PODCAST:
bind.playerHeaderLayout.playerHeaderFastForwardMediaButton.setVisibility(View.VISIBLE);
bind.playerHeaderLayout.playerHeaderRewindMediaButton.setVisibility(View.VISIBLE);
bind.playerHeaderLayout.playerHeaderNextMediaButton.setVisibility(View.GONE);
break;
case Constants.MEDIA_TYPE_MUSIC:
default:
bind.playerHeaderLayout.playerHeaderFastForwardMediaButton.setVisibility(View.GONE);
bind.playerHeaderLayout.playerHeaderRewindMediaButton.setVisibility(View.GONE);
bind.playerHeaderLayout.playerHeaderNextMediaButton.setVisibility(View.VISIBLE);
break;
}
}
}
private void setContentDuration(long duration) {
bind.playerHeaderLayout.playerHeaderSeekBar.setMax((int) (duration / 1000));
}
private void setProgress(MediaBrowser mediaBrowser) {
if (bind != null)
bind.playerHeaderLayout.playerHeaderSeekBar.setProgress((int) (mediaBrowser.getCurrentPosition() / 1000), true);
}
private void setPlayingState(boolean isPlaying) {
bind.playerHeaderLayout.playerHeaderButton.setChecked(isPlaying);
runProgressBarHandler(isPlaying);
}
private void setHeaderMediaController() {
bind.playerHeaderLayout.playerHeaderButton.setOnClickListener(view -> bind.getRoot().findViewById(R.id.exo_play_pause).performClick());
bind.playerHeaderLayout.playerHeaderNextMediaButton.setOnClickListener(view -> bind.getRoot().findViewById(R.id.exo_next).performClick());
bind.playerHeaderLayout.playerHeaderRewindMediaButton.setOnClickListener(view -> bind.getRoot().findViewById(R.id.exo_rew).performClick());
bind.playerHeaderLayout.playerHeaderFastForwardMediaButton.setOnClickListener(view -> bind.getRoot().findViewById(R.id.exo_ffwd).performClick());
}
private void setHeaderNextButtonState(boolean isEnabled) {
bind.playerHeaderLayout.playerHeaderNextMediaButton.setEnabled(isEnabled);
bind.playerHeaderLayout.playerHeaderNextMediaButton.setAlpha(isEnabled ? (float) 1.0 : (float) 0.3);
}
public View getPlayerHeader() {
return requireView().findViewById(R.id.player_header_layout);
}
public void goBackToFirstPage() {
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setCurrentItem(0, false);
goToControllerPage();
}
public void goToControllerPage() {
PlayerControllerVerticalPager playerControllerVerticalPager = (PlayerControllerVerticalPager) bind.playerBodyLayout.playerBodyBottomSheetViewPager.getAdapter();
if (playerControllerVerticalPager != null) {
PlayerControllerFragment playerControllerFragment = (PlayerControllerFragment) playerControllerVerticalPager.getRegisteredFragment(0);
if (playerControllerFragment != null) {
playerControllerFragment.goToControllerPage();
}
}
}
public void goToLyricsPage() {
PlayerControllerVerticalPager playerControllerVerticalPager = (PlayerControllerVerticalPager) bind.playerBodyLayout.playerBodyBottomSheetViewPager.getAdapter();
if (playerControllerVerticalPager != null) {
PlayerControllerFragment playerControllerFragment = (PlayerControllerFragment) playerControllerVerticalPager.getRegisteredFragment(0);
if (playerControllerFragment != null) {
playerControllerFragment.goToLyricsPage();
}
}
}
private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
progressBarHandler = new Handler();
progressBarRunnable = () -> {
setProgress(mediaBrowser);
progressBarHandler.postDelayed(progressBarRunnable, 1000);
};
}
private void runProgressBarHandler(boolean isPlaying) {
if (isPlaying) {
progressBarHandler.postDelayed(progressBarRunnable, 1000);
} else {
progressBarHandler.removeCallbacks(progressBarRunnable);
}
}
private void setHeaderBookmarksButton() {
if (Preferences.isSyncronizationEnabled()) {
playerBottomSheetViewModel.getPlayQueue().observeForever(new Observer<PlayQueue>() {
@Override
public void onChanged(PlayQueue playQueue) {
playerBottomSheetViewModel.getPlayQueue().removeObserver(this);
if (bind == null) return;
if (playQueue != null && !playQueue.getEntries().isEmpty()) {
int index = IntStream.range(0, playQueue.getEntries().size()).filter(ix -> playQueue.getEntries().get(ix).getId().equals(playQueue.getCurrent())).findFirst().orElse(-1);
if (index != -1) {
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.VISIBLE);
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setOnClickListener(v -> {
MediaManager.startQueue(mediaBrowserListenableFuture, playQueue.getEntries(), index);
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE);
});
}
} else {
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE);
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setOnClickListener(null);
}
}
});
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setOnLongClickListener(v -> {
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE);
return true;
});
new Handler().postDelayed(() -> {
if (bind != null)
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE);
}, Preferences.getSyncCountdownTimer() * 1000L);
}
}
}

View file

@ -0,0 +1,356 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ToggleButton;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.util.RepeatModeUtil;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.fragment.NavHostFragment;
import androidx.viewpager2.widget.ViewPager2;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerControllerBinding;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.android.material.chip.Chip;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@UnstableApi
public class PlayerControllerFragment extends Fragment {
private static final String TAG = "PlayerCoverFragment";
private InnerFragmentPlayerControllerBinding bind;
private ViewPager2 playerMediaCoverViewPager;
private ToggleButton buttonFavorite;
private TextView playerMediaTitleLabel;
private TextView playerArtistNameLabel;
private Button playbackSpeedButton;
private ToggleButton skipSilenceToggleButton;
private Chip playerMediaExtension;
private TextView playerMediaBitrate;
private ImageView playerMediaTranscodingIcon;
private Chip playerMediaTranscodedExtension;
private TextView playerMediaTranscodedBitrate;
private MainActivity activity;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = InnerFragmentPlayerControllerBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
init();
initCoverLyricsSlideView();
initMediaListenable();
initArtistLabelButton();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeBrowser();
bindMediaController();
}
@Override
public void onStop() {
releaseBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
playerMediaCoverViewPager = bind.getRoot().findViewById(R.id.player_media_cover_view_pager);
buttonFavorite = bind.getRoot().findViewById(R.id.button_favorite);
playerMediaTitleLabel = bind.getRoot().findViewById(R.id.player_media_title_label);
playerArtistNameLabel = bind.getRoot().findViewById(R.id.player_artist_name_label);
playbackSpeedButton = bind.getRoot().findViewById(R.id.player_playback_speed_button);
skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button);
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio);
playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension);
playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate);
}
private void initializeBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
bind.nowPlayingMediaControllerView.setPlayer(mediaBrowser);
setMediaControllerListener(mediaBrowser);
} catch (Exception e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void setMediaControllerListener(MediaBrowser mediaBrowser) {
setMediaControllerUI(mediaBrowser);
setMetadata(mediaBrowser.getMediaMetadata());
setMediaInfo(mediaBrowser.getMediaMetadata());
mediaBrowser.addListener(new Player.Listener() {
@Override
public void onMediaMetadataChanged(@NonNull MediaMetadata mediaMetadata) {
setMediaControllerUI(mediaBrowser);
setMetadata(mediaMetadata);
setMediaInfo(mediaMetadata);
}
});
}
private void setMetadata(MediaMetadata mediaMetadata) {
playerMediaTitleLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.title)));
playerArtistNameLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.artist)));
playerMediaTitleLabel.setSelected(true);
playerArtistNameLabel.setSelected(true);
}
private void setMediaInfo(MediaMetadata mediaMetadata) {
if (mediaMetadata.extras != null) {
String extension = mediaMetadata.extras.getString("suffix", "Unknown format");
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0
? mediaMetadata.extras.getInt("bitrate", 0) + "kbps"
: "Original";
playerMediaExtension.setText(extension);
if (bitrate.equals("Original")) {
playerMediaBitrate.setVisibility(View.GONE);
} else {
playerMediaBitrate.setVisibility(View.VISIBLE);
playerMediaBitrate.setText(bitrate);
}
}
String transcodingExtension = MusicUtil.getTranscodingFormatPreference();
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0
? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps"
: "Original";
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
playerMediaTranscodingIcon.setVisibility(View.GONE);
playerMediaTranscodedBitrate.setVisibility(View.GONE);
playerMediaTranscodedExtension.setVisibility(View.GONE);
} else {
playerMediaTranscodingIcon.setVisibility(View.VISIBLE);
playerMediaTranscodedBitrate.setVisibility(View.VISIBLE);
playerMediaTranscodedExtension.setVisibility(View.VISIBLE);
playerMediaTranscodedExtension.setText(transcodingExtension);
playerMediaTranscodedBitrate.setText(transcodingBitrate);
}
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
playerMediaTranscodingIcon.setVisibility(View.GONE);
playerMediaTranscodedBitrate.setVisibility(View.GONE);
playerMediaTranscodedExtension.setVisibility(View.GONE);
}
}
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
initPlaybackSpeedButton(mediaBrowser);
if (mediaBrowser.getMediaMetadata().extras != null) {
switch (mediaBrowser.getMediaMetadata().extras.getString("type", Constants.MEDIA_TYPE_MUSIC)) {
case Constants.MEDIA_TYPE_PODCAST:
bind.getRoot().setShowShuffleButton(false);
bind.getRoot().setShowRewindButton(true);
bind.getRoot().setShowPreviousButton(false);
bind.getRoot().setShowNextButton(false);
bind.getRoot().setShowFastForwardButton(true);
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE);
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.VISIBLE);
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.VISIBLE);
setPlaybackParameters(mediaBrowser);
break;
case Constants.MEDIA_TYPE_RADIO:
bind.getRoot().setShowShuffleButton(false);
bind.getRoot().setShowRewindButton(false);
bind.getRoot().setShowPreviousButton(false);
bind.getRoot().setShowNextButton(false);
bind.getRoot().setShowFastForwardButton(false);
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE);
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.GONE);
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.GONE);
setPlaybackParameters(mediaBrowser);
break;
case Constants.MEDIA_TYPE_MUSIC:
default:
bind.getRoot().setShowShuffleButton(true);
bind.getRoot().setShowRewindButton(false);
bind.getRoot().setShowPreviousButton(true);
bind.getRoot().setShowNextButton(true);
bind.getRoot().setShowFastForwardButton(false);
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL | RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE);
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.GONE);
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.GONE);
resetPlaybackParameters(mediaBrowser);
break;
}
}
}
private void initCoverLyricsSlideView() {
playerMediaCoverViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
playerMediaCoverViewPager.setAdapter(new PlayerControllerHorizontalPager(this));
playerMediaCoverViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (position == 0) {
activity.setBottomSheetDraggableState(true);
} else if (position == 1) {
activity.setBottomSheetDraggableState(false);
}
}
});
}
private void initMediaListenable() {
playerBottomSheetViewModel.getLiveMedia().observe(getViewLifecycleOwner(), media -> {
if (media != null) {
buttonFavorite.setChecked(media.getStarred() != null);
buttonFavorite.setOnClickListener(v -> playerBottomSheetViewModel.setFavorite(requireContext(), media));
buttonFavorite.setOnLongClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, media);
RatingDialog dialog = new RatingDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
return true;
});
if (getActivity() != null) {
playerBottomSheetViewModel.refreshMediaInfo(requireActivity(), media);
}
}
});
}
private void initArtistLabelButton() {
playerBottomSheetViewModel.getLiveArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {
playerArtistNameLabel.setOnClickListener(view -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artist);
NavHostFragment.findNavController(this).navigate(R.id.artistPageFragment, bundle);
activity.collapseBottomSheet();
});
}
});
}
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
playbackSpeedButton.setOnClickListener(view -> {
float currentSpeed = Preferences.getPlaybackSpeed();
if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_080) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_100));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_100));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_100);
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_100) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_125));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_125));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_125);
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_125) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_150));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_150));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_150);
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_150) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_175));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_175));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_175);
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_175) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_200));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_200));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_200);
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_200) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_080));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_080));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_080);
}
});
skipSilenceToggleButton.setOnClickListener(view -> {
Preferences.setSkipSilenceMode(!skipSilenceToggleButton.isChecked());
});
}
public void goToControllerPage() {
playerMediaCoverViewPager.setCurrentItem(0, false);
}
public void goToLyricsPage() {
playerMediaCoverViewPager.setCurrentItem(1, true);
}
private void setPlaybackParameters(MediaBrowser mediaBrowser) {
Button playbackSpeedButton = bind.getRoot().findViewById(R.id.player_playback_speed_button);
float currentSpeed = Preferences.getPlaybackSpeed();
boolean skipSilence = Preferences.isSkipSilenceMode();
mediaBrowser.setPlaybackParameters(new PlaybackParameters(currentSpeed));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, currentSpeed));
// TODO Skippare il silenzio
skipSilenceToggleButton.setChecked(skipSilence);
}
private void resetPlaybackParameters(MediaBrowser mediaBrowser) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_100));
// TODO Resettare lo skip del silenzio
}
}

View file

@ -0,0 +1,173 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.transition.Fade;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerCoverBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@UnstableApi
public class PlayerCoverFragment extends Fragment {
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private InnerFragmentPlayerCoverBinding bind;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
initOverlay();
initInnerButton();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeBrowser();
bindMediaController();
toggleOverlayVisibility(false);
}
@Override
public void onStop() {
releaseBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initOverlay() {
bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true));
bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false));
}
private void toggleOverlayVisibility(boolean isVisible) {
Transition transition = new Fade();
transition.setDuration(200);
transition.addTarget(bind.nowPlayingSongCoverButtonGroup);
TransitionManager.beginDelayedTransition(bind.getRoot(), transition);
bind.nowPlayingSongCoverButtonGroup.setVisibility(isVisible ? View.VISIBLE : View.GONE);
bind.innerButtonBottomRight.setVisibility(Preferences.isSyncronizationEnabled() ? View.VISIBLE : View.GONE);
bind.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE);
}
private void initInnerButton() {
playerBottomSheetViewModel.getLiveMedia().observe(getViewLifecycleOwner(), song -> {
if (song != null && bind != null) {
bind.innerButtonTopLeft.setOnClickListener(view -> {
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownload(song),
new Download(song)
);
});
bind.innerButtonTopRight.setOnClickListener(view -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, song);
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
}
);
bind.innerButtonBottomLeft.setOnClickListener(view -> {
playerBottomSheetViewModel.getMediaInstantMix(getViewLifecycleOwner(), song).observe(getViewLifecycleOwner(), media -> {
MediaManager.enqueue(mediaBrowserListenableFuture, media, true);
});
});
bind.innerButtonBottomRight.setOnClickListener(view -> {
if (playerBottomSheetViewModel.savePlayQueue()) {
Snackbar.make(requireView(), "Salvato", Snackbar.LENGTH_LONG).show();
}
});
bind.innerButtonBottomRightAlternative.setOnClickListener(view -> {
if (getActivity() != null) {
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
if (playerBottomSheetFragment != null) {
playerBottomSheetFragment.goToLyricsPage();
}
}
});
}
});
}
private void initializeBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
setMediaBrowserListener(mediaBrowser);
} catch (Exception exception) {
exception.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void setMediaBrowserListener(MediaBrowser mediaBrowser) {
setCover(mediaBrowser.getMediaMetadata());
mediaBrowser.addListener(new Player.Listener() {
@Override
public void onMediaMetadataChanged(@NonNull MediaMetadata mediaMetadata) {
setCover(mediaMetadata);
toggleOverlayVisibility(false);
}
});
}
private void setCover(MediaMetadata mediaMetadata) {
CustomGlideRequest.Builder
.from(requireContext(), mediaMetadata.extras != null ? mediaMetadata.extras.getString("coverArtId") : null)
.build()
.into(bind.nowPlayingSongCoverImageView);
}
}

View file

@ -0,0 +1,62 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerLyricsBinding;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
public class PlayerLyricsFragment extends Fragment {
private static final String TAG = "PlayerLyricsFragment";
private InnerFragmentPlayerLyricsBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerLyricsBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initLyrics();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initLyrics() {
playerBottomSheetViewModel.getLiveLyrics().observe(getViewLifecycleOwner(), lyrics -> {
if (bind != null) {
if (lyrics == null || lyrics.trim().equals("")) {
bind.nowPlayingSongLyricsTextView.setVisibility(View.GONE);
bind.emptyDescriptionImageView.setVisibility(View.VISIBLE);
bind.titleEmptyDescriptionLabel.setVisibility(View.VISIBLE);
} else {
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(lyrics));
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
bind.emptyDescriptionImageView.setVisibility(View.GONE);
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
}
}
});
}
}

View file

@ -0,0 +1,162 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerQueueBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.stream.Collectors;
@UnstableApi
public class PlayerQueueFragment extends Fragment implements ClickCallback {
private InnerFragmentPlayerQueueBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private PlayerSongQueueAdapter playerSongQueueAdapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerQueueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
initQueueRecyclerView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeBrowser();
}
@Override
public void onResume() {
super.onResume();
setMediaBrowserListenableFuture();
updateNowPlayingItem();
}
@Override
public void onStop() {
releaseBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initializeBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void setMediaBrowserListenableFuture() {
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
private void initQueueRecyclerView() {
bind.playerQueueRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playerQueueRecyclerView.setHasFixedSize(true);
playerSongQueueAdapter = new PlayerSongQueueAdapter(this);
bind.playerQueueRecyclerView.setAdapter(playerSongQueueAdapter);
playerBottomSheetViewModel.getQueueSong().observe(getViewLifecycleOwner(), queue -> {
if (queue != null) {
playerSongQueueAdapter.setItems(queue.stream().map(item -> (Child) item).collect(Collectors.toList()));
}
});
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
int originalPosition = -1;
int fromPosition = -1;
int toPosition = -1;
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
if (originalPosition == -1) {
originalPosition = viewHolder.getBindingAdapterPosition();
}
fromPosition = viewHolder.getBindingAdapterPosition();
toPosition = target.getBindingAdapterPosition();
/*
* Per spostare un elemento nella coda devo:
* - Spostare graficamente la traccia da una posizione all'altra con Collections.swap()
* - Spostare nel db la traccia, tramite QueueRepository
* - Notificare il Service dell'avvenuto spostamento con MusicPlayerRemote.moveSong()
*
* In onMove prendo la posizione di inizio e fine, ma solo al rilascio dell'elemento procedo allo spostamento
* In questo modo evito che ad ogni cambio di posizione vada a riscrivere nel db
* Al rilascio dell'elemento chiamo il metodo clearView()
*/
Collections.swap(playerSongQueueAdapter.getItems(), fromPosition, toPosition);
recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
return false;
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (originalPosition != -1 && fromPosition != -1 && toPosition != -1) {
MediaManager.swap(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), originalPosition, toPosition);
}
originalPosition = -1;
fromPosition = -1;
toPosition = -1;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
MediaManager.remove(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), viewHolder.getBindingAdapterPosition());
viewHolder.getBindingAdapter().notifyDataSetChanged();
}
}).attachToRecyclerView(bind.playerQueueRecyclerView);
}
private void updateNowPlayingItem() {
playerSongQueueAdapter.notifyDataSetChanged();
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
}
}

View file

@ -0,0 +1,186 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPlaylistCatalogueBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlaylistCatalogueViewModel;
@UnstableApi
public class PlaylistCatalogueFragment extends Fragment implements ClickCallback {
private FragmentPlaylistCatalogueBinding bind;
private MainActivity activity;
private PlaylistCatalogueViewModel playlistCatalogueViewModel;
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentPlaylistCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playlistCatalogueViewModel = new ViewModelProvider(requireActivity()).get(PlaylistCatalogueViewModel.class);
init();
initAppBar();
initPlaylistCatalogueView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
if (requireArguments().getString(Constants.PLAYLIST_ALL) != null) {
playlistCatalogueViewModel.setType(Constants.PLAYLIST_ALL);
} else if (requireArguments().getString(Constants.PLAYLIST_DOWNLOADED) != null) {
playlistCatalogueViewModel.setType(Constants.PLAYLIST_DOWNLOADED);
}
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.playlist_catalogue_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void initPlaylistCatalogueView() {
bind.playlistCatalogueRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistCatalogueRecyclerView.setHasFixedSize(true);
playlistHorizontalAdapter = new PlaylistHorizontalAdapter(this);
bind.playlistCatalogueRecyclerView.setAdapter(playlistHorizontalAdapter);
if (getActivity() != null) {
playlistCatalogueViewModel.getPlaylistList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), playlists -> {
if (playlists != null) playlistHorizontalAdapter.setItems(playlists);
});
}
bind.playlistCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.playlistListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_playlist_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
playlistHorizontalAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_playlist_sort_name) {
playlistHorizontalAdapter.sort(Constants.GENRE_ORDER_BY_NAME);
return true;
} else if (menuItem.getItemId() == R.id.menu_playlist_sort_random) {
playlistHorizontalAdapter.sort(Constants.GENRE_ORDER_BY_RANDOM);
return true;
}
return false;
});
popup.show();
}
@Override
public void onPlaylistClick(Bundle bundle) {
bundle.putBoolean("is_offline", false);
Navigation.findNavController(requireView()).navigate(R.id.playlistPageFragment, bundle);
hideKeyboard(requireView());
}
@Override
public void onPlaylistLongClick(Bundle bundle) {
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
hideKeyboard(requireView());
}
}

View file

@ -0,0 +1,197 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.PlaylistPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;
@UnstableApi
public class PlaylistPageFragment extends Fragment implements ClickCallback {
private FragmentPlaylistPageBinding bind;
private MainActivity activity;
private PlaylistPageViewModel playlistPageViewModel;
private SongHorizontalAdapter songHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.playlist_page_menu, menu);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentPlaylistPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playlistPageViewModel = new ViewModelProvider(requireActivity()).get(PlaylistPageViewModel.class);
init();
initAppBar();
initMusicButton();
initBackCover();
initSongsView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_download_playlist) {
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (isVisible() && getActivity() != null) {
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownloads(songs),
songs.stream().map(child -> {
Download toDownload = new Download(child);
toDownload.setPlaylistId(playlistPageViewModel.getPlaylist().getId());
toDownload.setPlaylistName(playlistPageViewModel.getPlaylist().getName());
return toDownload;
}).collect(Collectors.toList())
);
}
});
return true;
}
return false;
}
private void init() {
playlistPageViewModel.setPlaylist(requireArguments().getParcelable(Constants.PLAYLIST_OBJECT));
}
private void initAppBar() {
activity.setSupportActionBar(bind.animToolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.animToolbar.setTitle(MusicUtil.getReadableString(playlistPageViewModel.getPlaylist().getName()));
bind.playlistNameLabel.setText(MusicUtil.getReadableString(playlistPageViewModel.getPlaylist().getName()));
bind.playlistSongCountLabel.setText(getString(R.string.playlist_song_count, playlistPageViewModel.getPlaylist().getSongCount()));
bind.playlistDurationLabel.setText(getString(R.string.playlist_duration, MusicUtil.getReadableDurationString(playlistPageViewModel.getPlaylist().getDuration(), false)));
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
Objects.requireNonNull(bind.animToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initMusicButton() {
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) {
bind.playlistPagePlayButton.setOnClickListener(v -> {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
});
bind.playlistPageShuffleButton.setOnClickListener(v -> {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
});
}
});
}
private void initBackCover() {
CustomGlideRequest.Builder
.from(requireContext(), playlistPageViewModel.getPlaylist().getCoverArtId())
.build()
.into(bind.playlistCoverImageView);
}
private void initSongsView() {
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,155 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelCatalogueBinding;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.PodcastChannelCatalogueAdapter;
import com.cappielloantonio.tempo.viewmodel.PodcastChannelCatalogueViewModel;
@OptIn(markerClass = UnstableApi.class)
public class PodcastChannelCatalogueFragment extends Fragment implements ClickCallback {
private static final String TAG = "PodcastChannelCatalogue";
private FragmentPodcastChannelCatalogueBinding bind;
private MainActivity activity;
private PodcastChannelCatalogueViewModel podcastChannelCatalogueViewModel;
private PodcastChannelCatalogueAdapter podcastChannelCatalogueAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentPodcastChannelCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
podcastChannelCatalogueViewModel = new ViewModelProvider(requireActivity()).get(PodcastChannelCatalogueViewModel.class);
initAppBar();
initPodcastChannelCatalogueView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.podcastChannelInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.podcast_channel_catalogue_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void initPodcastChannelCatalogueView() {
bind.podcastChannelCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.podcastChannelCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.podcastChannelCatalogueRecyclerView.setHasFixedSize(true);
podcastChannelCatalogueAdapter = new PodcastChannelCatalogueAdapter(this);
podcastChannelCatalogueAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.podcastChannelCatalogueRecyclerView.setAdapter(podcastChannelCatalogueAdapter);
podcastChannelCatalogueViewModel.getPodcastChannels(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums != null) {
podcastChannelCatalogueAdapter.setItems(albums);
}
});
bind.podcastChannelCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
podcastChannelCatalogueAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
@Override
public void onPodcastChannelClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastChannelPageFragment, bundle);
hideKeyboard(requireView());
}
@Override
public void onPodcastChannelLongClick(Bundle bundle) {
// Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,158 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.PodcastEpisodeAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@UnstableApi
public class PodcastChannelPageFragment extends Fragment implements ClickCallback {
private FragmentPodcastChannelPageBinding bind;
private MainActivity activity;
private PodcastChannelPageViewModel podcastChannelPageViewModel;
private PodcastEpisodeAdapter podcastEpisodeAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentPodcastChannelPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
podcastChannelPageViewModel = new ViewModelProvider(requireActivity()).get(PodcastChannelPageViewModel.class);
init();
initAppBar();
initPodcastChannelInfo();
initPodcastChannelEpisodesView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
podcastChannelPageViewModel.setPodcastChannel(requireArguments().getParcelable(Constants.PODCAST_CHANNEL_OBJECT));
}
private void initAppBar() {
activity.setSupportActionBar(bind.animToolbar);
if (activity.getSupportActionBar() != null)
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
bind.collapsingToolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.collapsingToolbar.setExpandedTitleColor(getResources().getColor(R.color.white, null));
}
private void initPodcastChannelInfo() {
String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription());
if (bind != null)
bind.podcastChannelDescriptionTextView.setVisibility(!normalizePodcastChannelDescription.trim().isEmpty() ? View.VISIBLE : View.GONE);
if (getContext() != null && bind != null) CustomGlideRequest.Builder
.from(requireContext(), podcastChannelPageViewModel.getPodcastChannel().getCoverArtId())
.build()
.into(bind.podcastChannelBackdropImageView);
if (bind != null)
bind.podcastChannelDescriptionTextView.setText(normalizePodcastChannelDescription);
}
private void initPodcastChannelEpisodesView() {
bind.podcastEpisodesRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.podcastEpisodesRecyclerView.addItemDecoration(UIUtil.getDividerItemDecoration(requireContext()));
podcastEpisodeAdapter = new PodcastEpisodeAdapter(this);
bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter);
podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> {
if (channels == null) {
if (bind != null)
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.podcastChannelPageEpisodesSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.GONE);
if (!channels.isEmpty() && channels.get(0) != null && channels.get(0).getEpisodes() != null) {
List<PodcastEpisode> availableEpisode = channels.get(0).getEpisodes().stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
if (bind != null) {
bind.podcastEpisodesRecyclerView.setVisibility(availableEpisode.isEmpty() ? View.GONE : View.VISIBLE);
bind.podcastEpisodesAvailabilityTextView.setVisibility(availableEpisode.isEmpty() ? View.VISIBLE : View.GONE);
}
podcastEpisodeAdapter.setItems(availableEpisode);
}
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onPodcastEpisodeClick(Bundle bundle) {
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
activity.setBottomSheetInPeek(true);
}
@Override
public void onPodcastEpisodeLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,260 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentSearchBinding;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import com.paulrybitskyi.persistentsearchview.adapters.model.SuggestionItem;
import com.paulrybitskyi.persistentsearchview.listeners.OnSuggestionChangeListener;
import com.paulrybitskyi.persistentsearchview.utils.SuggestionCreationUtil;
import java.util.Collections;
@UnstableApi
public class SearchFragment extends Fragment implements ClickCallback {
private FragmentSearchBinding bind;
private MainActivity activity;
private SearchViewModel searchViewModel;
private ArtistAdapter artistAdapter;
private AlbumAdapter albumAdapter;
private SongHorizontalAdapter songHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentSearchBinding.inflate(inflater, container, false);
View view = bind.getRoot();
searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class);
initSearchResultView();
initSearchView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onResume() {
super.onResume();
inputFocus();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initSearchResultView() {
// Artists
bind.searchResultArtistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.searchResultArtistRecyclerView.setHasFixedSize(true);
artistAdapter = new ArtistAdapter(this, false, false);
bind.searchResultArtistRecyclerView.setAdapter(artistAdapter);
CustomLinearSnapHelper artistSnapHelper = new CustomLinearSnapHelper();
artistSnapHelper.attachToRecyclerView(bind.searchResultArtistRecyclerView);
// Albums
bind.searchResultAlbumRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.searchResultAlbumRecyclerView.setHasFixedSize(true);
albumAdapter = new AlbumAdapter(this);
bind.searchResultAlbumRecyclerView.setAdapter(albumAdapter);
CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
albumSnapHelper.attachToRecyclerView(bind.searchResultAlbumRecyclerView);
// Songs
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
}
private void initSearchView() {
if (isQueryValid(searchViewModel.getQuery())) {
search(searchViewModel.getQuery());
}
bind.persistentSearchView.setInputQuery(searchViewModel.getQuery());
setSuggestions();
bind.persistentSearchView.setOnSearchQueryChangeListener((searchView, oldQuery, newQuery) -> {
if (!newQuery.trim().equals("") && newQuery.trim().length() > 1) {
searchViewModel.getSearchSuggestion(newQuery).observe(getViewLifecycleOwner(), suggestions -> searchView.setSuggestions(SuggestionCreationUtil.asRegularSearchSuggestions(MusicUtil.getReadableStrings(suggestions)), false));
} else {
setSuggestions();
}
});
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
@Override
public void onSuggestionPicked(SuggestionItem suggestion) {
search(suggestion.getItemModel().getText());
}
@Override
public void onSuggestionRemoved(SuggestionItem suggestion) {
}
});
bind.persistentSearchView.setOnSearchConfirmedListener((searchView, query) -> {
if (isQueryValid(query)) {
searchView.collapse();
search(query);
} else {
Toast.makeText(requireContext(), getString(R.string.search_info_minimum_characters), Toast.LENGTH_SHORT).show();
}
});
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
@Override
public void onSuggestionPicked(SuggestionItem suggestion) {
search(suggestion.getItemModel().getText());
}
@Override
public void onSuggestionRemoved(SuggestionItem suggestion) {
searchViewModel.deleteRecentSearch(suggestion.getItemModel().getText());
}
});
bind.persistentSearchView.setOnClearInputBtnClickListener(v -> searchViewModel.setQuery(""));
}
private void setSuggestions() {
bind.persistentSearchView.setSuggestions(SuggestionCreationUtil.asRecentSearchSuggestions(searchViewModel.getRecentSearchSuggestion()), false);
}
public void search(String query) {
searchViewModel.setQuery(query);
bind.persistentSearchView.setInputQuery(query);
performSearch(query);
}
private void performSearch(String query) {
searchViewModel.search(query).observe(getViewLifecycleOwner(), result -> {
if (bind != null) {
if (result.getArtists() != null) {
bind.searchArtistSector.setVisibility(!result.getArtists().isEmpty() ? View.VISIBLE : View.GONE);
artistAdapter.setItems(result.getArtists());
} else {
artistAdapter.setItems(Collections.emptyList());
bind.searchArtistSector.setVisibility(View.GONE);
}
if (result.getAlbums() != null) {
bind.searchAlbumSector.setVisibility(!result.getAlbums().isEmpty() ? View.VISIBLE : View.GONE);
albumAdapter.setItems(result.getAlbums());
} else {
albumAdapter.setItems(Collections.emptyList());
bind.searchAlbumSector.setVisibility(View.GONE);
}
if (result.getSongs() != null) {
bind.searchSongSector.setVisibility(!result.getSongs().isEmpty() ? View.VISIBLE : View.GONE);
songHorizontalAdapter.setItems(result.getSongs());
} else {
songHorizontalAdapter.setItems(Collections.emptyList());
bind.searchSongSector.setVisibility(View.GONE);
}
}
});
bind.searchResultLayout.setVisibility(View.VISIBLE);
}
private boolean isQueryValid(String query) {
return !query.equals("") && query.trim().length() > 2;
}
private void inputFocus() {
bind.persistentSearchView.expand();
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,144 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.Intent;
import android.media.audiofx.AudioEffect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.helper.ThemeHelper;
import com.cappielloantonio.tempo.interfaces.ScanCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
@OptIn(markerClass = UnstableApi.class)
public class SettingsFragment extends PreferenceFragmentCompat {
private static final String TAG = "SettingsFragment";
private MainActivity activity;
private SettingViewModel settingViewModel;
private ActivityResultLauncher<Intent> someActivityResultLauncher;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
someActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
});
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
View view = super.onCreateView(inflater, container, savedInstanceState);
settingViewModel = new ViewModelProvider(requireActivity()).get(SettingViewModel.class);
if (view != null) {
getListView().setPadding(0, 0, 0, (int) getResources().getDimension(R.dimen.global_padding_bottom));
}
return view;
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(false);
activity.setBottomSheetVisibility(false);
}
@Override
public void onResume() {
super.onResume();
findPreference("logout").setOnPreferenceClickListener(preference -> {
activity.quit();
return true;
});
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
settingViewModel.launchScan(new ScanCallback() {
@Override
public void onError(Exception exception) {
findPreference("scan_library").setSummary(exception.getMessage());
}
@Override
public void onSuccess(boolean isScanning, long count) {
getScanStatus();
}
});
return true;
});
findPreference("equalizer").setOnPreferenceClickListener(preference -> {
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
someActivityResultLauncher.launch(intent);
return true;
});
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredSyncDialog dialog = new StarredSyncDialog();
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
@Override
public void onStop() {
super.onStop();
activity.setBottomSheetVisibility(true);
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.global_preferences, rootKey);
ListPreference themePreference = findPreference(Preferences.THEME);
if (themePreference != null) {
themePreference.setOnPreferenceChangeListener(
(preference, newValue) -> {
String themeOption = (String) newValue;
ThemeHelper.applyTheme(themeOption);
return true;
});
}
}
private void getScanStatus() {
settingViewModel.getScanStatus(new ScanCallback() {
@Override
public void onError(Exception exception) {
findPreference("scan_library").setSummary(exception.getMessage());
}
@Override
public void onSuccess(boolean isScanning, long count) {
findPreference("scan_library").setSummary("Scanning: counting " + count + " tracks");
if (isScanning) getScanStatus();
}
});
}
}

View file

@ -0,0 +1,208 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentSongListPageBinding;
import com.cappielloantonio.tempo.helper.recyclerview.PaginationScrollListener;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.SongListPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
@UnstableApi
public class SongListPageFragment extends Fragment implements ClickCallback {
private static final String TAG = "SongListPageFragment";
private FragmentSongListPageBinding bind;
private MainActivity activity;
private SongListPageViewModel songListPageViewModel;
private SongHorizontalAdapter songHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private boolean isLoading = true;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentSongListPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
songListPageViewModel = new ViewModelProvider(requireActivity()).get(SongListPageViewModel.class);
init();
initAppBar();
initButtons();
initSongListView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
if (requireArguments().getString(Constants.MEDIA_RECENTLY_PLAYED) != null) {
songListPageViewModel.title = Constants.MEDIA_RECENTLY_PLAYED;
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_recently_played);
bind.pageTitleLabel.setText(R.string.song_list_page_recently_played);
} else if (requireArguments().getString(Constants.MEDIA_MOST_PLAYED) != null) {
songListPageViewModel.title = Constants.MEDIA_MOST_PLAYED;
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_most_played);
bind.pageTitleLabel.setText(R.string.song_list_page_most_played);
} else if (requireArguments().getString(Constants.MEDIA_RECENTLY_ADDED) != null) {
songListPageViewModel.title = Constants.MEDIA_RECENTLY_ADDED;
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_recently_added);
bind.pageTitleLabel.setText(R.string.song_list_page_recently_added);
} else if (requireArguments().getString(Constants.MEDIA_BY_GENRE) != null) {
songListPageViewModel.title = Constants.MEDIA_BY_GENRE;
songListPageViewModel.genre = requireArguments().getParcelable(Constants.GENRE_OBJECT);
songListPageViewModel.toolbarTitle = MusicUtil.getReadableString(songListPageViewModel.genre.getGenre());
bind.pageTitleLabel.setText(MusicUtil.getReadableString(songListPageViewModel.genre.getGenre()));
} else if (requireArguments().getString(Constants.MEDIA_BY_ARTIST) != null) {
songListPageViewModel.title = Constants.MEDIA_BY_ARTIST;
songListPageViewModel.artist = requireArguments().getParcelable(Constants.ARTIST_OBJECT);
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_top, MusicUtil.getReadableString(songListPageViewModel.artist.getName()));
bind.pageTitleLabel.setText(getString(R.string.song_list_page_top, MusicUtil.getReadableString(songListPageViewModel.artist.getName())));
} else if (requireArguments().getString(Constants.MEDIA_BY_GENRES) != null) {
songListPageViewModel.title = Constants.MEDIA_BY_GENRES;
songListPageViewModel.filters = requireArguments().getStringArrayList("filters_list");
songListPageViewModel.filterNames = requireArguments().getStringArrayList("filter_name_list");
songListPageViewModel.toolbarTitle = songListPageViewModel.getFiltersTitle();
bind.pageTitleLabel.setText(songListPageViewModel.getFiltersTitle());
} else if (requireArguments().getString(Constants.MEDIA_BY_YEAR) != null) {
songListPageViewModel.title = Constants.MEDIA_BY_YEAR;
songListPageViewModel.year = requireArguments().getInt("year_object");
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_year, songListPageViewModel.year);
bind.pageTitleLabel.setText(getString(R.string.song_list_page_year, songListPageViewModel.year));
} else if (requireArguments().getString(Constants.MEDIA_STARRED) != null) {
songListPageViewModel.title = Constants.MEDIA_STARRED;
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_starred);
bind.pageTitleLabel.setText(R.string.song_list_page_starred);
} else if (requireArguments().getString(Constants.MEDIA_DOWNLOADED) != null) {
songListPageViewModel.title = Constants.MEDIA_DOWNLOADED;
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_downloaded);
bind.pageTitleLabel.setText(getString(R.string.song_list_page_downloaded));
} else if (requireArguments().getParcelable(Constants.ALBUM_OBJECT) != null) {
songListPageViewModel.album = requireArguments().getParcelable(Constants.ALBUM_OBJECT);
songListPageViewModel.title = Constants.MEDIA_FROM_ALBUM;
songListPageViewModel.toolbarTitle = MusicUtil.getReadableString(songListPageViewModel.album.getName());
bind.pageTitleLabel.setText(MusicUtil.getReadableString(songListPageViewModel.album.getName()));
}
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
if (bind != null)
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
if (bind != null)
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(songListPageViewModel.toolbarTitle);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void initButtons() {
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) {
bind.songListShuffleImageView.setOnClickListener(v -> {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(25, songs.size())), 0);
activity.setBottomSheetInPeek(true);
});
}
});
}
private void initSongListView() {
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songListRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
isLoading = false;
songHorizontalAdapter.setItems(songs);
});
bind.songListRecyclerView.addOnScrollListener(new PaginationScrollListener((LinearLayoutManager) bind.songListRecyclerView.getLayoutManager()) {
@Override
protected void loadMoreItems() {
isLoading = true;
songListPageViewModel.getSongsByPage(getViewLifecycleOwner());
}
@Override
public boolean isLoading() {
return isLoading;
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,202 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.fragment.NavHostFragment;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.MediaCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@UnstableApi
public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private AlbumBottomSheetViewModel albumBottomSheetViewModel;
private AlbumID3 album;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_album_dialog, container, false);
album = this.requireArguments().getParcelable(Constants.ALBUM_OBJECT);
albumBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(AlbumBottomSheetViewModel.class);
albumBottomSheetViewModel.setAlbum(album);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
private void init(View view) {
ImageView coverAlbum = view.findViewById(R.id.album_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), albumBottomSheetViewModel.getAlbum().getCoverArtId())
.build()
.into(coverAlbum);
TextView titleAlbum = view.findViewById(R.id.album_title_text_view);
titleAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getName()));
titleAlbum.setSelected(true);
TextView artistAlbum = view.findViewById(R.id.album_artist_text_view);
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(Boolean.TRUE.equals(albumBottomSheetViewModel.getAlbum().getStarred()));
favoriteToggle.setOnClickListener(v -> {
albumBottomSheetViewModel.setFavorite();
dismissBottomSheet();
});
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
playRadio.setOnClickListener(v -> {
AlbumRepository albumRepository = new AlbumRepository();
albumRepository.getInstantMix(album, 20, new MediaCallback() {
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
@Override
public void onLoadMedia(List<?> media) {
if (media.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
}
dismissBottomSheet();
}
});
});
TextView playRandom = view.findViewById(R.id.play_random_text_view);
playRandom.setOnClickListener(v -> {
AlbumRepository albumRepository = new AlbumRepository();
albumRepository.getAlbumTracks(album.getId()).observe(getViewLifecycleOwner(), songs -> {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
});
TextView playNext = view.findViewById(R.id.play_next_text_view);
playNext.setOnClickListener(v -> albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
}));
TextView addToQueue = view.findViewById(R.id.add_to_queue_text_view);
addToQueue.setOnClickListener(v -> albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, false);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
}));
TextView downloadAll = view.findViewById(R.id.download_all_text_view);
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
downloadAll.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).download(mediaItems, downloads);
dismissBottomSheet();
});
if (DownloadUtil.getDownloadTracker(requireContext()).areDownloaded(mediaItems)) {
removeAll.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems, downloads);
dismissBottomSheet();
});
} else {
removeAll.setVisibility(View.GONE);
}
});
TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view);
goToArtist.setOnClickListener(v -> albumBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artist);
NavHostFragment.findNavController(this).navigate(R.id.artistPageFragment, bundle);
} else {
Toast.makeText(requireContext(), getString(R.string.album_error_retrieving_artist), Toast.LENGTH_SHORT).show();
}
dismissBottomSheet();
}));
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View file

@ -0,0 +1,136 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.ArtistBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private static final String TAG = "AlbumBottomSheetDialog";
private ArtistBottomSheetViewModel artistBottomSheetViewModel;
private ArtistID3 artist;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_artist_dialog, container, false);
artist = this.requireArguments().getParcelable(Constants.ARTIST_OBJECT);
artistBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(ArtistBottomSheetViewModel.class);
artistBottomSheetViewModel.setArtist(artist);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
// TODO Utilizzare il viewmodel come tramite ed evitare le chiamate dirette
private void init(View view) {
ImageView coverArtist = view.findViewById(R.id.artist_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), artistBottomSheetViewModel.getArtist().getCoverArtId())
.build()
.into(coverArtist);
TextView nameArtist = view.findViewById(R.id.song_title_text_view);
nameArtist.setText(MusicUtil.getReadableString(artistBottomSheetViewModel.getArtist().getName()));
nameArtist.setSelected(true);
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(Boolean.TRUE.equals(artistBottomSheetViewModel.getArtist().getStarred()));
favoriteToggle.setOnClickListener(v -> {
artistBottomSheetViewModel.setFavorite();
dismissBottomSheet();
});
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
playRadio.setOnClickListener(v -> {
ArtistRepository artistRepository = new ArtistRepository();
artistRepository.getInstantMix(artist, 20).observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
}
dismissBottomSheet();
});
});
TextView playRandom = view.findViewById(R.id.play_random_text_view);
playRandom.setOnClickListener(v -> {
ArtistRepository artistRepository = new ArtistRepository();
artistRepository.getRandomSong(artist, 50).observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
} else {
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
}
dismissBottomSheet();
});
});
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View file

@ -0,0 +1,96 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.PodcastChannelBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class PodcastChannelBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private PodcastChannelBottomSheetViewModel podcastChannelBottomSheetViewModel;
private PodcastChannel podcastChannel;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_podcast_channel_dialog, container, false);
podcastChannel = requireArguments().getParcelable(Constants.PODCAST_CHANNEL_OBJECT);
podcastChannelBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PodcastChannelBottomSheetViewModel.class);
podcastChannelBottomSheetViewModel.setPodcastChannel(podcastChannel);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
private void init(View view) {
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), podcastChannelBottomSheetViewModel.getPodcastChannel().getCoverArtId())
.build()
.into(coverPodcast);
TextView titlePodcast = view.findViewById(R.id.podcast_title_text_view);
titlePodcast.setText(MusicUtil.getReadableString(podcastChannelBottomSheetViewModel.getPodcastChannel().getTitle()));
TextView delete = view.findViewById(R.id.delete_text_view);
delete.setOnClickListener(v -> {
podcastChannelBottomSheetViewModel.deletePodcastChannel();
dismissBottomSheet();
});
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View file

@ -0,0 +1,155 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.PodcastEpisodeBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class PodcastEpisodeBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private PodcastEpisodeBottomSheetViewModel podcastEpisodeBottomSheetViewModel;
private PodcastEpisode podcastEpisode;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_podcast_episode_dialog, container, false);
podcastEpisode = requireArguments().getParcelable(Constants.PODCAST_OBJECT);
podcastEpisodeBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PodcastEpisodeBottomSheetViewModel.class);
podcastEpisodeBottomSheetViewModel.setPodcastEpisode(podcastEpisode);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
private void init(View view) {
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getCoverArtId())
.build()
.into(coverPodcast);
TextView titlePodcast = view.findViewById(R.id.podcast_title_text_view);
titlePodcast.setText(MusicUtil.getReadableString(podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getTitle()));
titlePodcast.setSelected(true);
TextView playNext = view.findViewById(R.id.play_next_text_view);
playNext.setOnClickListener(v -> {
// TODO
// MediaManager.enqueue(mediaBrowserListenableFuture, podcast, true);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView addToQueue = view.findViewById(R.id.add_to_queue_text_view);
addToQueue.setOnClickListener(v -> {
// TODO
// MediaManager.enqueue(mediaBrowserListenableFuture, podcast, false);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView download = view.findViewById(R.id.download_text_view);
download.setOnClickListener(v -> {
// TODO
/* DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapMediaItem(podcast, false),
MappingUtil.mapDownload(podcast, null, null)
); */
dismissBottomSheet();
});
TextView remove = view.findViewById(R.id.remove_text_view);
remove.setOnClickListener(v -> {
// TODO
/* DownloadUtil.getDownloadTracker(requireContext()).remove(
MappingUtil.mapMediaItem(podcast, false),
MappingUtil.mapDownload(podcast, null, null)
); */
dismissBottomSheet();
});
initDownloadUI(download, remove);
TextView delete = view.findViewById(R.id.delete_text_view);
delete.setOnClickListener(v -> {
podcastEpisodeBottomSheetViewModel.deletePodcastEpisode();
dismissBottomSheet();
});
TextView goToChannel = view.findViewById(R.id.go_to_channel_text_view);
goToChannel.setOnClickListener(v -> {
Toast.makeText(requireContext(), "Open the channel", Toast.LENGTH_SHORT).show();
dismissBottomSheet();
});
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initDownloadUI(TextView download, TextView remove) {
// TODO
/* if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(MappingUtil.mapMediaItem(podcast, false))) {
download.setVisibility(View.GONE);
remove.setVisibility(View.VISIBLE);
} else {
download.setVisibility(View.VISIBLE);
remove.setVisibility(View.GONE);
} */
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View file

@ -0,0 +1,234 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.fragment.NavHostFragment;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private static final String TAG = "SongBottomSheetDialog";
private SongBottomSheetViewModel songBottomSheetViewModel;
private Child song;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_song_dialog, container, false);
song = requireArguments().getParcelable(Constants.TRACK_OBJECT);
songBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(SongBottomSheetViewModel.class);
songBottomSheetViewModel.setSong(song);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
private void init(View view) {
ImageView coverSong = view.findViewById(R.id.song_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), songBottomSheetViewModel.getSong().getCoverArtId())
.build()
.into(coverSong);
TextView titleSong = view.findViewById(R.id.song_title_text_view);
titleSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getTitle()));
titleSong.setSelected(true);
TextView artistSong = view.findViewById(R.id.song_artist_text_view);
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(Boolean.TRUE.equals(songBottomSheetViewModel.getSong().getStarred()));
favoriteToggle.setOnClickListener(v -> {
songBottomSheetViewModel.setFavorite(requireContext());
dismissBottomSheet();
});
favoriteToggle.setOnLongClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, song);
RatingDialog dialog = new RatingDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
dismissBottomSheet();
return true;
});
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
playRadio.setOnClickListener(v -> {
MediaManager.startQueue(mediaBrowserListenableFuture, song);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
songBottomSheetViewModel.getInstantMix(getViewLifecycleOwner(), song).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
dismissBottomSheet();
return;
}
if (songs.size() > 0) {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
dismissBottomSheet();
}
});
});
TextView playNext = view.findViewById(R.id.play_next_text_view);
playNext.setOnClickListener(v -> {
MediaManager.enqueue(mediaBrowserListenableFuture, song, true);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView addToQueue = view.findViewById(R.id.add_to_queue_text_view);
addToQueue.setOnClickListener(v -> {
MediaManager.enqueue(mediaBrowserListenableFuture, song, false);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView rate = view.findViewById(R.id.rate_text_view);
rate.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, song);
RatingDialog dialog = new RatingDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
dismissBottomSheet();
});
TextView download = view.findViewById(R.id.download_text_view);
download.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownload(song),
new Download(song)
);
dismissBottomSheet();
});
TextView remove = view.findViewById(R.id.remove_text_view);
remove.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).remove(
MappingUtil.mapDownload(song),
new Download(song)
);
dismissBottomSheet();
});
initDownloadUI(download, remove);
TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
addToPlaylist.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, song);
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
dismissBottomSheet();
});
TextView goToAlbum = view.findViewById(R.id.go_to_album_text_view);
goToAlbum.setOnClickListener(v -> songBottomSheetViewModel.getAlbum().observe(getViewLifecycleOwner(), album -> {
if (album != null) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, album);
NavHostFragment.findNavController(this).navigate(R.id.albumPageFragment, bundle);
} else
Toast.makeText(requireContext(), getString(R.string.song_bottom_sheet_error_retrieving_album), Toast.LENGTH_SHORT).show();
dismissBottomSheet();
}));
TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view);
goToArtist.setOnClickListener(v -> songBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artist);
NavHostFragment.findNavController(this).navigate(R.id.artistPageFragment, bundle);
} else
Toast.makeText(requireContext(), getString(R.string.song_bottom_sheet_error_retrieving_artist), Toast.LENGTH_SHORT).show();
dismissBottomSheet();
}));
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initDownloadUI(TextView download, TextView remove) {
if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(song.getId())) {
download.setVisibility(View.GONE);
remove.setVisibility(View.VISIBLE);
} else {
download.setVisibility(View.VISIBLE);
remove.setVisibility(View.GONE);
}
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View file

@ -0,0 +1,48 @@
package com.cappielloantonio.tempo.ui.fragment.pager;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.Fragment;
import androidx.media3.common.util.UnstableApi;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import java.util.ArrayList;
import java.util.List;
@OptIn(markerClass = UnstableApi.class)
public class HomePager extends FragmentStateAdapter {
private static final String TAG = "HomePager";
private final List<Fragment> fragments = new ArrayList<>();
private final List<String> titles = new ArrayList<>();
private final List<Integer> icons = new ArrayList<>();
public HomePager(@NonNull Fragment fragment) {
super(fragment);
}
@NonNull
@Override
public Fragment createFragment(int position) {
return fragments.get(position);
}
@Override
public int getItemCount() {
return fragments.size();
}
public void addFragment(Fragment fragment, String title, int drawable) {
fragments.add(fragment);
titles.add(title);
icons.add(drawable);
}
public String getPageTitle(int position) {
return titles.get(position);
}
public Integer getPageIcon(int position) {
return icons.get(position);
}
}

View file

@ -0,0 +1,34 @@
package com.cappielloantonio.tempo.ui.fragment.pager;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.cappielloantonio.tempo.ui.fragment.PlayerCoverFragment;
import com.cappielloantonio.tempo.ui.fragment.PlayerLyricsFragment;
public class PlayerControllerHorizontalPager extends FragmentStateAdapter {
private static final String TAG = "PlayerControllerHorizontalPager";
public PlayerControllerHorizontalPager(@NonNull Fragment fragment) {
super(fragment);
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
return new PlayerCoverFragment();
case 1:
return new PlayerLyricsFragment();
}
return new PlayerCoverFragment();
}
@Override
public int getItemCount() {
return 2;
}
}

View file

@ -0,0 +1,48 @@
package com.cappielloantonio.tempo.ui.fragment.pager;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.cappielloantonio.tempo.ui.fragment.PlayerControllerFragment;
import com.cappielloantonio.tempo.ui.fragment.PlayerQueueFragment;
import java.util.HashMap;
public class PlayerControllerVerticalPager extends FragmentStateAdapter {
private final HashMap<Integer, Fragment> maps;
public PlayerControllerVerticalPager(@NonNull Fragment fragment) {
super(fragment);
this.maps = new HashMap<>();
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
Fragment playerControllerFragment = new PlayerControllerFragment();
maps.put(position, playerControllerFragment);
return playerControllerFragment;
case 1:
Fragment playerQueueFragment = new PlayerQueueFragment();
maps.put(position, playerQueueFragment);
return playerQueueFragment;
}
Fragment playerControllerFragment = new PlayerControllerFragment();
maps.put(position, playerControllerFragment);
return playerControllerFragment;
}
@Override
public int getItemCount() {
return 2;
}
public Fragment getRegisteredFragment(int position) {
return maps.get(position);
}
}