feat: add functionality to delete podcast episodes and channels via bottom sheet

This commit is contained in:
antonio 2023-06-04 19:38:24 +02:00
parent 675ad1e9a6
commit 0248187f41
13 changed files with 336 additions and 82 deletions

View file

@ -82,4 +82,38 @@ public class PodcastRepository {
}
});
}
public void deletePodcastChannel(String channelId) {
App.getSubsonicClientInstance(false)
.getPodcastClient()
.deletePodcastChannel(channelId)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
public void deletePodcastEpisode(String episodeId) {
App.getSubsonicClientInstance(false)
.getPodcastClient()
.deletePodcastEpisode(episodeId)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
}

View file

@ -43,4 +43,14 @@ public class PodcastClient {
Log.d(TAG, "refreshPodcasts()");
return podcastService.refreshPodcasts(subsonic.getParams());
}
public Call<ApiResponse> deletePodcastChannel(String channelId) {
Log.d(TAG, "deletePodcastChannel()");
return podcastService.deletePodcastChannel(subsonic.getParams(), channelId);
}
public Call<ApiResponse> deletePodcastEpisode(String episodeId) {
Log.d(TAG, "deletePodcastEpisode()");
return podcastService.deletePodcastEpisode(subsonic.getParams(), episodeId);
}
}

View file

@ -19,4 +19,10 @@ public interface PodcastService {
@GET("refreshPodcasts")
Call<ApiResponse> refreshPodcasts(@QueryMap Map<String, String> params);
@GET("deletePodcastChannel")
Call<ApiResponse> deletePodcastChannel(@QueryMap Map<String, String> params, @Query("id") String id);
@GET("deletePodcastEpisode")
Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
}

View file

@ -1,29 +1,20 @@
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.util.Log;
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;
@ -37,8 +28,6 @@ import com.cappielloantonio.play.util.Constants;
import com.cappielloantonio.play.util.Preferences;
import com.cappielloantonio.play.util.UIUtil;
import com.cappielloantonio.play.viewmodel.PodcastViewModel;
import com.google.android.material.divider.MaterialDividerItemDecoration;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Objects;
@ -109,8 +98,10 @@ public class HomeTabPodcastFragment extends Fragment implements ClickCallback {
if (bind != null) bind.homePodcastChannelsSector.setVisibility(View.GONE);
if (bind != null) bind.emptyPodcastLayout.setVisibility(View.GONE);
} else {
if (bind != null) bind.homePodcastChannelsSector.setVisibility(!podcastChannels.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.emptyPodcastLayout.setVisibility(podcastChannels.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.homePodcastChannelsSector.setVisibility(!podcastChannels.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.emptyPodcastLayout.setVisibility(podcastChannels.isEmpty() ? View.VISIBLE : View.GONE);
}
});
}
@ -166,7 +157,7 @@ public class HomeTabPodcastFragment extends Fragment implements ClickCallback {
@Override
public void onPodcastEpisodeLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastBottomSheetDialog, bundle);
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
}
@Override
@ -176,6 +167,6 @@ public class HomeTabPodcastFragment extends Fragment implements ClickCallback {
@Override
public void onPodcastChannelLongClick(Bundle bundle) {
Toast.makeText(requireContext(), "Long click!", Toast.LENGTH_SHORT).show();
Navigation.findNavController(requireView()).navigate(R.id.podcastChannelBottomSheetDialog, bundle);
}
}

View file

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

View file

@ -15,9 +15,6 @@ import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
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.glide.CustomGlideRequest;
import com.cappielloantonio.play.service.MediaService;
@ -25,26 +22,26 @@ import com.cappielloantonio.play.subsonic.models.PodcastEpisode;
import com.cappielloantonio.play.ui.activity.MainActivity;
import com.cappielloantonio.play.util.Constants;
import com.cappielloantonio.play.util.MusicUtil;
import com.cappielloantonio.play.viewmodel.PodcastBottomSheetViewModel;
import com.cappielloantonio.play.viewmodel.PodcastEpisodeBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class PodcastBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private PodcastBottomSheetViewModel podcastBottomSheetViewModel;
private PodcastEpisode podcast;
public class PodcastEpisodeBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private PodcastEpisodeBottomSheetViewModel podcastEpisodeBottomSheetViewModel;
private PodcastEpisode podcastEpisode;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_podcast_dialog, container, false);
View view = inflater.inflate(R.layout.bottom_sheet_podcast_episode_dialog, container, false);
podcast = requireArguments().getParcelable(Constants.PODCAST_OBJECT);
podcastEpisode = requireArguments().getParcelable(Constants.PODCAST_OBJECT);
podcastBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PodcastBottomSheetViewModel.class);
podcastBottomSheetViewModel.setPodcast(podcast);
podcastEpisodeBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PodcastEpisodeBottomSheetViewModel.class);
podcastEpisodeBottomSheetViewModel.setPodcastEpisode(podcastEpisode);
init(view);
@ -68,18 +65,15 @@ public class PodcastBottomSheetDialog extends BottomSheetDialogFragment implemen
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), podcastBottomSheetViewModel.getPodcast().getCoverArtId())
.from(requireContext(), podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getCoverArtId())
.build()
.into(coverPodcast);
TextView titlePodcast = view.findViewById(R.id.podcast_title_text_view);
titlePodcast.setText(MusicUtil.getReadableString(podcastBottomSheetViewModel.getPodcast().getTitle()));
titlePodcast.setText(MusicUtil.getReadableString(podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getTitle()));
titlePodcast.setSelected(true);
TextView channel = view.findViewById(R.id.podcast_artist_text_view);
channel.setText(MusicUtil.getReadableString(podcastBottomSheetViewModel.getPodcast().getArtist()));
TextView playNext = view.findViewById(R.id.play_next_text_view);
playNext.setOnClickListener(v -> {
// TODO
@ -118,6 +112,12 @@ public class PodcastBottomSheetDialog extends BottomSheetDialogFragment implemen
initDownloadUI(download, remove);
TextView delete = view.findViewById(R.id.delete_text_view);
delete.setOnClickListener(v -> {
podcastEpisodeBottomSheetViewModel.deletePodcastEpisode();
dismissBottomSheet();
});
TextView goToChannel = view.findViewById(R.id.go_to_channel_text_view);
goToChannel.setOnClickListener(v -> {
Toast.makeText(requireContext(), "Open the channel", Toast.LENGTH_SHORT).show();

View file

@ -1,29 +0,0 @@
package com.cappielloantonio.play.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import com.cappielloantonio.play.repository.PodcastRepository;
import com.cappielloantonio.play.subsonic.models.PodcastEpisode;
public class PodcastBottomSheetViewModel extends AndroidViewModel {
private final PodcastRepository podcastRepository;
private PodcastEpisode podcast;
public PodcastBottomSheetViewModel(@NonNull Application application) {
super(application);
podcastRepository = new PodcastRepository();
}
public PodcastEpisode getPodcast() {
return podcast;
}
public void setPodcast(PodcastEpisode podcast) {
this.podcast = podcast;
}
}

View file

@ -0,0 +1,34 @@
package com.cappielloantonio.play.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import com.cappielloantonio.play.repository.PodcastRepository;
import com.cappielloantonio.play.subsonic.models.PodcastChannel;
import com.cappielloantonio.play.subsonic.models.PodcastEpisode;
public class PodcastChannelBottomSheetViewModel extends AndroidViewModel {
private final PodcastRepository podcastRepository;
private PodcastChannel podcastChannel;
public PodcastChannelBottomSheetViewModel(@NonNull Application application) {
super(application);
podcastRepository = new PodcastRepository();
}
public PodcastChannel getPodcastChannel() {
return podcastChannel;
}
public void setPodcastChannel(PodcastChannel podcastChannel) {
this.podcastChannel = podcastChannel;
}
public void deletePodcastChannel() {
if (podcastChannel != null) podcastRepository.deletePodcastChannel(podcastChannel.getId());
}
}

View file

@ -0,0 +1,33 @@
package com.cappielloantonio.play.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import com.cappielloantonio.play.repository.PodcastRepository;
import com.cappielloantonio.play.subsonic.models.PodcastEpisode;
public class PodcastEpisodeBottomSheetViewModel extends AndroidViewModel {
private final PodcastRepository podcastRepository;
private PodcastEpisode podcastEpisode;
public PodcastEpisodeBottomSheetViewModel(@NonNull Application application) {
super(application);
podcastRepository = new PodcastRepository();
}
public PodcastEpisode getPodcastEpisode() {
return podcastEpisode;
}
public void setPodcastEpisode(PodcastEpisode podcast) {
this.podcastEpisode = podcast;
}
public void deletePodcastEpisode() {
if (podcastEpisode != null) podcastRepository.deletePodcastEpisode(podcastEpisode.getId());
}
}

View file

@ -0,0 +1,67 @@
<?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="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:clipChildren="false">
<!-- Header -->
<ImageView
android:id="@+id/podcast_cover_image_view"
android:layout_width="54dp"
android:layout_height="54dp"
android:layout_margin="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/podcast_title_text_view"
style="@style/LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:maxLines="3"
android:text="@string/label_placeholder"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/podcast_cover_image_view"
app:layout_constraintTop_toTopOf="@+id/podcast_cover_image_view"
app:layout_constraintBottom_toBottomOf="@+id/podcast_cover_image_view"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/option_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="12dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:id="@+id/delete_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/podcast_bottom_sheet_delete" />
</LinearLayout>
</LinearLayout>

View file

@ -28,27 +28,15 @@
style="@style/LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="marquee"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:singleLine="true"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="@+id/podcast_cover_image_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/podcast_cover_image_view"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/podcast_artist_text_view"
style="@style/LabelSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:text="@string/label_placeholder"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/podcast_cover_image_view"
app:layout_constraintTop_toBottomOf="@+id/podcast_title_text_view" />
app:layout_constraintTop_toTopOf="@+id/podcast_cover_image_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
@ -73,7 +61,8 @@
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/podcast_bottom_sheet_play_next" />
android:text="@string/podcast_bottom_sheet_play_next"
android:visibility="gone" />
<TextView
android:id="@+id/add_to_queue_text_view"
@ -86,7 +75,8 @@
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/podcast_bottom_sheet_add_to_queue" />
android:text="@string/podcast_bottom_sheet_add_to_queue"
android:visibility="gone" />
<TextView
android:id="@+id/download_text_view"
@ -99,7 +89,8 @@
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/podcast_bottom_sheet_download" />
android:text="@string/podcast_bottom_sheet_download"
android:visibility="gone" />
<TextView
android:id="@+id/remove_text_view"
@ -112,7 +103,21 @@
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/podcast_bottom_sheet_remove" />
android:text="@string/podcast_bottom_sheet_remove"
android:visibility="gone" />
<TextView
android:id="@+id/delete_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/podcast_bottom_sheet_delete" />
<TextView
android:id="@+id/go_to_channel_text_view"
@ -124,6 +129,7 @@
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:visibility="gone"
android:paddingBottom="12dp"
android:text="@string/podcast_bottom_sheet_go_to_channel" />

View file

@ -313,8 +313,13 @@
android:label="AlbumBottomSheetDialog"
tools:layout="@layout/bottom_sheet_album_dialog" />
<dialog
android:id="@+id/podcastBottomSheetDialog"
android:name="com.cappielloantonio.play.ui.fragment.bottomsheetdialog.PodcastBottomSheetDialog"
android:label="PodcastBottomSheetDialog"
tools:layout="@layout/bottom_sheet_podcast_dialog" />
android:id="@+id/podcastEpisodeBottomSheetDialog"
android:name="com.cappielloantonio.play.ui.fragment.bottomsheetdialog.PodcastEpisodeBottomSheetDialog"
android:label="PodcastEpisodeBottomSheetDialog"
tools:layout="@layout/bottom_sheet_podcast_episode_dialog" />
<dialog
android:id="@+id/podcastChannelBottomSheetDialog"
android:name="com.cappielloantonio.play.ui.fragment.bottomsheetdialog.PodcastChannelBottomSheetDialog"
android:label="PodcastChannelBottomSheetDialog"
tools:layout="@layout/bottom_sheet_podcast_channel_dialog" />
</navigation>

View file

@ -243,6 +243,7 @@
<string name="home_title_podcast_channels">Channels</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_delete">Delete</string>
<string name="podcast_bottom_sheet_remove">Remove</string>
<string name="podcast_bottom_sheet_download">Download</string>
<string name="podcast_bottom_sheet_add_to_queue">Add to queue</string>