feat: add heart to artist/album pages, fixed artist cover art failing

This commit is contained in:
eddyizm 2025-12-11 22:07:44 -08:00
parent a110faabe3
commit fe60fea928
No known key found for this signature in database
GPG key ID: CF5F671829E8158A
6 changed files with 345 additions and 84 deletions

View file

@ -4,7 +4,7 @@ import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -12,6 +12,7 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast; import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -60,12 +61,14 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
private SongHorizontalAdapter songHorizontalAdapter; private SongHorizontalAdapter songHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture; private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
/** @noinspection deprecation*/
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
/** @noinspection deprecation*/
@Override @Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
@ -81,7 +84,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class); albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class);
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class); playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
init(); init(view);
initAppBar(); initAppBar();
initAlbumInfoTextButton(); initAlbumInfoTextButton();
initAlbumNotes(); initAlbumNotes();
@ -119,12 +122,13 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
bind = null; bind = null;
} }
/** @noinspection deprecation*/
@Override @Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) { public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_rate_album) { if (item.getItemId() == R.id.action_rate_album) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
AlbumID3 album = albumPageViewModel.getAlbum().getValue(); AlbumID3 album = albumPageViewModel.getAlbum().getValue();
bundle.putParcelable(Constants.ALBUM_OBJECT, (Parcelable) album); bundle.putParcelable(Constants.ALBUM_OBJECT, album);
RatingDialog dialog = new RatingDialog(); RatingDialog dialog = new RatingDialog();
dialog.setArguments(bundle); dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null); dialog.show(requireActivity().getSupportFragmentManager(), null);
@ -159,8 +163,21 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
return false; return false;
} }
private void init() { private void init(View view) {
albumPageViewModel.setAlbum(getViewLifecycleOwner(), requireArguments().getParcelable(Constants.ALBUM_OBJECT)); AlbumID3 albumArg = requireArguments().getParcelable(Constants.ALBUM_OBJECT);
assert albumArg != null;
albumPageViewModel.setAlbum(getViewLifecycleOwner(), albumArg);
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(albumArg.getStarred() != null);
favoriteToggle.setOnClickListener(v -> {
albumPageViewModel.setFavorite();
});
albumPageViewModel.getAlbum().observe(getViewLifecycleOwner(), album -> {
if (album != null) {
favoriteToggle.setChecked(album.getStarred() != null);
}
});
} }
private void initAppBar() { private void initAppBar() {

View file

@ -2,14 +2,18 @@ package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
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 android.widget.Toast;
import android.widget.ToggleButton;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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;
@ -40,6 +44,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
@UnstableApi @UnstableApi
public class ArtistPageFragment extends Fragment implements ClickCallback { public class ArtistPageFragment extends Fragment implements ClickCallback {
@ -63,7 +68,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class); artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class); playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
init(); init(view);
initAppBar(); initAppBar();
initArtistInfo(); initArtistInfo();
initPlayButtons(); initPlayButtons();
@ -100,7 +105,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bind = null; bind = null;
} }
private void init() { private void init(View view) {
artistPageViewModel.setArtist(requireArguments().getParcelable(Constants.ARTIST_OBJECT)); artistPageViewModel.setArtist(requireArguments().getParcelable(Constants.ARTIST_OBJECT));
bind.mostStreamedSongTextViewClickable.setOnClickListener(v -> { bind.mostStreamedSongTextViewClickable.setOnClickListener(v -> {
@ -109,6 +114,10 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bundle.putParcelable(Constants.ARTIST_OBJECT, artistPageViewModel.getArtist()); bundle.putParcelable(Constants.ARTIST_OBJECT, artistPageViewModel.getArtist());
activity.navController.navigate(R.id.action_artistPageFragment_to_songListPageFragment, bundle); activity.navController.navigate(R.id.action_artistPageFragment_to_songListPageFragment, bundle);
}); });
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(artistPageViewModel.getArtist().getStarred() != null);
favoriteToggle.setOnClickListener(v -> artistPageViewModel.setFavorite(requireContext()));
} }
private void initAppBar() { private void initAppBar() {
@ -133,10 +142,54 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
if (bind != null) if (bind != null)
bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE); bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE);
if (getContext() != null && bind != null) CustomGlideRequest.Builder if (getContext() != null && bind != null) {
.from(requireContext(), artistPageViewModel.getArtist().getId(), CustomGlideRequest.ResourceType.Artist) ArtistID3 currentArtist = artistPageViewModel.getArtist();
.build() String primaryId = currentArtist.getCoverArtId() != null && !currentArtist.getCoverArtId().trim().isEmpty()
.into(bind.artistBackdropImageView); ? currentArtist.getCoverArtId()
: currentArtist.getId();
final String fallbackId = (Objects.requireNonNull(primaryId).equals(currentArtist.getCoverArtId()) &&
currentArtist.getId() != null &&
!currentArtist.getId().equals(primaryId))
? currentArtist.getId()
: null;
CustomGlideRequest.Builder
.from(requireContext(), primaryId, CustomGlideRequest.ResourceType.Artist)
.build()
.listener(new com.bumptech.glide.request.RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable com.bumptech.glide.load.engine.GlideException e,
Object model,
@NonNull com.bumptech.glide.request.target.Target<Drawable> target,
boolean isFirstResource) {
if (e != null) {
e.getMessage();
if (e.getMessage().contains("400") && fallbackId != null) {
Log.d("ArtistCover", "Primary ID failed (400), trying fallback: " + fallbackId);
CustomGlideRequest.Builder
.from(requireContext(), fallbackId, CustomGlideRequest.ResourceType.Artist)
.build()
.into(bind.artistBackdropImageView);
return true;
}
}
return false;
}
@Override
public boolean onResourceReady(@NonNull Drawable resource,
@NonNull Object model,
com.bumptech.glide.request.target.Target<Drawable> target,
@NonNull com.bumptech.glide.load.DataSource dataSource,
boolean isFirstResource) {
return false;
}
})
.into(bind.artistBackdropImageView);
}
if (bind != null) bind.bioTextView.setText(normalizedBio); if (bind != null) bind.bioTextView.setText(normalizedBio);
@ -150,29 +203,24 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
} }
}); });
} }
private void initPlayButtons() { private void initPlayButtons() {
bind.artistPageShuffleButton.setOnClickListener(v -> { bind.artistPageShuffleButton.setOnClickListener(v -> artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> {
artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> { if (!songs.isEmpty()) {
if (!songs.isEmpty()) { MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0); activity.setBottomSheetInPeek(true);
activity.setBottomSheetInPeek(true); } else {
} else { Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show(); }
} }));
});
});
bind.artistPageRadioButton.setOnClickListener(v -> { bind.artistPageRadioButton.setOnClickListener(v -> artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> {
artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> { if (songs != null && !songs.isEmpty()) {
if (songs != null && !songs.isEmpty()) { MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0); activity.setBottomSheetInPeek(true);
activity.setBottomSheetInPeek(true); } else {
} else { Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_radio), Toast.LENGTH_SHORT).show();
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_radio), Toast.LENGTH_SHORT).show(); }
} }));
});
});
} }
private void initTopSongsView() { private void initTopSongsView() {

View file

@ -8,18 +8,23 @@ import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.interfaces.StarCallback;
import com.cappielloantonio.tempo.repository.AlbumRepository; import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository; import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.FavoriteRepository;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3; import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo; import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3; import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.NetworkUtil;
import java.util.Date;
import java.util.List; import java.util.List;
public class AlbumPageViewModel extends AndroidViewModel { public class AlbumPageViewModel extends AndroidViewModel {
private final AlbumRepository albumRepository; private final AlbumRepository albumRepository;
private final ArtistRepository artistRepository; private final ArtistRepository artistRepository;
private final FavoriteRepository favoriteRepository;
private String albumId; private String albumId;
private String artistId; private String artistId;
private final MutableLiveData<AlbumID3> album = new MutableLiveData<>(null); private final MutableLiveData<AlbumID3> album = new MutableLiveData<>(null);
@ -29,6 +34,7 @@ public class AlbumPageViewModel extends AndroidViewModel {
albumRepository = new AlbumRepository(); albumRepository = new AlbumRepository();
artistRepository = new ArtistRepository(); artistRepository = new ArtistRepository();
favoriteRepository = new FavoriteRepository();
} }
public LiveData<List<Child>> getAlbumSongLiveList() { public LiveData<List<Child>> getAlbumSongLiveList() {
@ -49,6 +55,61 @@ public class AlbumPageViewModel extends AndroidViewModel {
}); });
} }
public void setFavorite() {
AlbumID3 currentAlbum = album.getValue();
if (currentAlbum == null) return;
if (currentAlbum.getStarred() != null) {
if (NetworkUtil.isOffline()) {
removeFavoriteOffline(currentAlbum);
} else {
removeFavoriteOnline(currentAlbum);
}
} else {
if (NetworkUtil.isOffline()) {
setFavoriteOffline(currentAlbum);
} else {
setFavoriteOnline(currentAlbum);
}
}
}
private void removeFavoriteOffline(AlbumID3 album) {
favoriteRepository.starLater(null, album.getId(), null, false);
album.setStarred(null);
this.album.postValue(album);
}
private void removeFavoriteOnline(AlbumID3 album) {
favoriteRepository.unstar(null, album.getId(), null, new StarCallback() {
@Override
public void onError() {
favoriteRepository.starLater(null, album.getId(), null, false);
}
});
album.setStarred(null);
this.album.postValue(album);
}
private void setFavoriteOffline(AlbumID3 album) {
favoriteRepository.starLater(null, album.getId(), null, true);
album.setStarred(new Date());
this.album.postValue(album);
}
private void setFavoriteOnline(AlbumID3 album) {
favoriteRepository.star(null, album.getId(), null, new StarCallback() {
@Override
public void onError() {
favoriteRepository.starLater(null, album.getId(), null, true);
}
});
album.setStarred(new Date());
this.album.postValue(album);
}
public LiveData<ArtistID3> getArtist() { public LiveData<ArtistID3> getArtist() {
return artistRepository.getArtistInfo(artistId); return artistRepository.getArtistInfo(artistId);
} }

View file

@ -1,23 +1,37 @@
package com.cappielloantonio.tempo.viewmodel; package com.cappielloantonio.tempo.viewmodel;
import android.app.Application; import android.app.Application;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.interfaces.StarCallback;
import com.cappielloantonio.tempo.repository.AlbumRepository; import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository; import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.FavoriteRepository;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3; import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3; import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.ArtistInfo2; import com.cappielloantonio.tempo.subsonic.models.ArtistInfo2;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.NetworkUtil;
import com.cappielloantonio.tempo.util.Preferences;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public class ArtistPageViewModel extends AndroidViewModel { public class ArtistPageViewModel extends AndroidViewModel {
private final AlbumRepository albumRepository; private final AlbumRepository albumRepository;
private final ArtistRepository artistRepository; private final ArtistRepository artistRepository;
private final FavoriteRepository favoriteRepository;
private ArtistID3 artist; private ArtistID3 artist;
@ -26,6 +40,7 @@ public class ArtistPageViewModel extends AndroidViewModel {
albumRepository = new AlbumRepository(); albumRepository = new AlbumRepository();
artistRepository = new ArtistRepository(); artistRepository = new ArtistRepository();
favoriteRepository = new FavoriteRepository();
} }
public LiveData<List<AlbumID3>> getAlbumList() { public LiveData<List<AlbumID3>> getAlbumList() {
@ -55,4 +70,71 @@ public class ArtistPageViewModel extends AndroidViewModel {
public void setArtist(ArtistID3 artist) { public void setArtist(ArtistID3 artist) {
this.artist = artist; this.artist = artist;
} }
public void setFavorite(Context context) {
if (artist.getStarred() != null) {
if (NetworkUtil.isOffline()) {
removeFavoriteOffline();
} else {
removeFavoriteOnline();
}
} else {
if (NetworkUtil.isOffline()) {
setFavoriteOffline();
} else {
setFavoriteOnline(context);
}
}
}
private void removeFavoriteOffline() {
favoriteRepository.starLater(null, null, artist.getId(), false);
artist.setStarred(null);
}
private void removeFavoriteOnline() {
favoriteRepository.unstar(null, null, artist.getId(), new StarCallback() {
@Override
public void onError() {
favoriteRepository.starLater(null, null, artist.getId(), false);
}
});
artist.setStarred(null);
}
private void setFavoriteOffline() {
favoriteRepository.starLater(null, null, artist.getId(), true);
artist.setStarred(new Date());
}
private void setFavoriteOnline(Context context) {
favoriteRepository.star(null, null, artist.getId(), new StarCallback() {
@Override
public void onError() {
favoriteRepository.starLater(null, null, artist.getId(), true);
}
});
artist.setStarred(new Date());
if (Preferences.isStarredArtistsSyncEnabled()) {
artistRepository.getArtistAllSongs(artist.getId(), new ArtistRepository.ArtistSongsCallback() {
@OptIn(markerClass = UnstableApi.class)
@Override
public void onSongsCollected(List<Child> songs) {
if (songs != null && !songs.isEmpty()) {
DownloadUtil.getDownloadTracker(context).download(
MappingUtil.mapDownloads(songs),
songs.stream().map(Download::new).collect(Collectors.toList())
);
} else {
}
}
});
} else {
Log.d("ArtistSync", "Artist sync preference is disabled");
}
}
} }

View file

@ -174,7 +174,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_notes_textview" /> app:layout_constraintTop_toBottomOf="@+id/album_notes_textview" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<View <View
@ -188,43 +187,69 @@
app:layout_constraintTop_toBottomOf="@+id/album_detail_view" /> app:layout_constraintTop_toBottomOf="@+id/album_detail_view" />
<LinearLayout <LinearLayout
android:id="@+id/album_page_button_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingTop="4dp" android:paddingTop="4dp"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:gravity="center_vertical"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/upper_button_divider"> app:layout_constraintTop_toBottomOf="@+id/upper_button_divider">
<Button <LinearLayout
android:id="@+id/album_page_play_button" android:id="@+id/album_page_button_layout"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="4dp"
android:layout_weight="1" android:layout_weight="1"
android:padding="10dp" android:orientation="horizontal"
android:text="@string/album_page_play_button" android:gravity="center_vertical">
android:textAllCaps="false"
app:icon="@drawable/ic_play" <Button
app:iconGravity="textStart" android:id="@+id/album_page_play_button"
app:iconPadding="18dp" /> android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:padding="10dp"
android:text="@string/album_page_play_button"
android:textAllCaps="false"
app:icon="@drawable/ic_play"
app:iconGravity="textStart"
app:iconPadding="18dp" />
<Button
android:id="@+id/album_page_shuffle_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:padding="10dp"
android:text="@string/album_page_shuffle_button"
android:textAllCaps="false"
app:icon="@drawable/ic_shuffle"
app:iconGravity="textStart"
app:iconPadding="18dp" />
</LinearLayout>
<ToggleButton
android:id="@+id/button_favorite"
android:layout_width="34dp"
android:layout_height="34dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="0dp"
android:background="@drawable/button_favorite_selector"
android:checked="false"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:gravity="center"
android:text=""
android:textOff=""
android:textOn="" />
<Button
android:id="@+id/album_page_shuffle_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:padding="10dp"
android:text="@string/album_page_shuffle_button"
android:textAllCaps="false"
app:icon="@drawable/ic_shuffle"
app:iconGravity="textStart"
app:iconPadding="18dp" />
</LinearLayout> </LinearLayout>
<TextView <TextView
@ -239,7 +264,8 @@
android:visibility="gone" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_page_button_layout" /> app:layout_constraintTop_toBottomOf="@id/album_page_button_layout"
tools:ignore="NotSibling" />
<View <View
android:id="@+id/bottom_button_divider" android:id="@+id/bottom_button_divider"
@ -249,7 +275,7 @@
android:layout_marginBottom="18dp" android:layout_marginBottom="18dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_page_button_layout" /> app:layout_constraintTop_toBottomOf="@+id/album_bio_label" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View file

@ -63,40 +63,67 @@
android:layout_marginEnd="18dp" /> android:layout_marginEnd="18dp" />
<LinearLayout <LinearLayout
android:id="@+id/album_page_button_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingTop="4dp" android:paddingTop="4dp"
android:paddingBottom="4dp"> android:paddingBottom="4dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:gravity="center_vertical">
<Button
android:id="@+id/artist_page_shuffle_button" <LinearLayout
android:id="@+id/album_page_button_layout"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="4dp"
android:layout_weight="1" android:layout_weight="1"
android:padding="10dp" android:orientation="horizontal"
android:text="@string/artist_page_shuffle_button" android:gravity="center_vertical">
android:textAllCaps="false"
app:icon="@drawable/ic_shuffle" <Button
app:iconGravity="textStart" android:id="@+id/artist_page_shuffle_button"
app:iconPadding="18dp" /> android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:padding="10dp"
android:text="@string/artist_page_shuffle_button"
android:textAllCaps="false"
app:icon="@drawable/ic_shuffle"
app:iconGravity="textStart"
app:iconPadding="18dp" />
<Button
android:id="@+id/artist_page_radio_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:padding="10dp"
android:text="@string/artist_page_radio_button"
android:textAllCaps="false"
app:icon="@drawable/ic_feed"
app:iconGravity="textStart"
app:iconPadding="18dp" />
</LinearLayout>
<ToggleButton
android:id="@+id/button_favorite"
android:layout_width="34dp"
android:layout_height="34dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="0dp"
android:background="@drawable/button_favorite_selector"
android:checked="false"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:gravity="center"
android:text=""
android:textOff=""
android:textOn="" />
<Button
android:id="@+id/artist_page_radio_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:padding="10dp"
android:text="@string/artist_page_radio_button"
android:textAllCaps="false"
app:icon="@drawable/ic_feed"
app:iconGravity="textStart"
app:iconPadding="18dp" />
</LinearLayout> </LinearLayout>
<View <View