mirror of
https://github.com/antebudimir/tempus.git
synced 2026-01-01 18:03:33 +00:00
feat: podcast
This commit is contained in:
parent
e3a28fa914
commit
e85d7f9198
23 changed files with 1390 additions and 131 deletions
|
|
@ -28,9 +28,13 @@ public interface ClickCallback {
|
|||
|
||||
default void onServerLongClick(Bundle bundle) {}
|
||||
|
||||
default void onPodcastClick(Bundle bundle) {}
|
||||
default void onPodcastEpisodeClick(Bundle bundle) {}
|
||||
|
||||
default void onPodcastLongClick(Bundle bundle) {}
|
||||
default void onPodcastEpisodeLongClick(Bundle bundle) {}
|
||||
|
||||
default void onPodcastChannelClick(Bundle bundle) {}
|
||||
|
||||
default void onPodcastChannelLongClick(Bundle bundle) {}
|
||||
|
||||
default void onInternetRadioStationClick(Bundle bundle) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.cappielloantonio.play.repository.QueueRepository;
|
|||
import com.cappielloantonio.play.repository.SongRepository;
|
||||
import com.cappielloantonio.play.subsonic.models.Child;
|
||||
import com.cappielloantonio.play.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.play.subsonic.models.PodcastEpisode;
|
||||
import com.cappielloantonio.play.util.MappingUtil;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
|
@ -146,6 +147,23 @@ public class MediaManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static void startPodcast(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, PodcastEpisode podcastEpisode) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().clearMediaItems();
|
||||
mediaBrowserListenableFuture.get().setMediaItem(MappingUtil.mapMediaItem(podcastEpisode));
|
||||
mediaBrowserListenableFuture.get().prepare();
|
||||
mediaBrowserListenableFuture.get().play();
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void enqueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, boolean playImmediatelyAfter) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.cappielloantonio.play.subsonic.models
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.*
|
||||
|
|
@ -29,11 +30,14 @@ class PodcastEpisode : Parcelable {
|
|||
var transcodedContentType: String? = null
|
||||
var transcodedSuffix: String? = null
|
||||
var duration: Int? = null
|
||||
var bitRate: Int? = null
|
||||
@ColumnInfo("bitrate")
|
||||
@SerializedName("bitRate")
|
||||
var bitrate: Int? = null
|
||||
var path: String? = null
|
||||
|
||||
@ColumnInfo(name = "is_video")
|
||||
@SerializedName("isVideo")
|
||||
var video: Boolean? = null
|
||||
var isVideo: Boolean = false
|
||||
var userRating: Int? = null
|
||||
var averageRating: Double? = null
|
||||
var playCount: Long? = null
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
package com.cappielloantonio.play.ui.adapter;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.cappielloantonio.play.databinding.ItemHomeCataloguePodcastChannelBinding;
|
||||
import com.cappielloantonio.play.databinding.ItemLibraryCatalogueAlbumBinding;
|
||||
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.play.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.play.subsonic.models.PodcastChannel;
|
||||
import com.cappielloantonio.play.util.Constants;
|
||||
import com.cappielloantonio.play.util.MusicUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<PodcastChannelCatalogueAdapter.ViewHolder> implements Filterable {
|
||||
private final ClickCallback click;
|
||||
private final Filter filtering = new Filter() {
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
List<PodcastChannel> filteredList = new ArrayList<>();
|
||||
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
filteredList.addAll(podcastChannelsFull);
|
||||
} else {
|
||||
String filterPattern = constraint.toString().toLowerCase().trim();
|
||||
|
||||
for (PodcastChannel item : podcastChannelsFull) {
|
||||
if (item.getTitle().toLowerCase().contains(filterPattern)) {
|
||||
filteredList.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FilterResults results = new FilterResults();
|
||||
results.values = filteredList;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
podcastChannels.clear();
|
||||
podcastChannels.addAll((List) results.values);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
|
||||
private List<PodcastChannel> podcastChannels;
|
||||
private List<PodcastChannel> podcastChannelsFull;
|
||||
|
||||
public PodcastChannelCatalogueAdapter(ClickCallback click) {
|
||||
this.click = click;
|
||||
this.podcastChannels = Collections.emptyList();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
ItemHomeCataloguePodcastChannelBinding view = ItemHomeCataloguePodcastChannelBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
PodcastChannel podcastChannel = podcastChannels.get(position);
|
||||
|
||||
holder.item.podcastChannelTitleLabel.setText(MusicUtil.getReadableString(podcastChannel.getTitle()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId())
|
||||
.build()
|
||||
.into(holder.item.podcastChannelCatalogueCoverImageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return podcastChannels.size();
|
||||
}
|
||||
|
||||
public PodcastChannel getItem(int position) {
|
||||
return podcastChannels.get(position);
|
||||
}
|
||||
|
||||
public void setItems(List<PodcastChannel> podcastChannels) {
|
||||
this.podcastChannels = podcastChannels;
|
||||
this.podcastChannelsFull = new ArrayList<>(podcastChannels);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return filtering;
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ItemHomeCataloguePodcastChannelBinding item;
|
||||
|
||||
ViewHolder(ItemHomeCataloguePodcastChannelBinding item) {
|
||||
super(item.getRoot());
|
||||
|
||||
this.item = item;
|
||||
|
||||
item.podcastChannelTitleLabel.setSelected(true);
|
||||
|
||||
itemView.setOnClickListener(v -> onClick());
|
||||
itemView.setOnLongClickListener(v -> onLongClick());
|
||||
}
|
||||
|
||||
private void onClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onAlbumClick(bundle);
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onAlbumLongClick(bundle);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package com.cappielloantonio.play.ui.adapter;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.cappielloantonio.play.databinding.ItemHorizontalPodcastChannelBinding;
|
||||
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.play.subsonic.models.PodcastChannel;
|
||||
import com.cappielloantonio.play.util.Constants;
|
||||
import com.cappielloantonio.play.util.MusicUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class PodcastChannelHorizontalAdapter extends RecyclerView.Adapter<PodcastChannelHorizontalAdapter.ViewHolder> {
|
||||
private final ClickCallback click;
|
||||
|
||||
private List<PodcastChannel> podcastChannels;
|
||||
|
||||
public PodcastChannelHorizontalAdapter(ClickCallback click) {
|
||||
this.click = click;
|
||||
this.podcastChannels = Collections.emptyList();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
ItemHorizontalPodcastChannelBinding view = ItemHorizontalPodcastChannelBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
PodcastChannel podcastChannel = podcastChannels.get(position);
|
||||
|
||||
holder.item.podcastChannelTitleTextView.setText(MusicUtil.getReadableString(podcastChannel.getTitle()));
|
||||
holder.item.podcastChannelDescriptionTextView.setText(MusicUtil.getReadableString(podcastChannel.getDescription()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), podcastChannel.getOriginalImageUrl())
|
||||
.build()
|
||||
.into(holder.item.podcastChannelCoverImageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return podcastChannels.size();
|
||||
}
|
||||
|
||||
public void setItems(List<PodcastChannel> podcastChannels) {
|
||||
this.podcastChannels = podcastChannels;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public PodcastChannel getItem(int id) {
|
||||
return podcastChannels.get(id);
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ItemHorizontalPodcastChannelBinding item;
|
||||
|
||||
ViewHolder(ItemHorizontalPodcastChannelBinding item) {
|
||||
super(item.getRoot());
|
||||
|
||||
this.item = item;
|
||||
|
||||
item.podcastChannelTitleTextView.setSelected(true);
|
||||
item.podcastChannelDescriptionTextView.setSelected(true);
|
||||
|
||||
itemView.setOnClickListener(v -> onClick());
|
||||
itemView.setOnLongClickListener(v -> onLongClick());
|
||||
|
||||
item.podcastChannelMoreButton.setOnClickListener(v -> onLongClick());
|
||||
}
|
||||
|
||||
private void onClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onPodcastChannelClick(bundle);
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onPodcastChannelLongClick(bundle);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,6 @@ import android.view.ViewGroup;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
||||
import com.cappielloantonio.play.R;
|
||||
import com.cappielloantonio.play.databinding.ItemHomePodcastEpisodeBinding;
|
||||
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
||||
|
|
@ -47,7 +44,7 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||
holder.item.podcastTitleLabel.setText(MusicUtil.getReadableString(podcastEpisode.getTitle()));
|
||||
holder.item.podcastSubtitleLabel.setText(MusicUtil.getReadableString(podcastEpisode.getArtist()));
|
||||
holder.item.podcastReleasesAndDurationLabel.setText(holder.itemView.getContext().getString(R.string.podcast_release_date_duration_formatter, simpleDateFormat.format(podcastEpisode.getPublishDate()), MusicUtil.getReadablePodcastDurationString(podcastEpisode.getDuration())));
|
||||
holder.item.podcastDescriptionLabel.setText(MusicUtil.getReadableString(podcastEpisode.getDescription()));
|
||||
holder.item.podcastDescriptionText.setText(MusicUtil.getReadableString(podcastEpisode.getDescription()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
||||
|
|
@ -74,22 +71,24 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||
this.item = item;
|
||||
|
||||
itemView.setOnClickListener(v -> onClick());
|
||||
itemView.setOnLongClickListener(v -> openMore());
|
||||
|
||||
item.podcastMoreButton.setOnLongClickListener(v -> openMore());
|
||||
item.podcastPlayButton.setOnClickListener(v -> onClick());
|
||||
item.podcastMoreButton.setOnClickListener(v -> openMore());
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onPodcastClick(bundle);
|
||||
click.onPodcastEpisodeClick(bundle);
|
||||
}
|
||||
|
||||
private boolean openMore() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onPodcastLongClick(bundle);
|
||||
click.onPodcastEpisodeLongClick(bundle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,52 @@
|
|||
package com.cappielloantonio.play.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
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.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.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.cappielloantonio.play.R;
|
||||
import com.cappielloantonio.play.databinding.FragmentHomeTabPodcastBinding;
|
||||
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.play.service.MediaManager;
|
||||
import com.cappielloantonio.play.service.MediaService;
|
||||
import com.cappielloantonio.play.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.play.viewmodel.HomeViewModel;
|
||||
import com.cappielloantonio.play.ui.adapter.PodcastChannelHorizontalAdapter;
|
||||
import com.cappielloantonio.play.ui.adapter.PodcastEpisodeAdapter;
|
||||
import com.cappielloantonio.play.util.Constants;
|
||||
import com.cappielloantonio.play.util.UIUtil;
|
||||
import com.cappielloantonio.play.viewmodel.PodcastViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
@UnstableApi
|
||||
public class HomeTabPodcastFragment extends Fragment {
|
||||
public class HomeTabPodcastFragment extends Fragment implements ClickCallback {
|
||||
private static final String TAG = "HomeTabPodcastFragment";
|
||||
|
||||
private FragmentHomeTabPodcastBinding bind;
|
||||
private MainActivity activity;
|
||||
private HomeViewModel homeViewModel;
|
||||
private PodcastViewModel podcastViewModel;
|
||||
|
||||
private PodcastEpisodeAdapter podcastEpisodeAdapter;
|
||||
private PodcastChannelHorizontalAdapter podcastChannelHorizontalAdapter;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
|
|
@ -37,11 +57,21 @@ public class HomeTabPodcastFragment extends Fragment {
|
|||
|
||||
bind = FragmentHomeTabPodcastBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
|
||||
podcastViewModel = new ViewModelProvider(requireActivity()).get(PodcastViewModel.class);
|
||||
|
||||
init();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
initNewestPodcastsView();
|
||||
initPodcastChannelsView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
|
@ -61,6 +91,47 @@ public class HomeTabPodcastFragment extends Fragment {
|
|||
bind = null;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
bind.podcastChannelsTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_homeFragment_to_podcastChannelCatalogueFragment));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||
}
|
||||
|
|
@ -68,4 +139,25 @@ public class HomeTabPodcastFragment extends Fragment {
|
|||
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.podcastBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPodcastChannelClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.podcastChannelPageFragment, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPodcastChannelLongClick(Bundle bundle) {
|
||||
Toast.makeText(requireContext(), "Long click!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
package com.cappielloantonio.play.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.play.R;
|
||||
import com.cappielloantonio.play.databinding.FragmentPodcastChannelCatalogueBinding;
|
||||
import com.cappielloantonio.play.helper.recyclerview.GridItemDecoration;
|
||||
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.play.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.play.ui.adapter.PodcastChannelCatalogueAdapter;
|
||||
import com.cappielloantonio.play.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
package com.cappielloantonio.play.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.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 androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.cappielloantonio.play.R;
|
||||
import com.cappielloantonio.play.databinding.FragmentPodcastChannelPageBinding;
|
||||
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.play.service.MediaManager;
|
||||
import com.cappielloantonio.play.service.MediaService;
|
||||
import com.cappielloantonio.play.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.play.ui.adapter.PodcastEpisodeAdapter;
|
||||
import com.cappielloantonio.play.util.Constants;
|
||||
import com.cappielloantonio.play.util.MusicUtil;
|
||||
import com.cappielloantonio.play.util.UIUtil;
|
||||
import com.cappielloantonio.play.viewmodel.PodcastChannelPageViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
@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.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.mostStreamedSongRecyclerView.addItemDecoration(UIUtil.getDividerItemDecoration(requireContext()));
|
||||
|
||||
podcastEpisodeAdapter = new PodcastEpisodeAdapter(this);
|
||||
bind.mostStreamedSongRecyclerView.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.get(0) != null && channels.get(0).getEpisodes() != null) {
|
||||
if (bind != null)
|
||||
bind.podcastChannelPageEpisodesSector.setVisibility(!channels.get(0).getEpisodes().isEmpty() ? View.VISIBLE : View.GONE);
|
||||
podcastEpisodeAdapter.setItems(channels.get(0).getEpisodes());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
Toast.makeText(requireContext(), "Long click!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ object Constants {
|
|||
const val GENRE_OBJECT = "GENRE_OBJECT"
|
||||
const val PLAYLIST_OBJECT = "PLAYLIST_OBJECT"
|
||||
const val PODCAST_OBJECT = "PODCAST_OBJECT"
|
||||
const val PODCAST_CHANNEL_OBJECT = "PODCAST_CHANNEL_OBJECT"
|
||||
const val INTERNET_RADIO_STATION_OBJECT = "INTERNET_RADIO_STATION_OBJECT"
|
||||
|
||||
const val ALBUM_RECENTLY_PLAYED = "ALBUM_RECENTLY_PLAYED"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.media3.common.util.UnstableApi;
|
|||
import com.cappielloantonio.play.App;
|
||||
import com.cappielloantonio.play.subsonic.models.Child;
|
||||
import com.cappielloantonio.play.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.play.subsonic.models.PodcastEpisode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
|
@ -152,9 +153,76 @@ public class MappingUtil {
|
|||
.build();
|
||||
}
|
||||
|
||||
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
|
||||
Uri uri = getUri(podcastEpisode);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("id", podcastEpisode.getId());
|
||||
bundle.putString("parentId", podcastEpisode.getParentId());
|
||||
bundle.putBoolean("isDir", podcastEpisode.isDir());
|
||||
bundle.putString("title", podcastEpisode.getTitle());
|
||||
bundle.putString("album", podcastEpisode.getAlbum());
|
||||
bundle.putString("artist", podcastEpisode.getArtist());
|
||||
bundle.putInt("track", podcastEpisode.getTrack() != null ? podcastEpisode.getTrack() : 0);
|
||||
bundle.putInt("year", podcastEpisode.getYear() != null ? podcastEpisode.getYear() : 0);
|
||||
bundle.putString("genre", podcastEpisode.getGenre());
|
||||
bundle.putString("coverArtId", podcastEpisode.getCoverArtId());
|
||||
bundle.putLong("size", podcastEpisode.getSize() != null ? podcastEpisode.getSize() : 0);
|
||||
bundle.putString("contentType", podcastEpisode.getContentType());
|
||||
bundle.putString("suffix", podcastEpisode.getSuffix());
|
||||
bundle.putString("transcodedContentType", podcastEpisode.getTranscodedContentType());
|
||||
bundle.putString("transcodedSuffix", podcastEpisode.getTranscodedSuffix());
|
||||
bundle.putInt("duration", podcastEpisode.getDuration() != null ? podcastEpisode.getDuration() : 0);
|
||||
bundle.putInt("bitrate", podcastEpisode.getBitrate() != null ? podcastEpisode.getBitrate() : 0);
|
||||
bundle.putString("path", podcastEpisode.getPath());
|
||||
bundle.putBoolean("isVideo", podcastEpisode.isVideo());
|
||||
bundle.putInt("userRating", podcastEpisode.getUserRating() != null ? podcastEpisode.getUserRating() : 0);
|
||||
bundle.putDouble("averageRating", podcastEpisode.getAverageRating() != null ? podcastEpisode.getAverageRating() : 0);
|
||||
bundle.putLong("playCount", podcastEpisode.getPlayCount() != null ? podcastEpisode.getTrack() : 0);
|
||||
bundle.putInt("discNumber", podcastEpisode.getDiscNumber() != null ? podcastEpisode.getTrack() : 0);
|
||||
bundle.putLong("created", podcastEpisode.getCreated() != null ? podcastEpisode.getCreated().getTime() : 0);
|
||||
bundle.putLong("starred", podcastEpisode.getStarred() != null ? podcastEpisode.getStarred().getTime() : 0);
|
||||
bundle.putString("albumId", podcastEpisode.getAlbumId());
|
||||
bundle.putString("artistId", podcastEpisode.getArtistId());
|
||||
bundle.putString("type", podcastEpisode.getType());
|
||||
bundle.putLong("bookmarkPosition", podcastEpisode.getBookmarkPosition() != null ? podcastEpisode.getBookmarkPosition() : 0);
|
||||
bundle.putInt("originalWidth", podcastEpisode.getOriginalWidth() != null ? podcastEpisode.getOriginalWidth() : 0);
|
||||
bundle.putInt("originalHeight", podcastEpisode.getOriginalHeight() != null ? podcastEpisode.getOriginalHeight() : 0);
|
||||
bundle.putString("uri", uri.toString());
|
||||
|
||||
return new MediaItem.Builder()
|
||||
.setMediaId(podcastEpisode.getId())
|
||||
.setMediaMetadata(
|
||||
new MediaMetadata.Builder()
|
||||
.setTitle(MusicUtil.getReadableString(podcastEpisode.getTitle()))
|
||||
.setTrackNumber(podcastEpisode.getTrack())
|
||||
.setDiscNumber(podcastEpisode.getDiscNumber())
|
||||
.setReleaseYear(podcastEpisode.getYear())
|
||||
.setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum()))
|
||||
.setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist()))
|
||||
.setExtras(bundle)
|
||||
.build()
|
||||
)
|
||||
.setRequestMetadata(
|
||||
new MediaItem.RequestMetadata.Builder()
|
||||
.setMediaUri(uri)
|
||||
.setExtras(bundle)
|
||||
.build()
|
||||
)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.setUri(uri)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Uri getUri(Child media) {
|
||||
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId())
|
||||
? MusicUtil.getDownloadUri(media.getId())
|
||||
: MusicUtil.getStreamUri(media.getId());
|
||||
}
|
||||
|
||||
private static Uri getUri(PodcastEpisode podcastEpisode) {
|
||||
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getId())
|
||||
? MusicUtil.getDownloadUri(podcastEpisode.getId())
|
||||
: MusicUtil.getStreamUri(podcastEpisode.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package com.cappielloantonio.play.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
|
|
@ -19,4 +24,18 @@ public class UIUtil {
|
|||
public static boolean isCastApiAvailable(Context context) {
|
||||
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
|
||||
}
|
||||
|
||||
public static DividerItemDecoration getDividerItemDecoration(Context context) {
|
||||
int[] ATTRS = new int[]{android.R.attr.listDivider};
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(ATTRS);
|
||||
Drawable divider = a.getDrawable(0);
|
||||
InsetDrawable insetDivider = new InsetDrawable(divider, 42, 0, 42, 42);
|
||||
a.recycle();
|
||||
|
||||
DividerItemDecoration itemDecoration = new DividerItemDecoration(context, DividerItemDecoration.VERTICAL);
|
||||
itemDecoration.setDrawable(insetDivider);
|
||||
|
||||
return itemDecoration;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package com.cappielloantonio.play.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.play.App;
|
||||
import com.cappielloantonio.play.interfaces.MediaCallback;
|
||||
import com.cappielloantonio.play.repository.PodcastRepository;
|
||||
import com.cappielloantonio.play.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.play.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.play.subsonic.models.PodcastChannel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
|
||||
public class PodcastChannelCatalogueViewModel extends AndroidViewModel {
|
||||
private final PodcastRepository podcastRepository;
|
||||
|
||||
private final MutableLiveData<List<PodcastChannel>> podcastChannels = new MutableLiveData<>(null);
|
||||
|
||||
|
||||
public PodcastChannelCatalogueViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
podcastRepository = new PodcastRepository();
|
||||
}
|
||||
|
||||
public LiveData<List<PodcastChannel>> getPodcastChannels(LifecycleOwner owner) {
|
||||
if (podcastChannels.getValue() == null) {
|
||||
podcastRepository.getPodcastChannels(false, null).observe(owner, podcastChannels::postValue);
|
||||
}
|
||||
|
||||
return podcastChannels;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.cappielloantonio.play.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.cappielloantonio.play.repository.AlbumRepository;
|
||||
import com.cappielloantonio.play.repository.ArtistRepository;
|
||||
import com.cappielloantonio.play.repository.PodcastRepository;
|
||||
import com.cappielloantonio.play.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.play.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.play.subsonic.models.ArtistInfo2;
|
||||
import com.cappielloantonio.play.subsonic.models.Child;
|
||||
import com.cappielloantonio.play.subsonic.models.PodcastChannel;
|
||||
import com.cappielloantonio.play.subsonic.models.PodcastEpisode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PodcastChannelPageViewModel extends AndroidViewModel {
|
||||
private final PodcastRepository podcastRepository;
|
||||
|
||||
private PodcastChannel podcastChannel;
|
||||
|
||||
public PodcastChannelPageViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
podcastRepository = new PodcastRepository();
|
||||
}
|
||||
|
||||
public LiveData<List<PodcastChannel>> getPodcastChannelEpisodes() {
|
||||
return podcastRepository.getPodcastChannels(true, podcastChannel.getId());
|
||||
}
|
||||
|
||||
public PodcastChannel getPodcastChannel() {
|
||||
return podcastChannel;
|
||||
}
|
||||
|
||||
public void setPodcastChannel(PodcastChannel podcastChannel) {
|
||||
this.podcastChannel = podcastChannel;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.cappielloantonio.play.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.play.repository.PodcastRepository;
|
||||
import com.cappielloantonio.play.subsonic.models.PodcastChannel;
|
||||
import com.cappielloantonio.play.subsonic.models.PodcastEpisode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PodcastViewModel extends AndroidViewModel {
|
||||
private final PodcastRepository podcastRepository;
|
||||
|
||||
private final MutableLiveData<List<PodcastEpisode>> newestPodcastEpisodes = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<List<PodcastChannel>> podcastChannels = new MutableLiveData<>(null);
|
||||
|
||||
public PodcastViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
podcastRepository = new PodcastRepository();
|
||||
}
|
||||
|
||||
public LiveData<List<PodcastEpisode>> getNewestPodcastEpisodes(LifecycleOwner owner) {
|
||||
if (newestPodcastEpisodes.getValue() == null) {
|
||||
podcastRepository.getNewestPodcastEpisodes(20).observe(owner, newestPodcastEpisodes::postValue);
|
||||
}
|
||||
|
||||
return newestPodcastEpisodes;
|
||||
}
|
||||
|
||||
public LiveData<List<PodcastChannel>> getPodcastChannels(LifecycleOwner owner) {
|
||||
if (podcastChannels.getValue() == null) {
|
||||
podcastRepository.getPodcastChannels(false, null).observe(owner, podcastChannels::postValue);
|
||||
}
|
||||
|
||||
return podcastChannels;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue