mirror of
https://github.com/antebudimir/tempus.git
synced 2026-01-02 02:13: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 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) {}
|
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.repository.SongRepository;
|
||||||
import com.cappielloantonio.play.subsonic.models.Child;
|
import com.cappielloantonio.play.subsonic.models.Child;
|
||||||
import com.cappielloantonio.play.subsonic.models.InternetRadioStation;
|
import com.cappielloantonio.play.subsonic.models.InternetRadioStation;
|
||||||
|
import com.cappielloantonio.play.subsonic.models.PodcastEpisode;
|
||||||
import com.cappielloantonio.play.util.MappingUtil;
|
import com.cappielloantonio.play.util.MappingUtil;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
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) {
|
public static void enqueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, boolean playImmediatelyAfter) {
|
||||||
if (mediaBrowserListenableFuture != null) {
|
if (mediaBrowserListenableFuture != null) {
|
||||||
mediaBrowserListenableFuture.addListener(() -> {
|
mediaBrowserListenableFuture.addListener(() -> {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.cappielloantonio.play.subsonic.models
|
package com.cappielloantonio.play.subsonic.models
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
@ -29,11 +30,14 @@ class PodcastEpisode : Parcelable {
|
||||||
var transcodedContentType: String? = null
|
var transcodedContentType: String? = null
|
||||||
var transcodedSuffix: String? = null
|
var transcodedSuffix: String? = null
|
||||||
var duration: Int? = null
|
var duration: Int? = null
|
||||||
var bitRate: Int? = null
|
@ColumnInfo("bitrate")
|
||||||
|
@SerializedName("bitRate")
|
||||||
|
var bitrate: Int? = null
|
||||||
var path: String? = null
|
var path: String? = null
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_video")
|
||||||
@SerializedName("isVideo")
|
@SerializedName("isVideo")
|
||||||
var video: Boolean? = null
|
var isVideo: Boolean = false
|
||||||
var userRating: Int? = null
|
var userRating: Int? = null
|
||||||
var averageRating: Double? = null
|
var averageRating: Double? = null
|
||||||
var playCount: Long? = 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.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
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.R;
|
||||||
import com.cappielloantonio.play.databinding.ItemHomePodcastEpisodeBinding;
|
import com.cappielloantonio.play.databinding.ItemHomePodcastEpisodeBinding;
|
||||||
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
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.podcastTitleLabel.setText(MusicUtil.getReadableString(podcastEpisode.getTitle()));
|
||||||
holder.item.podcastSubtitleLabel.setText(MusicUtil.getReadableString(podcastEpisode.getArtist()));
|
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.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
|
CustomGlideRequest.Builder
|
||||||
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
||||||
|
|
@ -74,22 +71,24 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
||||||
this.item = item;
|
this.item = item;
|
||||||
|
|
||||||
itemView.setOnClickListener(v -> onClick());
|
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() {
|
public void onClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onPodcastClick(bundle);
|
click.onPodcastEpisodeClick(bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean openMore() {
|
private boolean openMore() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onPodcastLongClick(bundle);
|
click.onPodcastEpisodeLongClick(bundle);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,52 @@
|
||||||
package com.cappielloantonio.play.ui.fragment;
|
package com.cappielloantonio.play.ui.fragment;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
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.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.session.MediaBrowser;
|
import androidx.media3.session.MediaBrowser;
|
||||||
import androidx.media3.session.SessionToken;
|
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.databinding.FragmentHomeTabPodcastBinding;
|
||||||
|
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||||
|
import com.cappielloantonio.play.service.MediaManager;
|
||||||
import com.cappielloantonio.play.service.MediaService;
|
import com.cappielloantonio.play.service.MediaService;
|
||||||
import com.cappielloantonio.play.ui.activity.MainActivity;
|
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;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class HomeTabPodcastFragment extends Fragment {
|
public class HomeTabPodcastFragment extends Fragment implements ClickCallback {
|
||||||
private static final String TAG = "HomeTabPodcastFragment";
|
private static final String TAG = "HomeTabPodcastFragment";
|
||||||
|
|
||||||
private FragmentHomeTabPodcastBinding bind;
|
private FragmentHomeTabPodcastBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private HomeViewModel homeViewModel;
|
private PodcastViewModel podcastViewModel;
|
||||||
|
|
||||||
|
private PodcastEpisodeAdapter podcastEpisodeAdapter;
|
||||||
|
private PodcastChannelHorizontalAdapter podcastChannelHorizontalAdapter;
|
||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
|
|
@ -37,11 +57,21 @@ public class HomeTabPodcastFragment extends Fragment {
|
||||||
|
|
||||||
bind = FragmentHomeTabPodcastBinding.inflate(inflater, container, false);
|
bind = FragmentHomeTabPodcastBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
|
podcastViewModel = new ViewModelProvider(requireActivity()).get(PodcastViewModel.class);
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
initNewestPodcastsView();
|
||||||
|
initPodcastChannelsView();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
@ -61,6 +91,47 @@ public class HomeTabPodcastFragment extends Fragment {
|
||||||
bind = null;
|
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() {
|
private void initializeMediaBrowser() {
|
||||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
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() {
|
private void releaseMediaBrowser() {
|
||||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
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 GENRE_OBJECT = "GENRE_OBJECT"
|
||||||
const val PLAYLIST_OBJECT = "PLAYLIST_OBJECT"
|
const val PLAYLIST_OBJECT = "PLAYLIST_OBJECT"
|
||||||
const val PODCAST_OBJECT = "PODCAST_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 INTERNET_RADIO_STATION_OBJECT = "INTERNET_RADIO_STATION_OBJECT"
|
||||||
|
|
||||||
const val ALBUM_RECENTLY_PLAYED = "ALBUM_RECENTLY_PLAYED"
|
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.App;
|
||||||
import com.cappielloantonio.play.subsonic.models.Child;
|
import com.cappielloantonio.play.subsonic.models.Child;
|
||||||
import com.cappielloantonio.play.subsonic.models.InternetRadioStation;
|
import com.cappielloantonio.play.subsonic.models.InternetRadioStation;
|
||||||
|
import com.cappielloantonio.play.subsonic.models.PodcastEpisode;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -152,9 +153,76 @@ public class MappingUtil {
|
||||||
.build();
|
.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) {
|
private static Uri getUri(Child media) {
|
||||||
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId())
|
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId())
|
||||||
? MusicUtil.getDownloadUri(media.getId())
|
? MusicUtil.getDownloadUri(media.getId())
|
||||||
: MusicUtil.getStreamUri(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;
|
package com.cappielloantonio.play.util;
|
||||||
|
|
||||||
import android.content.Context;
|
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.ConnectionResult;
|
||||||
import com.google.android.gms.common.GoogleApiAvailability;
|
import com.google.android.gms.common.GoogleApiAvailability;
|
||||||
|
|
@ -19,4 +24,18 @@ public class UIUtil {
|
||||||
public static boolean isCastApiAvailable(Context context) {
|
public static boolean isCastApiAvailable(Context context) {
|
||||||
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,94 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.core.widget.NestedScrollView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="@dimen/global_padding_bottom">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_podcast_channels_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<!-- Label and button -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TitleLarge"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/home_title_podcast_channels" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/podcast_channels_text_view_clickable"
|
||||||
|
style="@style/TitleMedium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/home_title_podcast_channels_see_all_button" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/podcast_channels_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/upper_button_divider"
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_marginHorizontal="16dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/home_newest_podcasts_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TitleLarge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/home_title_newest_podcasts" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/newest_podcasts_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingTop="8dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/podcast_episodes_placeholder"
|
||||||
|
layout="@layout/item_placeholder_horizontal"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:layout_collapseMode="pin"
|
||||||
|
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingTop="8dp">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/app_bar_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/podcast_channel_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="172dp"
|
||||||
|
android:background="?attr/colorSurface"
|
||||||
|
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TitleLarge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="24dp"
|
||||||
|
android:text="@string/podcast_channel_catalogue_title_expanded"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/podcast_channel_catalogue_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="@dimen/global_padding_bottom"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
127
app/src/main/res/layout/fragment_podcast_channel_page.xml
Normal file
127
app/src/main/res/layout/fragment_podcast_channel_page.xml
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/appbar_header_height">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
|
android:id="@+id/collapsing_toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:expandedTitleMarginStart="@dimen/activity_margin_content"
|
||||||
|
app:contentScrim="?attr/colorSurface"
|
||||||
|
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/podcast_channel_backdrop_image_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_collapseMode="parallax" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/appbar_header_height"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:background="@drawable/gradient_backdrop_background_image" />
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/anim_toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:layout_collapseMode="pin" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/fragment_artist_page_nested_scroll_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="18dp"
|
||||||
|
android:paddingBottom="@dimen/global_padding_bottom">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/podcast_channel_page_bio_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="22dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TitleLarge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/podcast_channel_page_title_description_section" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/podcast_channel_description_text_view"
|
||||||
|
style="@style/TitleMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingEnd="16dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/podcast_channel_page_description_placeholder"
|
||||||
|
layout="@layout/item_placehoder_biography"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/upper_button_divider"
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_marginHorizontal="16dp" />
|
||||||
|
|
||||||
|
<!-- Label and button -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/podcast_channel_page_episodes_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="22dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TitleLarge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/podcast_channel_page_title_episode_section" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/most_streamed_song_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingTop="8dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/podcast_channel_page_episodes_placeholder"
|
||||||
|
layout="@layout/item_placeholder_horizontal"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/podcast_channel_catalogue_cover_image_view"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintDimensionRatio="W, 1:1"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/podcast_channel_title_label"
|
||||||
|
style="@style/LabelMedium"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/podcast_channel_catalogue_cover_image_view" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -1,121 +1,108 @@
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
style="@style/Widget.Material3.CardView.Outlined"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:clipChildren="false"
|
||||||
android:layout_marginTop="8dp"
|
android:orientation="horizontal"
|
||||||
android:layout_marginEnd="16dp"
|
android:paddingHorizontal="16dp">
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
app:cardCornerRadius="4dp">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<ImageView
|
||||||
|
android:id="@+id/podcast_cover_image_view"
|
||||||
|
android:layout_width="96dp"
|
||||||
|
android:layout_height="96dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/podcast_upper_divider"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/podcast_title_label"
|
||||||
|
style="@style/LabelMedium"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="5"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/podcast_subtitle_label"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/podcast_cover_image_view"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/podcast_subtitle_label"
|
||||||
|
style="@style/LabelSmall"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/podcast_upper_divider"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/podcast_cover_image_view"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/podcast_title_label" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/podcast_upper_divider"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="0.5dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/podcast_cover_image_view" />
|
||||||
|
|
||||||
<ImageView
|
<TextView
|
||||||
android:id="@+id/podcast_cover_image_view"
|
android:id="@+id/podcast_description_text"
|
||||||
android:layout_width="96dp"
|
style="@style/LabelSmall"
|
||||||
android:layout_height="96dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="12dp"
|
android:ellipsize="end"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/podcast_upper_divider"
|
android:maxLines="7"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:text="@string/label_placeholder"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/podcast_upper_divider" />
|
||||||
|
|
||||||
<TextView
|
<Button
|
||||||
android:id="@+id/podcast_title_label"
|
android:id="@+id/podcast_play_button"
|
||||||
style="@style/LabelLarge"
|
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
||||||
android:layout_width="0dp"
|
android:layout_width="52dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="52dp"
|
||||||
android:ellipsize="end"
|
android:layout_marginTop="12dp"
|
||||||
android:maxLines="2"
|
android:layout_marginBottom="12dp"
|
||||||
android:paddingStart="12dp"
|
android:insetLeft="0dp"
|
||||||
android:paddingEnd="16dp"
|
android:insetTop="0dp"
|
||||||
android:text="@string/label_placeholder"
|
android:insetRight="0dp"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/podcast_subtitle_label"
|
android:insetBottom="0dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:cornerRadius="30dp"
|
||||||
app:layout_constraintStart_toEndOf="@+id/podcast_cover_image_view"
|
app:icon="@drawable/ic_play"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintVertical_chainStyle="packed" />
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/podcast_description_text" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/podcast_releases_and_duration_label"
|
||||||
|
style="@style/LabelSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/podcast_play_button"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/podcast_play_button"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/podcast_play_button" />
|
||||||
|
|
||||||
<TextView
|
<Button
|
||||||
android:id="@+id/podcast_subtitle_label"
|
android:id="@+id/podcast_more_button"
|
||||||
style="@style/LabelMedium"
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
app:icon="@drawable/ic_more_vert"
|
||||||
android:maxLines="2"
|
app:layout_constraintBottom_toBottomOf="@+id/podcast_play_button"
|
||||||
android:paddingStart="12dp"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:paddingEnd="16dp"
|
app:layout_constraintTop_toTopOf="@+id/podcast_play_button" />
|
||||||
android:text="@string/label_placeholder"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/podcast_upper_divider"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/podcast_cover_image_view"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/podcast_title_label" />
|
|
||||||
|
|
||||||
<View
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
android:id="@+id/podcast_upper_divider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0.5dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/podcast_cover_image_view"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/podcast_description_label"
|
|
||||||
style="@style/LabelSmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="2"
|
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingEnd="12dp"
|
|
||||||
android:text="@string/label_placeholder"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/podcast_upper_divider" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/podcast_play_button"
|
|
||||||
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
|
||||||
android:layout_width="52dp"
|
|
||||||
android:layout_height="52dp"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
app:icon="@drawable/ic_play"
|
|
||||||
android:insetLeft="0dp"
|
|
||||||
android:insetTop="0dp"
|
|
||||||
android:insetRight="0dp"
|
|
||||||
android:insetBottom="0dp"
|
|
||||||
app:cornerRadius="30dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/podcast_description_label"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/podcast_releases_and_duration_label"
|
|
||||||
style="@style/LabelSmall"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingEnd="12dp"
|
|
||||||
android:text="@string/label_placeholder"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/podcast_play_button"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/podcast_play_button"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/podcast_play_button" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/podcast_more_button"
|
|
||||||
style="@style/Widget.Material3.Button.IconButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:icon="@drawable/ic_more_vert"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/podcast_play_button"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/podcast_play_button" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
|
||||||
64
app/src/main/res/layout/item_horizontal_podcast_channel.xml
Normal file
64
app/src/main/res/layout/item_horizontal_podcast_channel.xml
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="3dp"
|
||||||
|
android:paddingBottom="3dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/podcast_channel_cover_image_view"
|
||||||
|
android:layout_width="52dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="2dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/podcast_channel_title_text_view"
|
||||||
|
style="@style/LabelMedium"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/podcast_channel_more_button"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/podcast_channel_cover_image_view"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/podcast_channel_description_text_view"
|
||||||
|
style="@style/LabelSmall"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/podcast_channel_more_button"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/podcast_channel_cover_image_view"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/podcast_channel_title_text_view" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/podcast_channel_more_button"
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:background="@drawable/ic_more_vert"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -65,6 +65,9 @@
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_homeFragment_to_playlistPageFragment"
|
android:id="@+id/action_homeFragment_to_playlistPageFragment"
|
||||||
app:destination="@id/playlistPageFragment" />
|
app:destination="@id/playlistPageFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_homeFragment_to_podcastChannelCatalogueFragment"
|
||||||
|
app:destination="@id/podcastChannelCatalogueFragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/libraryFragment"
|
android:id="@+id/libraryFragment"
|
||||||
|
|
@ -127,7 +130,7 @@
|
||||||
app:destination="@id/settingsFragment" />
|
app:destination="@id/settingsFragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_downloadFragment_to_searchFragment"
|
android:id="@+id/action_downloadFragment_to_searchFragment"
|
||||||
app:destination="@id/searchFragment"/>
|
app:destination="@id/searchFragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_downloadFragment_to_playlistPageFragment"
|
android:id="@+id/action_downloadFragment_to_playlistPageFragment"
|
||||||
app:destination="@id/playlistPageFragment" />
|
app:destination="@id/playlistPageFragment" />
|
||||||
|
|
@ -262,6 +265,20 @@
|
||||||
android:name="com.cappielloantonio.play.ui.fragment.PlaylistPageFragment"
|
android:name="com.cappielloantonio.play.ui.fragment.PlaylistPageFragment"
|
||||||
android:label="PlaylistPageFragment"
|
android:label="PlaylistPageFragment"
|
||||||
tools:layout="@layout/fragment_playlist_page" />
|
tools:layout="@layout/fragment_playlist_page" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/podcastChannelCatalogueFragment"
|
||||||
|
android:name="com.cappielloantonio.play.ui.fragment.PodcastChannelCatalogueFragment"
|
||||||
|
android:label="PodcastChannelCatalogueFragment"
|
||||||
|
tools:layout="@layout/fragment_podcast_channel_catalogue">
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_podcastChannelCatalogueFragment_to_podcastChannelPageFragment"
|
||||||
|
app:destination="@id/podcastChannelPageFragment" />
|
||||||
|
</fragment>
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/podcastChannelPageFragment"
|
||||||
|
android:name="com.cappielloantonio.play.ui.fragment.PodcastChannelPageFragment"
|
||||||
|
android:label="PodcastChannelPageFragment"
|
||||||
|
tools:layout="@layout/fragment_podcast_channel_page" />
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/songBottomSheetDialog"
|
android:id="@+id/songBottomSheetDialog"
|
||||||
android:name="com.cappielloantonio.play.ui.fragment.bottomsheetdialog.SongBottomSheetDialog"
|
android:name="com.cappielloantonio.play.ui.fragment.bottomsheetdialog.SongBottomSheetDialog"
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@
|
||||||
<string name="home_title_starred_tracks">★ Starred tracks</string>
|
<string name="home_title_starred_tracks">★ Starred tracks</string>
|
||||||
<string name="home_title_starred_tracks_see_all_button">See all</string>
|
<string name="home_title_starred_tracks_see_all_button">See all</string>
|
||||||
<string name="home_title_internet_radio_station">Internet radio stations</string>
|
<string name="home_title_internet_radio_station">Internet radio stations</string>
|
||||||
|
<string name="home_title_podcast_channels_see_all_button">See all</string>
|
||||||
<string name="label_dot_separator">•</string>
|
<string name="label_dot_separator">•</string>
|
||||||
<string name="label_placeholder">--</string>
|
<string name="label_placeholder">--</string>
|
||||||
<string name="library_title_album">Albums</string>
|
<string name="library_title_album">Albums</string>
|
||||||
|
|
@ -118,6 +119,10 @@
|
||||||
<string name="playlist_page_play_button">Play</string>
|
<string name="playlist_page_play_button">Play</string>
|
||||||
<string name="playlist_page_shuffle_button">Shuffle</string>
|
<string name="playlist_page_shuffle_button">Shuffle</string>
|
||||||
<string name="playlist_song_count">Playlist • %1$d songs</string>
|
<string name="playlist_song_count">Playlist • %1$d songs</string>
|
||||||
|
<string name="podcast_channel_catalogue_title_expanded">Browse Channels</string>
|
||||||
|
<string name="podcast_channel_catalogue_title">Channels</string>
|
||||||
|
<string name="podcast_channel_page_title_description_section">Description</string>
|
||||||
|
<string name="podcast_channel_page_title_episode_section">Episodes</string>
|
||||||
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
||||||
<string name="radio_editor_dialog_hint_name">Radio Name</string>
|
<string name="radio_editor_dialog_hint_name">Radio Name</string>
|
||||||
<string name="radio_editor_dialog_hint_stream_url">Radio Stream URL</string>
|
<string name="radio_editor_dialog_hint_stream_url">Radio Stream URL</string>
|
||||||
|
|
@ -219,6 +224,7 @@
|
||||||
<string name="home_title_best_of">Best of</string>
|
<string name="home_title_best_of">Best of</string>
|
||||||
<string name="home_subtitle_best_of">Top songs of your favorite artists</string>
|
<string name="home_subtitle_best_of">Top songs of your favorite artists</string>
|
||||||
<string name="home_title_newest_podcasts">Newest podcasts</string>
|
<string name="home_title_newest_podcasts">Newest podcasts</string>
|
||||||
|
<string name="home_title_podcast_channels">Channels</string>
|
||||||
<string name="artist_adapter_radio_station_starting">Searching…</string>
|
<string name="artist_adapter_radio_station_starting">Searching…</string>
|
||||||
<string name="podcast_bottom_sheet_go_to_channel">Go to channel</string>
|
<string name="podcast_bottom_sheet_go_to_channel">Go to channel</string>
|
||||||
<string name="podcast_bottom_sheet_remove">Remove</string>
|
<string name="podcast_bottom_sheet_remove">Remove</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue