diff --git a/app/build.gradle b/app/build.gradle index f669cb16..18013afa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,8 +28,8 @@ android { tempo { dimension "default" applicationId 'com.cappielloantonio.tempo' - versionCode 19 - versionName '3.5.4' + versionCode 20 + versionName '3.5.5' } notquitemy { diff --git a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java index ffa00602..2ee4e55e 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java +++ b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java @@ -204,6 +204,22 @@ public class MediaManager { } } + public static void shuffle(ListenableFuture mediaBrowserListenableFuture, List 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 mediaBrowserListenableFuture, List media, int from, int to) { if (mediaBrowserListenableFuture != null) { mediaBrowserListenableFuture.addListener(() -> { diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/TrackInfoDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/TrackInfoDialog.java new file mode 100644 index 00000000..ccd4d601 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/TrackInfoDialog.java @@ -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); + } + } +} diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java index 39ccbcc0..83995d8a 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java @@ -7,6 +7,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.PopupMenu; +import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; @@ -37,6 +38,8 @@ import java.util.Objects; @UnstableApi public class DownloadFragment extends Fragment implements ClickCallback { + private static final String TAG = "DownloadFragment"; + private FragmentDownloadBinding bind; private MainActivity activity; private DownloadViewModel downloadViewModel; @@ -160,6 +163,23 @@ public class DownloadFragment extends Fragment implements ClickCallback { } 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(); + } }); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java index 435a4d06..36b4d3bf 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java @@ -240,6 +240,10 @@ public class PlayerBottomSheetFragment extends Fragment { } } + public void goToQueuePage() { + bind.playerBodyLayout.playerBodyBottomSheetViewPager.setCurrentItem(1, true); + } + private void defineProgressBarHandler(MediaBrowser mediaBrowser) { progressBarHandler = new Handler(); progressBarRunnable = () -> { diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java index 917573f6..fc60d9b0 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java @@ -6,12 +6,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; -import android.widget.ImageView; +import android.widget.ImageButton; import android.widget.TextView; -import android.widget.Toast; import android.widget.ToggleButton; import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; 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.ui.activity.MainActivity; 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.util.Constants; import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel; 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.MoreExecutors; @@ -51,10 +53,8 @@ public class PlayerControllerFragment extends Fragment { private ToggleButton skipSilenceToggleButton; private Chip playerMediaExtension; private TextView playerMediaBitrate; - private ImageView playerMediaTranscodingIcon; - private ImageView playerMediaTranscodingPriorityIcon; - private Chip playerMediaTranscodedExtension; - private TextView playerMediaTranscodedBitrate; + private ConstraintLayout playerQuickActionView; + private ImageButton playerTrackInfo; private MainActivity activity; private PlayerBottomSheetViewModel playerBottomSheetViewModel; @@ -70,10 +70,10 @@ public class PlayerControllerFragment extends Fragment { playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class); init(); + initQuickActionView(); initCoverLyricsSlideView(); initMediaListenable(); initArtistLabelButton(); - initTranscodingInfo(); return view; } @@ -106,10 +106,19 @@ public class PlayerControllerFragment extends Fragment { skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button); playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension); playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate); - playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio); - playerMediaTranscodingPriorityIcon = bind.getRoot().findViewById(R.id.player_media_server_transcode_priority); - playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension); - playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate); + playerQuickActionView = bind.getRoot().findViewById(R.id.player_quick_action_view); + playerTrackInfo = bind.getRoot().findViewById(R.id.player_info_track); + } + + 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() { @@ -160,9 +169,7 @@ public class PlayerControllerFragment extends Fragment { private void setMediaInfo(MediaMetadata mediaMetadata) { if (mediaMetadata.extras != null) { String extension = mediaMetadata.extras.getString("suffix", "Unknown format"); - String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 - ? mediaMetadata.extras.getInt("bitrate", 0) + "kbps" - : "Original"; + String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 ? mediaMetadata.extras.getInt("bitrate", 0) + "kbps" : "Original"; playerMediaExtension.setText(extension); @@ -174,38 +181,10 @@ public class PlayerControllerFragment extends Fragment { } } - String transcodingExtension = MusicUtil.getTranscodingFormatPreference(); - String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0 - ? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps" - : "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); - } + playerTrackInfo.setOnClickListener(view -> { + TrackInfoDialog dialog = new TrackInfoDialog(mediaMetadata); + dialog.show(activity.getSupportFragmentManager(), null); + }); } 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) { playbackSpeedButton.setOnClickListener(view -> { float currentSpeed = Preferences.getPlaybackSpeed(); diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java index 568d0672..63c2b46f 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java @@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment; import android.content.ComponentName; import android.os.Bundle; +import android.os.Handler; import android.transition.Fade; import android.transition.Transition; import android.transition.TransitionManager; @@ -39,6 +40,8 @@ public class PlayerCoverFragment extends Fragment { private InnerFragmentPlayerCoverBinding bind; private ListenableFuture mediaBrowserListenableFuture; + private final Handler handler = new Handler(); + @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false); @@ -72,9 +75,19 @@ public class PlayerCoverFragment extends Fragment { 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() { bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true)); bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false)); + bind.nowPlayingTapButton.setOnClickListener(view -> toggleOverlayVisibility(true)); } private void toggleOverlayVisibility(boolean isVisible) { @@ -84,9 +97,12 @@ public class PlayerCoverFragment extends Fragment { TransitionManager.beginDelayedTransition(bind.getRoot(), transition); 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.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE); + + if (!isVisible) initTapButtonHideTransition(); } private void initInnerButton() { diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java index e80913ed..8fd5f3b5 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java @@ -25,12 +25,16 @@ import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter; import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel; 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.stream.Collectors; @UnstableApi public class PlayerQueueFragment extends Fragment implements ClickCallback { + private static final String TAG = "PlayerQueueFragment"; + private InnerFragmentPlayerQueueBinding bind; private PlayerBottomSheetViewModel playerBottomSheetViewModel; @@ -54,6 +58,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { public void onStart() { super.onStart(); initializeBrowser(); + bindMediaController(); } @Override @@ -83,6 +88,17 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { 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() { playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); } @@ -151,6 +167,36 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { }).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 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() { playerSongQueueAdapter.notifyDataSetChanged(); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java index 818c8f1a..2b914eb2 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java @@ -147,8 +147,6 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements })); 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 -> { List mediaItems = MappingUtil.mapDownloads(songs); List 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); 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 mediaItems = MappingUtil.mapDownloads(songs); + List 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); goToArtist.setOnClickListener(v -> albumBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> { if (artist != null) { @@ -191,6 +193,16 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements dismiss(); } + private void initDownloadUI(TextView removeAll) { + albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> { + List mediaItems = MappingUtil.mapDownloads(songs); + + if (DownloadUtil.getDownloadTracker(requireContext()).areDownloaded(mediaItems)) { + removeAll.setVisibility(View.VISIBLE); + } + }); + } + private void initializeMediaBrowser() { mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync(); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/MusicUtil.java b/app/src/main/java/com/cappielloantonio/tempo/util/MusicUtil.java index c98ddad2..5ae88785 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/util/MusicUtil.java +++ b/app/src/main/java/com/cappielloantonio/tempo/util/MusicUtil.java @@ -14,6 +14,8 @@ import com.cappielloantonio.tempo.model.Download; import com.cappielloantonio.tempo.repository.DownloadRepository; import com.cappielloantonio.tempo.subsonic.models.Child; +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -211,6 +213,27 @@ public class MusicUtil { 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) { return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining()); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/DownloadViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/DownloadViewModel.java index 468ede57..3059eb09 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/DownloadViewModel.java +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/DownloadViewModel.java @@ -1,6 +1,7 @@ package com.cappielloantonio.tempo.viewmodel; import android.app.Application; +import android.util.Log; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; @@ -11,7 +12,6 @@ import androidx.lifecycle.MutableLiveData; import com.cappielloantonio.tempo.model.DownloadStack; import com.cappielloantonio.tempo.repository.DownloadRepository; import com.cappielloantonio.tempo.subsonic.models.Child; -import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Preferences; import java.util.ArrayList; diff --git a/app/src/main/res/drawable/ic_info_stream.xml b/app/src/main/res/drawable/ic_info_stream.xml new file mode 100644 index 00000000..5165a161 --- /dev/null +++ b/app/src/main/res/drawable/ic_info_stream.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_queue.xml b/app/src/main/res/drawable/ic_queue.xml new file mode 100644 index 00000000..6a04876d --- /dev/null +++ b/app/src/main/res/drawable/ic_queue.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_tap.xml b/app/src/main/res/drawable/ic_tap.xml new file mode 100644 index 00000000..7d295106 --- /dev/null +++ b/app/src/main/res/drawable/ic_tap.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_album_dialog.xml b/app/src/main/res/layout/bottom_sheet_album_dialog.xml index 70000085..5d4cb050 100644 --- a/app/src/main/res/layout/bottom_sheet_album_dialog.xml +++ b/app/src/main/res/layout/bottom_sheet_album_dialog.xml @@ -157,6 +157,7 @@ android:paddingTop="12dp" android:paddingEnd="20dp" android:paddingBottom="12dp" + android:visibility="gone" android:text="@string/album_bottom_sheet_remove_all" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/inner_fragment_player_controller_layout.xml b/app/src/main/res/layout/inner_fragment_player_controller_layout.xml index 750c61e8..e00badd5 100644 --- a/app/src/main/res/layout/inner_fragment_player_controller_layout.xml +++ b/app/src/main/res/layout/inner_fragment_player_controller_layout.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + 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"/> + 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"/> - - - - - + android:padding="16dp" + android:layout_marginEnd="8dp" + android:background="?attr/selectableItemBackgroundBorderless" + android:scaleType="fitCenter" + 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" /> - - + + app:layout_constraintGuide_percent="0.575" /> + app:layout_constraintVertical_bias=".45" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/inner_fragment_player_cover.xml b/app/src/main/res/layout/inner_fragment_player_cover.xml index dabe7675..8eae8bd1 100644 --- a/app/src/main/res/layout/inner_fragment_player_cover.xml +++ b/app/src/main/res/layout/inner_fragment_player_cover.xml @@ -15,6 +15,22 @@ app:layout_constraintDimensionRatio="1:1" app:layout_constraintTop_toTopOf="parent" /> +