Merge branch 'main' into main

This commit is contained in:
GallowsDove 2023-09-07 10:39:03 +02:00 committed by GitHub
commit 223954e9ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 900 additions and 114 deletions

View file

@ -28,8 +28,8 @@ android {
tempo { tempo {
dimension "default" dimension "default"
applicationId 'com.cappielloantonio.tempo' applicationId 'com.cappielloantonio.tempo'
versionCode 19 versionCode 20
versionName '3.5.4' versionName '3.5.5'
} }
notquitemy { notquitemy {

View file

@ -204,6 +204,22 @@ public class MediaManager {
} }
} }
public static void shuffle(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex, int endIndex) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
try {
if (mediaBrowserListenableFuture.isDone()) {
mediaBrowserListenableFuture.get().removeMediaItems(startIndex, endIndex + 1);
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(media).subList(startIndex, endIndex + 1));
swapDatabase(media);
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
}
public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int from, int to) { public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int from, int to) {
if (mediaBrowserListenableFuture != null) { if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> { mediaBrowserListenableFuture.addListener(() -> {

View file

@ -0,0 +1,132 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.media3.common.MediaMetadata;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogTrackInfoBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
public class TrackInfoDialog extends DialogFragment {
private static final String TAG = "TrackInfoDialog";
private DialogTrackInfoBinding bind;
private MediaMetadata mediaMetadata;
public TrackInfoDialog(MediaMetadata mediaMetadata) {
this.mediaMetadata = mediaMetadata;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogTrackInfoBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setPositiveButton(R.string.track_info_dialog_positive_button, (dialog, id) -> dialog.cancel());
return builder.create();
}
@Override
public void onStart() {
super.onStart();
setTrackInfo();
setTrackTranscodingInfo();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setTrackInfo() {
bind.trakTitleInfoTextView.setText(mediaMetadata.title);
bind.trakArtistInfoTextView.setText(mediaMetadata.artist);
if (mediaMetadata.extras != null) {
CustomGlideRequest.Builder
.from(requireContext(), mediaMetadata.extras.getString("coverArtId", ""))
.build()
.into(bind.trackCoverInfoImageView);
bind.titleValueSector.setText(mediaMetadata.extras.getString("title", getString(R.string.label_placeholder)));
bind.albumValueSector.setText(mediaMetadata.extras.getString("album", getString(R.string.label_placeholder)));
bind.artistValueSector.setText(mediaMetadata.extras.getString("artist", getString(R.string.label_placeholder)));
bind.trackNumberValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("track", 0)));
bind.yearValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("year", 0)));
bind.genreValueSector.setText(mediaMetadata.extras.getString("genre", getString(R.string.label_placeholder)));
bind.sizeValueSector.setText(MusicUtil.getReadableByteCount(mediaMetadata.extras.getLong("size", 0)));
bind.contentTypeValueSector.setText(mediaMetadata.extras.getString("contentType", getString(R.string.label_placeholder)));
bind.suffixValueSector.setText(mediaMetadata.extras.getString("suffix", getString(R.string.label_placeholder)));
bind.transcodedContentTypeValueSector.setText(mediaMetadata.extras.getString("transcodedContentType", getString(R.string.label_placeholder)));
bind.transcodedSuffixValueSector.setText(mediaMetadata.extras.getString("transcodedSuffix", getString(R.string.label_placeholder)));
bind.durationValueSector.setText(MusicUtil.getReadableDurationString(mediaMetadata.extras.getInt("duration", 0), false));
bind.bitrateValueSector.setText(mediaMetadata.extras.getInt("bitrate", 0) + " kbps");
bind.pathValueSector.setText(mediaMetadata.extras.getString("path", getString(R.string.label_placeholder)));
bind.discNumberValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("discNumber", 0)));
}
}
private void setTrackTranscodingInfo() {
StringBuilder info = new StringBuilder();
boolean prioritizeServerTranscoding = Preferences.isServerPrioritized();
String transcodingExtension = MusicUtil.getTranscodingFormatPreference();
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0 ? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps" : "Original";
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
info.append(getString(R.string.track_info_summary_downloaded_file));
bind.trakTranscodingInfoTextView.setText(info);
return;
}
if (prioritizeServerTranscoding) {
info.append(getString(R.string.track_info_summary_server_prioritized));
bind.trakTranscodingInfoTextView.setText(info);
return;
}
if (!prioritizeServerTranscoding && transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
info.append(getString(R.string.track_info_summary_original_file));
bind.trakTranscodingInfoTextView.setText(info);
return;
}
if (!prioritizeServerTranscoding && !transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
info.append(getString(R.string.track_info_summary_transcoding_codec, transcodingExtension));
bind.trakTranscodingInfoTextView.setText(info);
return;
}
if (!prioritizeServerTranscoding && transcodingExtension.equals("raw") && !transcodingBitrate.equals("Original")) {
info.append(getString(R.string.track_info_summary_transcoding_bitrate, transcodingBitrate));
bind.trakTranscodingInfoTextView.setText(info);
return;
}
if (!prioritizeServerTranscoding && !transcodingExtension.equals("raw") && !transcodingBitrate.equals("Original")) {
info.append(getString(R.string.track_info_summary_full_transcode, transcodingExtension, transcodingBitrate));
bind.trakTranscodingInfoTextView.setText(info);
}
}
}

View file

@ -7,6 +7,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@ -37,6 +38,8 @@ import java.util.Objects;
@UnstableApi @UnstableApi
public class DownloadFragment extends Fragment implements ClickCallback { public class DownloadFragment extends Fragment implements ClickCallback {
private static final String TAG = "DownloadFragment";
private FragmentDownloadBinding bind; private FragmentDownloadBinding bind;
private MainActivity activity; private MainActivity activity;
private DownloadViewModel downloadViewModel; private DownloadViewModel downloadViewModel;
@ -160,6 +163,23 @@ public class DownloadFragment extends Fragment implements ClickCallback {
} }
bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE); bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE);
setupBackPressing(stack.size());
});
}
private void setupBackPressing(int stackSize) {
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (stackSize > 1) {
downloadViewModel.popViewStack();
} else {
activity.navController.navigateUp();
}
remove();
}
}); });
} }

View file

@ -240,6 +240,10 @@ public class PlayerBottomSheetFragment extends Fragment {
} }
} }
public void goToQueuePage() {
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setCurrentItem(1, true);
}
private void defineProgressBarHandler(MediaBrowser mediaBrowser) { private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
progressBarHandler = new Handler(); progressBarHandler = new Handler();
progressBarRunnable = () -> { progressBarRunnable = () -> {

View file

@ -6,12 +6,12 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton; import android.widget.ToggleButton;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaMetadata; import androidx.media3.common.MediaMetadata;
@ -29,12 +29,14 @@ import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerControllerBindi
import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.RatingDialog; import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
import com.cappielloantonio.tempo.ui.dialog.TrackInfoDialog;
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager; import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel; import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.android.material.chip.Chip; import com.google.android.material.chip.Chip;
import com.google.android.material.elevation.SurfaceColors;
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;
@ -51,10 +53,8 @@ public class PlayerControllerFragment extends Fragment {
private ToggleButton skipSilenceToggleButton; private ToggleButton skipSilenceToggleButton;
private Chip playerMediaExtension; private Chip playerMediaExtension;
private TextView playerMediaBitrate; private TextView playerMediaBitrate;
private ImageView playerMediaTranscodingIcon; private ConstraintLayout playerQuickActionView;
private ImageView playerMediaTranscodingPriorityIcon; private ImageButton playerTrackInfo;
private Chip playerMediaTranscodedExtension;
private TextView playerMediaTranscodedBitrate;
private MainActivity activity; private MainActivity activity;
private PlayerBottomSheetViewModel playerBottomSheetViewModel; private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@ -70,10 +70,10 @@ public class PlayerControllerFragment extends Fragment {
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class); playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
init(); init();
initQuickActionView();
initCoverLyricsSlideView(); initCoverLyricsSlideView();
initMediaListenable(); initMediaListenable();
initArtistLabelButton(); initArtistLabelButton();
initTranscodingInfo();
return view; return view;
} }
@ -106,10 +106,19 @@ public class PlayerControllerFragment extends Fragment {
skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button); skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button);
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension); playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate); playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio); playerQuickActionView = bind.getRoot().findViewById(R.id.player_quick_action_view);
playerMediaTranscodingPriorityIcon = bind.getRoot().findViewById(R.id.player_media_server_transcode_priority); playerTrackInfo = bind.getRoot().findViewById(R.id.player_info_track);
playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension); }
playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate);
private void initQuickActionView() {
playerQuickActionView.setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 8));
playerQuickActionView.setOnClickListener(view -> {
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
if (playerBottomSheetFragment != null) {
playerBottomSheetFragment.goToQueuePage();
}
});
} }
private void initializeBrowser() { private void initializeBrowser() {
@ -160,9 +169,7 @@ public class PlayerControllerFragment extends Fragment {
private void setMediaInfo(MediaMetadata mediaMetadata) { private void setMediaInfo(MediaMetadata mediaMetadata) {
if (mediaMetadata.extras != null) { if (mediaMetadata.extras != null) {
String extension = mediaMetadata.extras.getString("suffix", "Unknown format"); String extension = mediaMetadata.extras.getString("suffix", "Unknown format");
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 ? mediaMetadata.extras.getInt("bitrate", 0) + "kbps" : "Original";
? mediaMetadata.extras.getInt("bitrate", 0) + "kbps"
: "Original";
playerMediaExtension.setText(extension); playerMediaExtension.setText(extension);
@ -174,38 +181,10 @@ public class PlayerControllerFragment extends Fragment {
} }
} }
String transcodingExtension = MusicUtil.getTranscodingFormatPreference(); playerTrackInfo.setOnClickListener(view -> {
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0 TrackInfoDialog dialog = new TrackInfoDialog(mediaMetadata);
? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps" dialog.show(activity.getSupportFragmentManager(), null);
: "Original"; });
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
playerMediaTranscodingIcon.setVisibility(View.GONE);
playerMediaTranscodedBitrate.setVisibility(View.GONE);
playerMediaTranscodedExtension.setVisibility(View.GONE);
} else {
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
playerMediaTranscodingIcon.setVisibility(View.VISIBLE);
playerMediaTranscodedBitrate.setVisibility(View.VISIBLE);
playerMediaTranscodedExtension.setVisibility(View.VISIBLE);
playerMediaTranscodedExtension.setText(transcodingExtension);
playerMediaTranscodedBitrate.setText(transcodingBitrate);
}
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
playerMediaTranscodingIcon.setVisibility(View.GONE);
playerMediaTranscodedBitrate.setVisibility(View.GONE);
playerMediaTranscodedExtension.setVisibility(View.GONE);
}
if (Preferences.isServerPrioritized() && mediaMetadata.extras != null && !mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
playerMediaTranscodingPriorityIcon.setVisibility(View.VISIBLE);
playerMediaTranscodingIcon.setVisibility(View.GONE);
playerMediaTranscodedBitrate.setVisibility(View.GONE);
playerMediaTranscodedExtension.setVisibility(View.GONE);
}
} }
private void setMediaControllerUI(MediaBrowser mediaBrowser) { private void setMediaControllerUI(MediaBrowser mediaBrowser) {
@ -305,13 +284,6 @@ public class PlayerControllerFragment extends Fragment {
}); });
} }
private void initTranscodingInfo() {
playerMediaTranscodingPriorityIcon.setOnLongClickListener(view -> {
Toast.makeText(requireActivity(), R.string.settings_audio_transcode_priority_toast, Toast.LENGTH_SHORT).show();
return true;
});
}
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) { private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
playbackSpeedButton.setOnClickListener(view -> { playbackSpeedButton.setOnClickListener(view -> {
float currentSpeed = Preferences.getPlaybackSpeed(); float currentSpeed = Preferences.getPlaybackSpeed();

View file

@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName; import android.content.ComponentName;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.transition.Fade; import android.transition.Fade;
import android.transition.Transition; import android.transition.Transition;
import android.transition.TransitionManager; import android.transition.TransitionManager;
@ -39,6 +40,8 @@ public class PlayerCoverFragment extends Fragment {
private InnerFragmentPlayerCoverBinding bind; private InnerFragmentPlayerCoverBinding bind;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture; private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private final Handler handler = new Handler();
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false); bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false);
@ -72,9 +75,19 @@ public class PlayerCoverFragment extends Fragment {
bind = null; bind = null;
} }
private void initTapButtonHideTransition() {
bind.nowPlayingTapButton.setVisibility(View.VISIBLE);
handler.removeCallbacksAndMessages(null);
final Runnable runnable = () -> bind.nowPlayingTapButton.setVisibility(View.GONE);
handler.postDelayed(runnable, 10000);
}
private void initOverlay() { private void initOverlay() {
bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true)); bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true));
bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false)); bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false));
bind.nowPlayingTapButton.setOnClickListener(view -> toggleOverlayVisibility(true));
} }
private void toggleOverlayVisibility(boolean isVisible) { private void toggleOverlayVisibility(boolean isVisible) {
@ -84,9 +97,12 @@ public class PlayerCoverFragment extends Fragment {
TransitionManager.beginDelayedTransition(bind.getRoot(), transition); TransitionManager.beginDelayedTransition(bind.getRoot(), transition);
bind.nowPlayingSongCoverButtonGroup.setVisibility(isVisible ? View.VISIBLE : View.GONE); bind.nowPlayingSongCoverButtonGroup.setVisibility(isVisible ? View.VISIBLE : View.GONE);
bind.nowPlayingTapButton.setVisibility(isVisible ? View.GONE : View.VISIBLE);
bind.innerButtonBottomRight.setVisibility(Preferences.isSyncronizationEnabled() ? View.VISIBLE : View.GONE); bind.innerButtonBottomRight.setVisibility(Preferences.isSyncronizationEnabled() ? View.VISIBLE : View.GONE);
bind.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE); bind.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE);
if (!isVisible) initTapButtonHideTransition();
} }
private void initInnerButton() { private void initInnerButton() {

View file

@ -25,12 +25,16 @@ import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel; import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@UnstableApi @UnstableApi
public class PlayerQueueFragment extends Fragment implements ClickCallback { public class PlayerQueueFragment extends Fragment implements ClickCallback {
private static final String TAG = "PlayerQueueFragment";
private InnerFragmentPlayerQueueBinding bind; private InnerFragmentPlayerQueueBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel; private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@ -54,6 +58,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
initializeBrowser(); initializeBrowser();
bindMediaController();
} }
@Override @Override
@ -83,6 +88,17 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture); MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
} }
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
initShuffleButton(mediaBrowser);
} catch (Exception exception) {
exception.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void setMediaBrowserListenableFuture() { private void setMediaBrowserListenableFuture() {
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
} }
@ -151,6 +167,36 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
}).attachToRecyclerView(bind.playerQueueRecyclerView); }).attachToRecyclerView(bind.playerQueueRecyclerView);
} }
private void initShuffleButton(MediaBrowser mediaBrowser) {
bind.playerShuffleQueueFab.setOnClickListener(view -> {
int startPosition = mediaBrowser.getCurrentMediaItemIndex() + 1;
int endPosition = playerSongQueueAdapter.getItems().size() - 1;
if (startPosition < endPosition) {
ArrayList<Integer> pool = new ArrayList<>();
for (int i = startPosition; i <= endPosition; i++) {
pool.add(i);
}
while (pool.size() >= 2) {
int fromPosition = (int) (Math.random() * (pool.size()));
int positionA = pool.get(fromPosition);
pool.remove(fromPosition);
int toPosition = (int) (Math.random() * (pool.size()));
int positionB = pool.get(toPosition);
pool.remove(toPosition);
Collections.swap(playerSongQueueAdapter.getItems(), positionA, positionB);
bind.playerQueueRecyclerView.getAdapter().notifyItemMoved(positionA, positionB);
}
MediaManager.shuffle(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), startPosition, endPosition);
}
});
}
private void updateNowPlayingItem() { private void updateNowPlayingItem() {
playerSongQueueAdapter.notifyDataSetChanged(); playerSongQueueAdapter.notifyDataSetChanged();
} }

View file

@ -147,8 +147,6 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
})); }));
TextView downloadAll = view.findViewById(R.id.download_all_text_view); TextView downloadAll = view.findViewById(R.id.download_all_text_view);
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> { albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs); List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList()); List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
@ -157,17 +155,21 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
DownloadUtil.getDownloadTracker(requireContext()).download(mediaItems, downloads); DownloadUtil.getDownloadTracker(requireContext()).download(mediaItems, downloads);
dismissBottomSheet(); dismissBottomSheet();
}); });
if (DownloadUtil.getDownloadTracker(requireContext()).areDownloaded(mediaItems)) {
removeAll.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems, downloads);
dismissBottomSheet();
});
} else {
removeAll.setVisibility(View.GONE);
}
}); });
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
removeAll.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems, downloads);
dismissBottomSheet();
});
});
initDownloadUI(removeAll);
TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view); TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view);
goToArtist.setOnClickListener(v -> albumBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> { goToArtist.setOnClickListener(v -> albumBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) { if (artist != null) {
@ -191,6 +193,16 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
dismiss(); dismiss();
} }
private void initDownloadUI(TextView removeAll) {
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
if (DownloadUtil.getDownloadTracker(requireContext()).areDownloaded(mediaItems)) {
removeAll.setVisibility(View.VISIBLE);
}
});
}
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();
} }

View file

@ -14,6 +14,8 @@ import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.DownloadRepository; import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -211,6 +213,27 @@ public class MusicUtil {
return readableStrings; return readableStrings;
} }
public static String getReadableByteCount(long bytes) {
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
if (absB < 1024) {
return bytes + " B";
}
long value = absB;
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
value >>= 10;
ci.next();
}
value *= Long.signum(bytes);
return String.format("%.1f %ciB", value / 1024.0, ci.current());
}
public static String passwordHexEncoding(String plainPassword) { public static String passwordHexEncoding(String plainPassword) {
return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining()); return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining());
} }

View file

@ -1,6 +1,7 @@
package com.cappielloantonio.tempo.viewmodel; package com.cappielloantonio.tempo.viewmodel;
import android.app.Application; import android.app.Application;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.AndroidViewModel;
@ -11,7 +12,6 @@ import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.model.DownloadStack; import com.cappielloantonio.tempo.model.DownloadStack;
import com.cappielloantonio.tempo.repository.DownloadRepository; import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.util.Preferences;
import java.util.ArrayList; import java.util.ArrayList;

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M440,680L520,680L520,440L440,440L440,680ZM480,360Q497,360 508.5,348.5Q520,337 520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320Q440,337 451.5,348.5Q463,360 480,360ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:autoMirrored="true">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M624,720Q574,720 539,685Q504,650 504,600Q504,550 539,515Q574,480 624,480Q636,480 648.5,482.5Q661,485 672,490L672,192L864,192L864,264L744,264L744,600Q744,650 709,685Q674,720 624,720ZM144,552L144,480L432,480L432,552L144,552ZM144,408L144,336L576,336L576,408L144,408ZM144,264L144,192L576,192L576,264L144,264Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880ZM167,340Q154,318 147,292.5Q140,267 140,240Q140,157 198.5,98.5Q257,40 340,40Q423,40 481.5,98.5Q540,157 540,240Q540,267 533,292.5Q526,318 513,340L444,300Q452,286 456,271.5Q460,257 460,240Q460,190 425,155Q390,120 340,120Q290,120 255,155Q220,190 220,240Q220,257 224,271.5Q228,286 236,300L167,340ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620L502,620Z"/>
</vector>

View file

@ -157,6 +157,7 @@
android:paddingTop="12dp" android:paddingTop="12dp"
android:paddingEnd="20dp" android:paddingEnd="20dp"
android:paddingBottom="12dp" android:paddingBottom="12dp"
android:visibility="gone"
android:text="@string/album_bottom_sheet_remove_all" /> android:text="@string/album_bottom_sheet_remove_all" />
<TextView <TextView

View file

@ -0,0 +1,447 @@
<androidx.core.widget.NestedScrollView 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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp">
<ImageView
android:id="@+id/track_cover_info_image_view"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="center"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/trak_title_info_text_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/trak_title_info_text_view"
style="@style/LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toTopOf="@+id/trak_artist_info_text_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/track_cover_info_image_view"
app:layout_constraintTop_toTopOf="@+id/track_cover_info_image_view"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/trak_artist_info_text_view"
style="@style/LabelSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="@+id/track_cover_info_image_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/trak_title_info_text_view"
app:layout_constraintTop_toBottomOf="@+id/trak_title_info_text_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/trak_transcoding_info_text_view"
style="@style/TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp" />
<LinearLayout
android:id="@+id/title_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<TextView
android:id="@+id/title_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_title" />
<TextView
android:id="@+id/title_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/album_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/album_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_album" />
<TextView
android:id="@+id/album_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/artist_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/artist_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_artist" />
<TextView
android:id="@+id/artist_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/track_number_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/track_number_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_track_number" />
<TextView
android:id="@+id/track_number_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/year_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/year_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_year" />
<TextView
android:id="@+id/year_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/genre_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/genre_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_genre" />
<TextView
android:id="@+id/genre_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/size_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/size_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_size" />
<TextView
android:id="@+id/size_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/content_type_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/content_type_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_content_type" />
<TextView
android:id="@+id/content_type_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/suffix_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/suffix_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_suffix" />
<TextView
android:id="@+id/suffix_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/transcoded_content_type_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/transcoded_content_type_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_transcoded_content_type" />
<TextView
android:id="@+id/transcoded_content_type_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/transcoded_suffix_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/transcoded_suffix_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_transcoded_suffix" />
<TextView
android:id="@+id/transcoded_suffix_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/duration_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/duration_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_duration" />
<TextView
android:id="@+id/duration_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/bitrate_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/bitrate_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_bitrate" />
<TextView
android:id="@+id/bitrate_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/path_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/path_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_path" />
<TextView
android:id="@+id/path_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/disc_number_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/disc_number_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_disc_number" />
<TextView
android:id="@+id/disc_number_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -5,7 +5,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_media_quality_sector" android:id="@+id/player_media_quality_sector"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -24,48 +24,38 @@
android:checked="true" android:checked="true"
android:clickable="false" android:clickable="false"
android:text="Unknown" android:text="Unknown"
app:chipStrokeWidth="0dp" /> app:chipStrokeWidth="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/player_media_bitrate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"/>
<TextView <TextView
android:id="@+id/player_media_bitrate" android:id="@+id/player_media_bitrate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:visibility="gone" /> app:layout_constraintTop_toTopOf="@id/player_media_extension"
app:layout_constraintBottom_toBottomOf="@id/player_media_extension"
app:layout_constraintStart_toEndOf="@id/player_media_extension"
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView <ImageButton
android:id="@+id/player_media_transcoding_audio" android:id="@+id/player_info_track"
android:layout_width="24dp"
android:layout_height="20dp"
android:layout_margin="12dp"
android:src="@drawable/ic_transcode"
android:visibility="gone" />
<ImageView
android:id="@+id/player_media_server_transcode_priority"
android:layout_width="24dp"
android:layout_height="20dp"
android:src="@drawable/ic_server_transcode_priority"
android:visibility="gone" />
<com.google.android.material.chip.Chip
android:id="@+id/player_media_transcoded_extension"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="true" android:padding="16dp"
android:clickable="false" android:layout_marginEnd="8dp"
android:text="Unknown" android:background="?attr/selectableItemBackgroundBorderless"
android:visibility="gone" android:scaleType="fitCenter"
app:chipStrokeWidth="0dp" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/player_media_extension"
app:layout_constraintBottom_toBottomOf="@id/player_media_extension"
app:srcCompat="@drawable/ic_info_stream"
app:tint="?attr/colorOnPrimaryContainer" />
<TextView </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/player_media_transcoded_bitrate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:visibility="gone" />
</LinearLayout>
<androidx.viewpager2.widget.ViewPager2 <androidx.viewpager2.widget.ViewPager2
android:id="@+id/player_media_cover_view_pager" android:id="@+id/player_media_cover_view_pager"
@ -83,7 +73,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintGuide_percent="0.60" /> app:layout_constraintGuide_percent="0.575" />
<TextView <TextView
android:id="@+id/player_media_title_label" android:id="@+id/player_media_title_label"
@ -194,11 +184,11 @@
android:layout_width="70dp" android:layout_width="70dp"
android:layout_height="70dp" android:layout_height="70dp"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toTopOf="@+id/player_quick_action_view"
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/exo_progress" app:layout_constraintTop_toBottomOf="@+id/exo_progress"
app:layout_constraintVertical_bias=".60" /> app:layout_constraintVertical_bias=".45" />
<View <View
android:id="@+id/placeholder_view_middle_right" android:id="@+id/placeholder_view_middle_right"
@ -282,11 +272,11 @@
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="80dp" android:layout_height="80dp"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toTopOf="@+id/player_quick_action_view"
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/exo_progress" app:layout_constraintTop_toBottomOf="@+id/exo_progress"
app:layout_constraintVertical_bias=".60" app:layout_constraintVertical_bias=".45"
app:tint="?attr/colorOnPrimaryContainer" /> app:tint="?attr/colorOnPrimaryContainer" />
<ImageButton <ImageButton
@ -343,4 +333,25 @@
app:layout_constraintStart_toEndOf="@+id/placeholder_view_middle_right" app:layout_constraintStart_toEndOf="@+id/placeholder_view_middle_right"
app:layout_constraintTop_toTopOf="@+id/placeholder_view_middle_right" app:layout_constraintTop_toTopOf="@+id/placeholder_view_middle_right"
app:tint="?attr/colorOnPrimaryContainer" /> app:tint="?attr/colorOnPrimaryContainer" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_quick_action_view"
android:layout_width="match_parent"
android:layout_height="@dimen/now_playing_bottom_peek_height"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageButton
android:id="@+id/player_open_queue_button"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="?attr/selectableItemBackgroundBorderless"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:srcCompat="@drawable/ic_queue" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -15,6 +15,22 @@
app:layout_constraintDimensionRatio="1:1" app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/now_playing_tap_button"
style="@style/Widget.Material3.Button.TonalButton.Icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="16dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
app:cornerRadius="64dp"
android:alpha="0.7"
app:icon="@drawable/ic_tap"
app:layout_constraintEnd_toEndOf="@+id/now_playing_song_cover_image_view"
app:layout_constraintBottom_toBottomOf="@+id/now_playing_song_cover_image_view" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/now_playing_song_cover_button_group" android:id="@+id/now_playing_song_cover_button_group"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -1,14 +1,32 @@
<com.cappielloantonio.tempo.helper.recyclerview.NestedScrollableHost xmlns:android="http://schemas.android.com/apk/res/android" <?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:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <com.cappielloantonio.tempo.helper.recyclerview.NestedScrollableHost
android:id="@+id/player_queue_recycler_view" android:id="@+id/player_queue_nested_scrollable_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:clipToPadding="false"
android:orientation="vertical" <androidx.recyclerview.widget.RecyclerView
android:paddingTop="8dp" android:id="@+id/player_queue_recycler_view"
android:paddingBottom="@dimen/global_padding_bottom" /> android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="@dimen/global_padding_bottom" />
</com.cappielloantonio.tempo.helper.recyclerview.NestedScrollableHost>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/player_shuffle_queue_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
app:srcCompat="@drawable/ic_shuffle"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</com.cappielloantonio.tempo.helper.recyclerview.NestedScrollableHost>

View file

@ -3,6 +3,7 @@
<dimen name="activity_margin_content">24dp</dimen> <dimen name="activity_margin_content">24dp</dimen>
<dimen name="bottom_sheet_behavior_peek_height">136dp</dimen> <dimen name="bottom_sheet_behavior_peek_height">136dp</dimen>
<dimen name="bottom_sheet_peek_height">56dp</dimen> <dimen name="bottom_sheet_peek_height">56dp</dimen>
<dimen name="now_playing_bottom_peek_height">64dp</dimen>
<dimen name="global_padding_bottom">164dp</dimen> <dimen name="global_padding_bottom">164dp</dimen>
<dimen name="radius">2dp</dimen> <dimen name="radius">2dp</dimen>
<dimen name="dots_height">2dp</dimen> <dimen name="dots_height">2dp</dimen>

View file

@ -304,4 +304,27 @@
<string name="downloaded_bottom_sheet_play_next">Play next</string> <string name="downloaded_bottom_sheet_play_next">Play next</string>
<string name="downloaded_bottom_sheet_add_to_queue">Add to queue</string> <string name="downloaded_bottom_sheet_add_to_queue">Add to queue</string>
<string name="downloaded_bottom_sheet_remove_all">Remove all</string> <string name="downloaded_bottom_sheet_remove_all">Remove all</string>
<string name="track_info_dialog_positive_button">OK</string>
<string name="track_info_dialog_title">Track info</string>
<string name="track_info_title">Title</string>
<string name="track_info_album">Album</string>
<string name="track_info_artist">Artist</string>
<string name="track_info_track_number">Track number</string>
<string name="track_info_year">Year</string>
<string name="track_info_genre">Genre</string>
<string name="track_info_size">Size</string>
<string name="track_info_content_type">Content Type</string>
<string name="track_info_suffix">Suffix</string>
<string name="track_info_transcoded_content_type">Transcoded content type</string>
<string name="track_info_transcoded_suffix">Transcoded suffix</string>
<string name="track_info_duration">Duration</string>
<string name="track_info_bitrate">Bitrate</string>
<string name="track_info_path">Path</string>
<string name="track_info_disc_number">Disc number</string>
<string name="track_info_summary_downloaded_file">The file has been downloaded using the Subsonic APIs. The codec and bitrate of the file remain unchanged from the source file.</string>
<string name="track_info_summary_server_prioritized">The quality of the file to be played is left up to the server\'s decision. The app will not enforce the choice of codec and bitrate for any potential transcoding.</string>
<string name="track_info_summary_original_file">The application will only read the original file as provided by the server. The app will explicitly request the server for the untranscoded file with the bitrate of the original source.</string>
<string name="track_info_summary_transcoding_codec">The application will request the server to transcode the file. The requested codec by the user is %1$s, while the bitrate will be the same as the source file. The potential transcoding of the file into the chosen format is dependent on the server, as it may or may not support the operation.</string>
<string name="track_info_summary_transcoding_bitrate">The application will request the server to modify the bitrate of the file. The user requested a bitrate of %1$s, while the codec of the source file will remain the same. Any changes to the bitrate of the file in the chosen format will be done by the server, which may or may not support the operation.</string>
<string name="track_info_summary_full_transcode">The application will request the server to transcode the file and modify its bitrate. The user requested codec is %1$s, with a bitrate of %2$s. Any potential changes to the codec and bitrate of the file in the chosen format will be handled by the server, which may or may not support the operation.</string>
</resources> </resources>