diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 816683ca..b8d72d8b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -42,6 +42,16 @@
+
+
+
+
+
+
+
+
getPlaylist(String id) {
+ MutableLiveData playlistLiveData = new MutableLiveData<>();
+
+ App.getSubsonicClientInstance(false)
+ .getPlaylistClient()
+ .getPlaylist(id)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful()
+ && response.body() != null
+ && response.body().getSubsonicResponse().getPlaylist() != null) {
+ playlistLiveData.setValue(response.body().getSubsonicResponse().getPlaylist());
+ } else {
+ playlistLiveData.setValue(null);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ playlistLiveData.setValue(null);
+ }
+ });
+
+ return playlistLiveData;
+ }
+
public void addSongToPlaylist(String playlistId, ArrayList songsId) {
if (songsId.isEmpty()) {
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_all_skipped), Toast.LENGTH_SHORT).show();
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java b/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java
index db76b98d..9aa558f8 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java
@@ -37,6 +37,8 @@ import com.cappielloantonio.tempo.ui.dialog.ConnectionAlertDialog;
import com.cappielloantonio.tempo.ui.dialog.GithubTempoUpdateDialog;
import com.cappielloantonio.tempo.ui.dialog.ServerUnreachableDialog;
import com.cappielloantonio.tempo.ui.fragment.PlayerBottomSheetFragment;
+import com.cappielloantonio.tempo.util.AssetLinkNavigator;
+import com.cappielloantonio.tempo.util.AssetLinkUtil;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
@@ -60,6 +62,8 @@ public class MainActivity extends BaseActivity {
private BottomNavigationView bottomNavigationView;
public NavController navController;
private BottomSheetBehavior bottomSheetBehavior;
+ private AssetLinkNavigator assetLinkNavigator;
+ private AssetLinkUtil.AssetLink pendingAssetLink;
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
private Intent pendingDownloadPlaybackIntent;
@@ -76,6 +80,7 @@ public class MainActivity extends BaseActivity {
setContentView(view);
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
+ assetLinkNavigator = new AssetLinkNavigator(this);
connectivityStatusBroadcastReceiver = new ConnectivityStatusBroadcastReceiver(this);
connectivityStatusReceiverManager(true);
@@ -311,6 +316,24 @@ public class MainActivity extends BaseActivity {
public void goFromLogin() {
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
goToHome();
+ consumePendingAssetLink();
+ }
+
+ public void openAssetLink(@NonNull AssetLinkUtil.AssetLink assetLink) {
+ openAssetLink(assetLink, true);
+ }
+
+ public void openAssetLink(@NonNull AssetLinkUtil.AssetLink assetLink, boolean collapsePlayer) {
+ if (!isUserAuthenticated()) {
+ pendingAssetLink = assetLink;
+ return;
+ }
+ if (collapsePlayer) {
+ setBottomSheetInPeek(true);
+ }
+ if (assetLinkNavigator != null) {
+ assetLinkNavigator.open(assetLink);
+ }
}
public void quit() {
@@ -443,6 +466,7 @@ public class MainActivity extends BaseActivity {
|| intent.hasExtra(Constants.EXTRA_DOWNLOAD_URI)) {
pendingDownloadPlaybackIntent = new Intent(intent);
}
+ handleAssetLinkIntent(intent);
}
private void consumePendingPlaybackIntent() {
@@ -452,6 +476,35 @@ public class MainActivity extends BaseActivity {
playDownloadedMedia(intent);
}
+ private void handleAssetLinkIntent(Intent intent) {
+ AssetLinkUtil.AssetLink assetLink = AssetLinkUtil.parse(intent);
+ if (assetLink == null) {
+ return;
+ }
+ if (!isUserAuthenticated()) {
+ pendingAssetLink = assetLink;
+ intent.setData(null);
+ return;
+ }
+ if (assetLinkNavigator != null) {
+ assetLinkNavigator.open(assetLink);
+ }
+ intent.setData(null);
+ }
+
+ private boolean isUserAuthenticated() {
+ return Preferences.getPassword() != null
+ || (Preferences.getToken() != null && Preferences.getSalt() != null);
+ }
+
+ private void consumePendingAssetLink() {
+ if (pendingAssetLink == null || assetLinkNavigator == null) {
+ return;
+ }
+ assetLinkNavigator.open(pendingAssetLink);
+ pendingAssetLink = null;
+ }
+
private void playDownloadedMedia(Intent intent) {
String uriString = intent.getStringExtra(Constants.EXTRA_DOWNLOAD_URI);
if (TextUtils.isEmpty(uriString)) {
@@ -500,4 +553,4 @@ public class MainActivity extends BaseActivity {
MediaManager.playDownloadedMediaItem(getMediaBrowserListenableFuture(), mediaItem);
}
-}
\ No newline at end of file
+}
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
index f866c250..e6b91f01 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/TrackInfoDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/TrackInfoDialog.java
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.dialog;
import android.app.Dialog;
import android.os.Bundle;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
@@ -10,6 +11,7 @@ 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.AssetLinkUtil;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
@@ -21,6 +23,11 @@ public class TrackInfoDialog extends DialogFragment {
private DialogTrackInfoBinding bind;
private final MediaMetadata mediaMetadata;
+ private AssetLinkUtil.AssetLink songLink;
+ private AssetLinkUtil.AssetLink albumLink;
+ private AssetLinkUtil.AssetLink artistLink;
+ private AssetLinkUtil.AssetLink genreLink;
+ private AssetLinkUtil.AssetLink yearLink;
public TrackInfoDialog(MediaMetadata mediaMetadata) {
this.mediaMetadata = mediaMetadata;
@@ -52,6 +59,8 @@ public class TrackInfoDialog extends DialogFragment {
}
private void setTrackInfo() {
+ genreLink = null;
+ yearLink = null;
bind.trakTitleInfoTextView.setText(mediaMetadata.title);
bind.trakArtistInfoTextView.setText(
mediaMetadata.artist != null
@@ -61,17 +70,41 @@ public class TrackInfoDialog extends DialogFragment {
: "");
if (mediaMetadata.extras != null) {
+ songLink = AssetLinkUtil.buildAssetLink(AssetLinkUtil.TYPE_SONG, mediaMetadata.extras.getString("id"));
+ albumLink = AssetLinkUtil.buildAssetLink(AssetLinkUtil.TYPE_ALBUM, mediaMetadata.extras.getString("albumId"));
+ artistLink = AssetLinkUtil.buildAssetLink(AssetLinkUtil.TYPE_ARTIST, mediaMetadata.extras.getString("artistId"));
+ genreLink = AssetLinkUtil.parseLinkString(mediaMetadata.extras.getString("assetLinkGenre"));
+ yearLink = AssetLinkUtil.parseLinkString(mediaMetadata.extras.getString("assetLinkYear"));
+
CustomGlideRequest.Builder
.from(requireContext(), mediaMetadata.extras.getString("coverArtId", ""), CustomGlideRequest.ResourceType.Song)
.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)));
+ bindAssetLink(bind.trackCoverInfoImageView, albumLink != null ? albumLink : songLink);
+ bindAssetLink(bind.trakTitleInfoTextView, songLink);
+ bindAssetLink(bind.trakArtistInfoTextView, artistLink != null ? artistLink : songLink);
+
+ String titleValue = mediaMetadata.extras.getString("title", getString(R.string.label_placeholder));
+ String albumValue = mediaMetadata.extras.getString("album", getString(R.string.label_placeholder));
+ String artistValue = mediaMetadata.extras.getString("artist", getString(R.string.label_placeholder));
+ String genreValue = mediaMetadata.extras.getString("genre", getString(R.string.label_placeholder));
+ int yearValue = mediaMetadata.extras.getInt("year", 0);
+
+ if (genreLink == null && genreValue != null && !genreValue.isEmpty() && !getString(R.string.label_placeholder).contentEquals(genreValue)) {
+ genreLink = AssetLinkUtil.buildAssetLink(AssetLinkUtil.TYPE_GENRE, genreValue);
+ }
+
+ if (yearLink == null && yearValue != 0) {
+ yearLink = AssetLinkUtil.buildAssetLink(AssetLinkUtil.TYPE_YEAR, String.valueOf(yearValue));
+ }
+
+ bind.titleValueSector.setText(titleValue);
+ bind.albumValueSector.setText(albumValue);
+ bind.artistValueSector.setText(artistValue);
bind.trackNumberValueSector.setText(mediaMetadata.extras.getInt("track", 0) != 0 ? String.valueOf(mediaMetadata.extras.getInt("track", 0)) : getString(R.string.label_placeholder));
- bind.yearValueSector.setText(mediaMetadata.extras.getInt("year", 0) != 0 ? String.valueOf(mediaMetadata.extras.getInt("year", 0)) : getString(R.string.label_placeholder));
- bind.genreValueSector.setText(mediaMetadata.extras.getString("genre", getString(R.string.label_placeholder)));
+ bind.yearValueSector.setText(yearValue != 0 ? String.valueOf(yearValue) : getString(R.string.label_placeholder));
+ bind.genreValueSector.setText(genreValue);
bind.sizeValueSector.setText(mediaMetadata.extras.getLong("size", 0) != 0 ? MusicUtil.getReadableByteCount(mediaMetadata.extras.getLong("size", 0)) : getString(R.string.label_placeholder));
bind.contentTypeValueSector.setText(mediaMetadata.extras.getString("contentType", getString(R.string.label_placeholder)));
bind.suffixValueSector.setText(mediaMetadata.extras.getString("suffix", getString(R.string.label_placeholder)));
@@ -83,6 +116,12 @@ public class TrackInfoDialog extends DialogFragment {
bind.bitDepthValueSector.setText(mediaMetadata.extras.getInt("bitDepth", 0) != 0 ? mediaMetadata.extras.getInt("bitDepth", 0) + " bits" : getString(R.string.label_placeholder));
bind.pathValueSector.setText(mediaMetadata.extras.getString("path", getString(R.string.label_placeholder)));
bind.discNumberValueSector.setText(mediaMetadata.extras.getInt("discNumber", 0) != 0 ? String.valueOf(mediaMetadata.extras.getInt("discNumber", 0)) : getString(R.string.label_placeholder));
+
+ bindAssetLink(bind.titleValueSector, songLink);
+ bindAssetLink(bind.albumValueSector, albumLink);
+ bindAssetLink(bind.artistValueSector, artistLink);
+ bindAssetLink(bind.genreValueSector, genreLink);
+ bindAssetLink(bind.yearValueSector, yearLink);
}
}
@@ -135,4 +174,31 @@ public class TrackInfoDialog extends DialogFragment {
bind.trakTranscodingInfoTextView.setText(info);
}
}
+
+ private void bindAssetLink(android.view.View view, AssetLinkUtil.AssetLink assetLink) {
+ if (view == null) return;
+ if (assetLink == null) {
+ AssetLinkUtil.clearLinkAppearance(view);
+ view.setOnClickListener(null);
+ view.setOnLongClickListener(null);
+ view.setClickable(false);
+ view.setLongClickable(false);
+ return;
+ }
+
+ view.setClickable(true);
+ view.setLongClickable(true);
+ AssetLinkUtil.applyLinkAppearance(view);
+ view.setOnClickListener(v -> {
+ dismissAllowingStateLoss();
+ boolean collapse = !AssetLinkUtil.TYPE_SONG.equals(assetLink.type);
+ ((com.cappielloantonio.tempo.ui.activity.MainActivity) requireActivity()).openAssetLink(assetLink, collapse);
+ });
+ view.setOnLongClickListener(v -> {
+ AssetLinkUtil.copyToClipboard(requireContext(), assetLink);
+ Toast.makeText(requireContext(), getString(R.string.asset_link_copied_toast, assetLink.id), Toast.LENGTH_SHORT).show();
+ return true;
+ });
+ }
+
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java
index d0b417d7..a7f5d174 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java
@@ -35,6 +35,7 @@ import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
+import com.cappielloantonio.tempo.util.AssetLinkUtil;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
@@ -177,8 +178,35 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
bind.albumNameLabel.setText(album.getName());
bind.albumArtistLabel.setText(album.getArtist());
+ AssetLinkUtil.applyLinkAppearance(bind.albumArtistLabel);
+ AssetLinkUtil.AssetLink artistLink = buildArtistLink(album);
+ bind.albumArtistLabel.setOnLongClickListener(v -> {
+ if (artistLink != null) {
+ AssetLinkUtil.copyToClipboard(requireContext(), artistLink);
+ Toast.makeText(requireContext(), getString(R.string.asset_link_copied_toast, artistLink.id), Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ return false;
+ });
bind.albumReleaseYearLabel.setText(album.getYear() != 0 ? String.valueOf(album.getYear()) : "");
- bind.albumReleaseYearLabel.setVisibility(album.getYear() != 0 ? View.VISIBLE : View.GONE);
+ if (album.getYear() != 0) {
+ bind.albumReleaseYearLabel.setVisibility(View.VISIBLE);
+ AssetLinkUtil.applyLinkAppearance(bind.albumReleaseYearLabel);
+ bind.albumReleaseYearLabel.setOnClickListener(v -> openYearLink(album.getYear()));
+ bind.albumReleaseYearLabel.setOnLongClickListener(v -> {
+ AssetLinkUtil.AssetLink yearLink = buildYearLink(album.getYear());
+ if (yearLink != null) {
+ AssetLinkUtil.copyToClipboard(requireContext(), yearLink);
+ Toast.makeText(requireContext(), getString(R.string.asset_link_copied_toast, yearLink.id), Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ });
+ } else {
+ bind.albumReleaseYearLabel.setVisibility(View.GONE);
+ bind.albumReleaseYearLabel.setOnClickListener(null);
+ bind.albumReleaseYearLabel.setOnLongClickListener(null);
+ AssetLinkUtil.clearLinkAppearance(bind.albumReleaseYearLabel);
+ }
bind.albumSongCountDurationTextview.setText(getString(R.string.album_page_tracks_count_and_duration, album.getSongCount(), album.getDuration() != null ? album.getDuration() / 60 : 0));
if (album.getGenre() != null && !album.getGenre().isEmpty()) {
bind.albumGenresTextview.setText(album.getGenre());
@@ -347,4 +375,23 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
private void setMediaBrowserListenableFuture() {
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
-}
\ No newline at end of file
+
+ private void openYearLink(int year) {
+ AssetLinkUtil.AssetLink link = buildYearLink(year);
+ if (link != null) {
+ activity.openAssetLink(link);
+ }
+ }
+
+ private AssetLinkUtil.AssetLink buildYearLink(int year) {
+ if (year <= 0) return null;
+ return AssetLinkUtil.buildAssetLink(AssetLinkUtil.TYPE_YEAR, String.valueOf(year));
+ }
+
+ private AssetLinkUtil.AssetLink buildArtistLink(AlbumID3 album) {
+ if (album == null || album.getArtistId() == null || album.getArtistId().isEmpty()) {
+ return null;
+ }
+ return AssetLinkUtil.buildAssetLink(AssetLinkUtil.TYPE_ARTIST, album.getArtistId());
+ }
+}
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 6841f247..e2bca343 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
@@ -195,6 +195,7 @@ public class PlayerBottomSheetFragment extends Fragment {
}
}
+
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
if (mediaBrowser.getMediaMetadata().extras != null) {
switch (mediaBrowser.getMediaMetadata().extras.getString("type", Constants.MEDIA_TYPE_MUSIC)) {
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 e63436c7..e3155b56 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
@@ -13,9 +13,10 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.RatingBar;
import android.widget.TextView;
import android.widget.ToggleButton;
-import android.widget.RatingBar;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
@@ -41,12 +42,14 @@ 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.AssetLinkUtil;
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.cappielloantonio.tempo.viewmodel.RatingViewModel;
import com.google.android.material.chip.Chip;
+import com.google.android.material.chip.ChipGroup;
import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@@ -76,6 +79,10 @@ public class PlayerControllerFragment extends Fragment {
private ImageButton playerTrackInfo;
private LinearLayout ratingContainer;
private ImageButton equalizerButton;
+ private ChipGroup assetLinkChipGroup;
+ private Chip playerSongLinkChip;
+ private Chip playerAlbumLinkChip;
+ private Chip playerArtistLinkChip;
private MainActivity activity;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@@ -139,6 +146,10 @@ public class PlayerControllerFragment extends Fragment {
songRatingBar = bind.getRoot().findViewById(R.id.song_rating_bar);
ratingContainer = bind.getRoot().findViewById(R.id.rating_container);
equalizerButton = bind.getRoot().findViewById(R.id.player_open_equalizer_button);
+ assetLinkChipGroup = bind.getRoot().findViewById(R.id.asset_link_chip_group);
+ playerSongLinkChip = bind.getRoot().findViewById(R.id.asset_link_song_chip);
+ playerAlbumLinkChip = bind.getRoot().findViewById(R.id.asset_link_album_chip);
+ playerArtistLinkChip = bind.getRoot().findViewById(R.id.asset_link_artist_chip);
checkAndSetRatingContainerVisibility();
}
@@ -219,6 +230,8 @@ public class PlayerControllerFragment extends Fragment {
|| mediaMetadata.extras != null && Objects.equals(mediaMetadata.extras.getString("type"), Constants.MEDIA_TYPE_RADIO) && mediaMetadata.extras.getString("uri") != null
? View.VISIBLE
: View.GONE);
+
+ updateAssetLinkChips(mediaMetadata);
}
private void setMediaInfo(MediaMetadata mediaMetadata) {
@@ -259,6 +272,110 @@ public class PlayerControllerFragment extends Fragment {
});
}
+ private void updateAssetLinkChips(MediaMetadata mediaMetadata) {
+ if (assetLinkChipGroup == null) return;
+ String mediaType = mediaMetadata.extras != null ? mediaMetadata.extras.getString("type", Constants.MEDIA_TYPE_MUSIC) : Constants.MEDIA_TYPE_MUSIC;
+ if (!Constants.MEDIA_TYPE_MUSIC.equals(mediaType)) {
+ clearAssetLinkChip(playerSongLinkChip);
+ clearAssetLinkChip(playerAlbumLinkChip);
+ clearAssetLinkChip(playerArtistLinkChip);
+ syncAssetLinkGroupVisibility();
+ return;
+ }
+
+ String songId = mediaMetadata.extras != null ? mediaMetadata.extras.getString("id") : null;
+ String albumId = mediaMetadata.extras != null ? mediaMetadata.extras.getString("albumId") : null;
+ String artistId = mediaMetadata.extras != null ? mediaMetadata.extras.getString("artistId") : null;
+
+ AssetLinkUtil.AssetLink songLink = bindAssetLinkChip(playerSongLinkChip, AssetLinkUtil.TYPE_SONG, songId);
+ AssetLinkUtil.AssetLink albumLink = bindAssetLinkChip(playerAlbumLinkChip, AssetLinkUtil.TYPE_ALBUM, albumId);
+ AssetLinkUtil.AssetLink artistLink = bindAssetLinkChip(playerArtistLinkChip, AssetLinkUtil.TYPE_ARTIST, artistId);
+ bindAssetLinkView(playerMediaTitleLabel, songLink);
+ bindAssetLinkView(playerArtistNameLabel, artistLink != null ? artistLink : songLink);
+ bindAssetLinkView(playerMediaCoverViewPager, songLink);
+ syncAssetLinkGroupVisibility();
+ }
+
+ private AssetLinkUtil.AssetLink bindAssetLinkChip(Chip chip, String type, String id) {
+ if (chip == null) return null;
+ if (TextUtils.isEmpty(id)) {
+ clearAssetLinkChip(chip);
+ return null;
+ }
+
+ String label = getString(AssetLinkUtil.getLabelRes(type));
+ AssetLinkUtil.AssetLink assetLink = AssetLinkUtil.buildAssetLink(type, id);
+ if (assetLink == null) {
+ clearAssetLinkChip(chip);
+ return null;
+ }
+
+ chip.setText(getString(R.string.asset_link_chip_text, label, assetLink.id));
+ chip.setVisibility(View.VISIBLE);
+
+ chip.setOnClickListener(v -> {
+ if (assetLink != null) {
+ activity.openAssetLink(assetLink);
+ }
+ });
+
+ chip.setOnLongClickListener(v -> {
+ if (assetLink != null) {
+ AssetLinkUtil.copyToClipboard(requireContext(), assetLink);
+ Toast.makeText(requireContext(), getString(R.string.asset_link_copied_toast, id), Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ });
+
+ return assetLink;
+ }
+
+ private void clearAssetLinkChip(Chip chip) {
+ if (chip == null) return;
+ chip.setVisibility(View.GONE);
+ chip.setText("");
+ chip.setOnClickListener(null);
+ chip.setOnLongClickListener(null);
+ }
+
+ private void bindAssetLinkView(View view, AssetLinkUtil.AssetLink assetLink) {
+ if (view == null) return;
+ if (assetLink == null) {
+ AssetLinkUtil.clearLinkAppearance(view);
+ view.setOnClickListener(null);
+ view.setOnLongClickListener(null);
+ view.setClickable(false);
+ view.setLongClickable(false);
+ return;
+ }
+
+ view.setClickable(true);
+ view.setLongClickable(true);
+ AssetLinkUtil.applyLinkAppearance(view);
+ view.setOnClickListener(v -> {
+ boolean collapse = !AssetLinkUtil.TYPE_SONG.equals(assetLink.type);
+ activity.openAssetLink(assetLink, collapse);
+ });
+ view.setOnLongClickListener(v -> {
+ AssetLinkUtil.copyToClipboard(requireContext(), assetLink);
+ Toast.makeText(requireContext(), getString(R.string.asset_link_copied_toast, assetLink.id), Toast.LENGTH_SHORT).show();
+ return true;
+ });
+ }
+
+ private void syncAssetLinkGroupVisibility() {
+ if (assetLinkChipGroup == null) return;
+ boolean hasVisible = false;
+ for (int i = 0; i < assetLinkChipGroup.getChildCount(); i++) {
+ View child = assetLinkChipGroup.getChildAt(i);
+ if (child.getVisibility() == View.VISIBLE) {
+ hasVisible = true;
+ break;
+ }
+ }
+ assetLinkChipGroup.setVisibility(hasVisible ? View.VISIBLE : View.GONE);
+ }
+
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
initPlaybackSpeedButton(mediaBrowser);
@@ -548,4 +665,4 @@ public class PlayerControllerFragment extends Fragment {
isServiceBound = false;
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java
index 90e32793..39ba4394 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java
@@ -30,6 +30,7 @@ import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
+import com.cappielloantonio.tempo.util.AssetLinkUtil;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.ExternalAudioReader;
@@ -39,6 +40,8 @@ import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
+import com.google.android.material.chip.Chip;
+import com.google.android.material.chip.ChipGroup;
import com.google.common.util.concurrent.ListenableFuture;
import android.content.Intent;
@@ -56,6 +59,13 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
private TextView downloadButton;
private TextView removeButton;
+ private ChipGroup assetLinkChipGroup;
+ private Chip songLinkChip;
+ private Chip albumLinkChip;
+ private Chip artistLinkChip;
+ private AssetLinkUtil.AssetLink currentSongLink;
+ private AssetLinkUtil.AssetLink currentAlbumLink;
+ private AssetLinkUtil.AssetLink currentArtistLink;
private ListenableFuture mediaBrowserListenableFuture;
@@ -109,6 +119,11 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
TextView artistSong = view.findViewById(R.id.song_artist_text_view);
artistSong.setText(songBottomSheetViewModel.getSong().getArtist());
+ initAssetLinkChips(view);
+ bindAssetLinkView(coverSong, currentSongLink);
+ bindAssetLinkView(titleSong, currentSongLink);
+ bindAssetLinkView(artistSong, currentArtistLink != null ? currentArtistLink : currentSongLink);
+
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(songBottomSheetViewModel.getSong().getStarred() != null);
favoriteToggle.setOnClickListener(v -> {
@@ -282,6 +297,95 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
}
}
+ private void initAssetLinkChips(View root) {
+ assetLinkChipGroup = root.findViewById(R.id.asset_link_chip_group);
+ songLinkChip = root.findViewById(R.id.asset_link_song_chip);
+ albumLinkChip = root.findViewById(R.id.asset_link_album_chip);
+ artistLinkChip = root.findViewById(R.id.asset_link_artist_chip);
+
+ currentSongLink = bindAssetLinkChip(songLinkChip, AssetLinkUtil.TYPE_SONG, song.getId());
+ currentAlbumLink = bindAssetLinkChip(albumLinkChip, AssetLinkUtil.TYPE_ALBUM, song.getAlbumId());
+ currentArtistLink = bindAssetLinkChip(artistLinkChip, AssetLinkUtil.TYPE_ARTIST, song.getArtistId());
+ syncAssetLinkGroupVisibility();
+ }
+
+ private AssetLinkUtil.AssetLink bindAssetLinkChip(@Nullable Chip chip, String type, @Nullable String id) {
+ if (chip == null) return null;
+ if (id == null || id.isEmpty()) {
+ clearAssetLinkChip(chip);
+ return null;
+ }
+
+ String label = getString(AssetLinkUtil.getLabelRes(type));
+ AssetLinkUtil.AssetLink assetLink = AssetLinkUtil.buildAssetLink(type, id);
+ if (assetLink == null) {
+ clearAssetLinkChip(chip);
+ return null;
+ }
+
+ chip.setText(getString(R.string.asset_link_chip_text, label, assetLink.id));
+ chip.setVisibility(View.VISIBLE);
+
+ chip.setOnClickListener(v -> {
+ if (assetLink != null) {
+ ((MainActivity) requireActivity()).openAssetLink(assetLink);
+ }
+ });
+
+ chip.setOnLongClickListener(v -> {
+ if (assetLink != null) {
+ AssetLinkUtil.copyToClipboard(requireContext(), assetLink);
+ Toast.makeText(requireContext(), getString(R.string.asset_link_copied_toast, id), Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ });
+
+ return assetLink;
+ }
+
+ private void clearAssetLinkChip(@Nullable Chip chip) {
+ if (chip == null) return;
+ chip.setVisibility(View.GONE);
+ chip.setText("");
+ chip.setOnClickListener(null);
+ chip.setOnLongClickListener(null);
+ }
+
+ private void syncAssetLinkGroupVisibility() {
+ if (assetLinkChipGroup == null) return;
+ boolean hasVisible = false;
+ for (int i = 0; i < assetLinkChipGroup.getChildCount(); i++) {
+ View child = assetLinkChipGroup.getChildAt(i);
+ if (child.getVisibility() == View.VISIBLE) {
+ hasVisible = true;
+ break;
+ }
+ }
+ assetLinkChipGroup.setVisibility(hasVisible ? View.VISIBLE : View.GONE);
+ }
+
+ private void bindAssetLinkView(@Nullable View view, @Nullable AssetLinkUtil.AssetLink assetLink) {
+ if (view == null) return;
+ if (assetLink == null) {
+ AssetLinkUtil.clearLinkAppearance(view);
+ view.setOnClickListener(null);
+ view.setOnLongClickListener(null);
+ view.setClickable(false);
+ view.setLongClickable(false);
+ return;
+ }
+
+ view.setClickable(true);
+ view.setLongClickable(true);
+ AssetLinkUtil.applyLinkAppearance(view);
+ view.setOnClickListener(v -> ((MainActivity) requireActivity()).openAssetLink(assetLink, !AssetLinkUtil.TYPE_SONG.equals(assetLink.type)));
+ view.setOnLongClickListener(v -> {
+ AssetLinkUtil.copyToClipboard(requireContext(), assetLink);
+ Toast.makeText(requireContext(), getString(R.string.asset_link_copied_toast, assetLink.id), Toast.LENGTH_SHORT).show();
+ return true;
+ });
+ }
+
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
@@ -293,4 +397,4 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
private void refreshShares() {
homeViewModel.refreshShares(requireActivity());
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/AssetLinkNavigator.java b/app/src/main/java/com/cappielloantonio/tempo/util/AssetLinkNavigator.java
new file mode 100644
index 00000000..9d3ba966
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/util/AssetLinkNavigator.java
@@ -0,0 +1,188 @@
+package com.cappielloantonio.tempo.util;
+
+import android.os.Bundle;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.navigation.NavController;
+import androidx.navigation.NavOptions;
+
+import com.cappielloantonio.tempo.BuildConfig;
+import com.cappielloantonio.tempo.R;
+import com.cappielloantonio.tempo.repository.AlbumRepository;
+import com.cappielloantonio.tempo.repository.ArtistRepository;
+import com.cappielloantonio.tempo.repository.PlaylistRepository;
+import com.cappielloantonio.tempo.repository.SongRepository;
+import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
+import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
+import com.cappielloantonio.tempo.subsonic.models.Child;
+import com.cappielloantonio.tempo.subsonic.models.Playlist;
+import com.cappielloantonio.tempo.subsonic.models.Genre;
+import com.cappielloantonio.tempo.ui.activity.MainActivity;
+import com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog.SongBottomSheetDialog;
+import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel;
+
+public final class AssetLinkNavigator {
+ private final MainActivity activity;
+ private final SongRepository songRepository = new SongRepository();
+ private final AlbumRepository albumRepository = new AlbumRepository();
+ private final ArtistRepository artistRepository = new ArtistRepository();
+ private final PlaylistRepository playlistRepository = new PlaylistRepository();
+
+ public AssetLinkNavigator(@NonNull MainActivity activity) {
+ this.activity = activity;
+ }
+
+ public void open(@Nullable AssetLinkUtil.AssetLink assetLink) {
+ if (assetLink == null) {
+ return;
+ }
+ switch (assetLink.type) {
+ case AssetLinkUtil.TYPE_SONG:
+ openSong(assetLink.id);
+ break;
+ case AssetLinkUtil.TYPE_ALBUM:
+ openAlbum(assetLink.id);
+ break;
+ case AssetLinkUtil.TYPE_ARTIST:
+ openArtist(assetLink.id);
+ break;
+ case AssetLinkUtil.TYPE_PLAYLIST:
+ openPlaylist(assetLink.id);
+ break;
+ case AssetLinkUtil.TYPE_GENRE:
+ openGenre(assetLink.id);
+ break;
+ case AssetLinkUtil.TYPE_YEAR:
+ openYear(assetLink.id);
+ break;
+ default:
+ Toast.makeText(activity, R.string.asset_link_error_unsupported, Toast.LENGTH_SHORT).show();
+ break;
+ }
+ }
+
+ private void openSong(@NonNull String id) {
+ MutableLiveData liveData = songRepository.getSong(id);
+ Observer observer = new Observer() {
+ @Override
+ public void onChanged(Child child) {
+ liveData.removeObserver(this);
+ if (child == null) {
+ Toast.makeText(activity, R.string.asset_link_error_song, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ SongBottomSheetViewModel viewModel = new ViewModelProvider(activity).get(SongBottomSheetViewModel.class);
+ viewModel.setSong(child);
+ SongBottomSheetDialog dialog = new SongBottomSheetDialog();
+ Bundle args = new Bundle();
+ args.putParcelable(Constants.TRACK_OBJECT, child);
+ dialog.setArguments(args);
+ dialog.show(activity.getSupportFragmentManager(), null);
+ }
+ };
+ liveData.observe(activity, observer);
+ }
+
+ private void openAlbum(@NonNull String id) {
+ MutableLiveData liveData = albumRepository.getAlbum(id);
+ Observer observer = new Observer() {
+ @Override
+ public void onChanged(AlbumID3 album) {
+ liveData.removeObserver(this);
+ if (album == null) {
+ Toast.makeText(activity, R.string.asset_link_error_album, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putParcelable(Constants.ALBUM_OBJECT, album);
+ navigateSafely(R.id.albumPageFragment, args);
+ }
+ };
+ liveData.observe(activity, observer);
+ }
+
+ private void openArtist(@NonNull String id) {
+ MutableLiveData liveData = artistRepository.getArtist(id);
+ Observer observer = new Observer() {
+ @Override
+ public void onChanged(ArtistID3 artist) {
+ liveData.removeObserver(this);
+ if (artist == null) {
+ Toast.makeText(activity, R.string.asset_link_error_artist, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putParcelable(Constants.ARTIST_OBJECT, artist);
+ navigateSafely(R.id.artistPageFragment, args);
+ }
+ };
+ liveData.observe(activity, observer);
+ }
+
+ private void openPlaylist(@NonNull String id) {
+ MutableLiveData liveData = playlistRepository.getPlaylist(id);
+ Observer observer = new Observer() {
+ @Override
+ public void onChanged(Playlist playlist) {
+ liveData.removeObserver(this);
+ if (playlist == null) {
+ Toast.makeText(activity, R.string.asset_link_error_playlist, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ Bundle args = new Bundle();
+ args.putParcelable(Constants.PLAYLIST_OBJECT, playlist);
+ navigateSafely(R.id.playlistPageFragment, args);
+ }
+ };
+ liveData.observe(activity, observer);
+ }
+
+ private void openGenre(@NonNull String genreName) {
+ String trimmed = genreName.trim();
+ if (trimmed.isEmpty()) {
+ Toast.makeText(activity, R.string.asset_link_error_unsupported, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ Genre genre = new Genre();
+ genre.setGenre(trimmed);
+ genre.setSongCount(0);
+ genre.setAlbumCount(0);
+ Bundle args = new Bundle();
+ args.putParcelable(Constants.GENRE_OBJECT, genre);
+ args.putString(Constants.MEDIA_BY_GENRE, Constants.MEDIA_BY_GENRE);
+ navigateSafely(R.id.songListPageFragment, args);
+ }
+
+ private void openYear(@NonNull String yearValue) {
+ try {
+ int year = Integer.parseInt(yearValue.trim());
+ Bundle args = new Bundle();
+ args.putInt("year_object", year);
+ args.putString(Constants.MEDIA_BY_YEAR, Constants.MEDIA_BY_YEAR);
+ navigateSafely(R.id.songListPageFragment, args);
+ } catch (NumberFormatException ex) {
+ Toast.makeText(activity, R.string.asset_link_error_unsupported, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void navigateSafely(int destinationId, @Nullable Bundle args) {
+ activity.runOnUiThread(() -> {
+ NavController navController = activity.navController;
+ if (navController == null) {
+ return;
+ }
+ if (navController.getCurrentDestination() != null
+ && navController.getCurrentDestination().getId() == destinationId) {
+ navController.navigate(destinationId, args, new NavOptions.Builder().setLaunchSingleTop(true).build());
+ } else {
+ navController.navigate(destinationId, args);
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/AssetLinkUtil.java b/app/src/main/java/com/cappielloantonio/tempo/util/AssetLinkUtil.java
new file mode 100644
index 00000000..1609a88a
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/util/AssetLinkUtil.java
@@ -0,0 +1,188 @@
+package com.cappielloantonio.tempo.util;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.core.content.ContextCompat;
+
+import com.cappielloantonio.tempo.R;
+
+import java.util.Objects;
+
+import com.google.android.material.color.MaterialColors;
+
+public final class AssetLinkUtil {
+ public static final String SCHEME = "tempo";
+ public static final String HOST_ASSET = "asset";
+
+ public static final String TYPE_SONG = "song";
+ public static final String TYPE_ALBUM = "album";
+ public static final String TYPE_ARTIST = "artist";
+ public static final String TYPE_PLAYLIST = "playlist";
+ public static final String TYPE_GENRE = "genre";
+ public static final String TYPE_YEAR = "year";
+
+ private AssetLinkUtil() {
+ }
+
+ @Nullable
+ public static AssetLink parse(@Nullable Intent intent) {
+ if (intent == null) return null;
+ return parse(intent.getData());
+ }
+
+ @Nullable
+ public static AssetLink parse(@Nullable Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+
+ if (!SCHEME.equalsIgnoreCase(uri.getScheme())) {
+ return null;
+ }
+
+ String host = uri.getHost();
+ if (!HOST_ASSET.equalsIgnoreCase(host)) {
+ return null;
+ }
+
+ if (uri.getPathSegments().size() < 2) {
+ return null;
+ }
+
+ String type = uri.getPathSegments().get(0);
+ String id = uri.getPathSegments().get(1);
+ if (TextUtils.isEmpty(type) || TextUtils.isEmpty(id)) {
+ return null;
+ }
+
+ if (!isSupportedType(type)) {
+ return null;
+ }
+
+ return new AssetLink(type, id, uri);
+ }
+
+ public static boolean isSupportedType(@Nullable String type) {
+ if (type == null) return false;
+ switch (type) {
+ case TYPE_SONG:
+ case TYPE_ALBUM:
+ case TYPE_ARTIST:
+ case TYPE_PLAYLIST:
+ case TYPE_GENRE:
+ case TYPE_YEAR:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @NonNull
+ public static Uri buildUri(@NonNull String type, @NonNull String id) {
+ return new Uri.Builder()
+ .scheme(SCHEME)
+ .authority(HOST_ASSET)
+ .appendPath(type)
+ .appendPath(id)
+ .build();
+ }
+
+ @Nullable
+ public static String buildLink(@Nullable String type, @Nullable String id) {
+ if (TextUtils.isEmpty(type) || TextUtils.isEmpty(id) || !isSupportedType(type)) {
+ return null;
+ }
+ return buildUri(Objects.requireNonNull(type), Objects.requireNonNull(id)).toString();
+ }
+
+ @Nullable
+ public static AssetLink buildAssetLink(@Nullable String type, @Nullable String id) {
+ String link = buildLink(type, id);
+ return parseLinkString(link);
+ }
+
+ @Nullable
+ public static AssetLink parseLinkString(@Nullable String link) {
+ if (TextUtils.isEmpty(link)) {
+ return null;
+ }
+ return parse(Uri.parse(link));
+ }
+
+ public static void copyToClipboard(@NonNull Context context, @NonNull AssetLink assetLink) {
+ ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+ if (clipboardManager == null) {
+ return;
+ }
+ ClipData clipData = ClipData.newPlainText(context.getString(R.string.asset_link_clipboard_label), assetLink.uri.toString());
+ clipboardManager.setPrimaryClip(clipData);
+ }
+
+ @StringRes
+ public static int getLabelRes(@NonNull String type) {
+ switch (type) {
+ case TYPE_SONG:
+ return R.string.asset_link_label_song;
+ case TYPE_ALBUM:
+ return R.string.asset_link_label_album;
+ case TYPE_ARTIST:
+ return R.string.asset_link_label_artist;
+ case TYPE_PLAYLIST:
+ return R.string.asset_link_label_playlist;
+ case TYPE_GENRE:
+ return R.string.asset_link_label_genre;
+ case TYPE_YEAR:
+ return R.string.asset_link_label_year;
+ default:
+ return R.string.asset_link_label_unknown;
+ }
+ }
+
+ public static void applyLinkAppearance(@NonNull View view) {
+ if (view instanceof TextView) {
+ TextView textView = (TextView) view;
+ if (textView.getTag(R.id.tag_link_original_color) == null) {
+ textView.setTag(R.id.tag_link_original_color, textView.getCurrentTextColor());
+ }
+ int accent = MaterialColors.getColor(view, com.google.android.material.R.attr.colorPrimary,
+ ContextCompat.getColor(view.getContext(), android.R.color.holo_blue_light));
+ textView.setTextColor(accent);
+ }
+ }
+
+ public static void clearLinkAppearance(@NonNull View view) {
+ if (view instanceof TextView) {
+ TextView textView = (TextView) view;
+ Object original = textView.getTag(R.id.tag_link_original_color);
+ if (original instanceof Integer) {
+ textView.setTextColor((Integer) original);
+ } else {
+ int defaultColor = MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnSurface,
+ ContextCompat.getColor(view.getContext(), android.R.color.primary_text_light));
+ textView.setTextColor(defaultColor);
+ }
+ }
+ }
+
+ public static final class AssetLink {
+ public final String type;
+ public final String id;
+ public final Uri uri;
+
+ AssetLink(@NonNull String type, @NonNull String id, @NonNull Uri uri) {
+ this.type = type;
+ this.id = id;
+ this.uri = uri;
+ }
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/ExternalAudioWriter.java b/app/src/main/java/com/cappielloantonio/tempo/util/ExternalAudioWriter.java
index cf0f768e..efd97350 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/util/ExternalAudioWriter.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/util/ExternalAudioWriter.java
@@ -17,6 +17,9 @@ import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
@@ -102,35 +105,76 @@ public class ExternalAudioWriter {
ExternalDownloadMetadataStore.remove(metadataKey);
return;
}
- String scheme = mediaUri.getScheme();
- if (scheme == null || (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https"))) {
- notifyFailure(context, "Unsupported media URI.");
- ExternalDownloadMetadataStore.remove(metadataKey);
- return;
- }
+
+ String scheme = mediaUri.getScheme() != null ? mediaUri.getScheme().toLowerCase(Locale.ROOT) : "";
HttpURLConnection connection = null;
+ DocumentFile sourceDocument = null;
+ File sourceFile = null;
+ long remoteLength = -1;
+ String mimeType = null;
DocumentFile targetFile = null;
- try {
- connection = (HttpURLConnection) new URL(mediaUri.toString()).openConnection();
- connection.setConnectTimeout(CONNECT_TIMEOUT_MS);
- connection.setReadTimeout(READ_TIMEOUT_MS);
- connection.setRequestProperty("Accept-Encoding", "identity");
- connection.connect();
- int responseCode = connection.getResponseCode();
- if (responseCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
- notifyFailure(context, "Server returned " + responseCode);
+ try {
+ if (scheme.equals("http") || scheme.equals("https")) {
+ connection = (HttpURLConnection) new URL(mediaUri.toString()).openConnection();
+ connection.setConnectTimeout(CONNECT_TIMEOUT_MS);
+ connection.setReadTimeout(READ_TIMEOUT_MS);
+ connection.setRequestProperty("Accept-Encoding", "identity");
+ connection.connect();
+
+ int responseCode = connection.getResponseCode();
+ if (responseCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
+ notifyFailure(context, "Server returned " + responseCode);
+ ExternalDownloadMetadataStore.remove(metadataKey);
+ return;
+ }
+
+ mimeType = connection.getContentType();
+ remoteLength = connection.getContentLengthLong();
+ } else if (scheme.equals("content")) {
+ sourceDocument = DocumentFile.fromSingleUri(context, mediaUri);
+ mimeType = context.getContentResolver().getType(mediaUri);
+ if (sourceDocument != null) {
+ remoteLength = sourceDocument.length();
+ }
+ } else if (scheme.equals("file")) {
+ String path = mediaUri.getPath();
+ if (path != null) {
+ sourceFile = new File(path);
+ if (sourceFile.exists()) {
+ remoteLength = sourceFile.length();
+ }
+ }
+ String ext = MimeTypeMap.getFileExtensionFromUrl(mediaUri.toString());
+ if (ext != null && !ext.isEmpty()) {
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
+ }
+ } else {
+ notifyFailure(context, "Unsupported media URI.");
ExternalDownloadMetadataStore.remove(metadataKey);
return;
}
- String mimeType = connection.getContentType();
if (mimeType == null || mimeType.isEmpty()) {
mimeType = "application/octet-stream";
}
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+ if ((extension == null || extension.isEmpty()) && sourceDocument != null && sourceDocument.getName() != null) {
+ String name = sourceDocument.getName();
+ int dot = name.lastIndexOf('.');
+ if (dot >= 0 && dot < name.length() - 1) {
+ extension = name.substring(dot + 1);
+ }
+ }
+ if ((extension == null || extension.isEmpty()) && sourceFile != null) {
+ String name = sourceFile.getName();
+ int dot = name.lastIndexOf('.');
+ if (dot >= 0 && dot < name.length() - 1) {
+ extension = name.substring(dot + 1);
+ }
+ }
if (extension == null || extension.isEmpty()) {
String suffix = child.getSuffix();
if (suffix != null && !suffix.isEmpty()) {
@@ -146,7 +190,6 @@ public class ExternalAudioWriter {
String fileName = sanitized + "." + extension;
DocumentFile existingFile = findFile(directory, fileName);
- long remoteLength = connection.getContentLengthLong();
Long recordedSize = ExternalDownloadMetadataStore.getSize(metadataKey);
if (existingFile != null && existingFile.exists()) {
long localLength = existingFile.length();
@@ -175,7 +218,7 @@ public class ExternalAudioWriter {
}
Uri targetUri = targetFile.getUri();
- try (InputStream in = connection.getInputStream();
+ try (InputStream in = openInputStream(context, mediaUri, scheme, connection, sourceFile);
OutputStream out = context.getContentResolver().openOutputStream(targetUri)) {
if (out == null) {
notifyFailure(context, "Cannot open output stream.");
@@ -319,4 +362,32 @@ public class ExternalAudioWriter {
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
}
+
+ private static InputStream openInputStream(Context context,
+ Uri mediaUri,
+ String scheme,
+ HttpURLConnection connection,
+ File sourceFile) throws IOException {
+ switch (scheme) {
+ case "http":
+ case "https":
+ if (connection == null) {
+ throw new IOException("Connection not initialized");
+ }
+ return connection.getInputStream();
+ case "content":
+ InputStream contentStream = context.getContentResolver().openInputStream(mediaUri);
+ if (contentStream == null) {
+ throw new IOException("Cannot open content stream");
+ }
+ return contentStream;
+ case "file":
+ if (sourceFile == null || !sourceFile.exists()) {
+ throw new IOException("Missing source file");
+ }
+ return new FileInputStream(sourceFile);
+ default:
+ throw new IOException("Unsupported scheme " + scheme);
+ }
+ }
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/MappingUtil.java b/app/src/main/java/com/cappielloantonio/tempo/util/MappingUtil.java
index 9bd7fcad..558aa218 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/util/MappingUtil.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/util/MappingUtil.java
@@ -74,6 +74,12 @@ public class MappingUtil {
bundle.putInt("originalWidth", media.getOriginalWidth() != null ? media.getOriginalWidth() : 0);
bundle.putInt("originalHeight", media.getOriginalHeight() != null ? media.getOriginalHeight() : 0);
bundle.putString("uri", uri.toString());
+ bundle.putString("assetLinkSong", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, media.getId()));
+ bundle.putString("assetLinkAlbum", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, media.getAlbumId()));
+ bundle.putString("assetLinkArtist", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, media.getArtistId()));
+ bundle.putString("assetLinkGenre", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_GENRE, media.getGenre()));
+ Integer year = media.getYear();
+ bundle.putString("assetLinkYear", year != null && year != 0 ? AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_YEAR, String.valueOf(year)) : null);
return new MediaItem.Builder()
.setMediaId(media.getId())
diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java
index 06986145..93a1a7e2 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java
@@ -5,17 +5,20 @@ import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
+import android.text.TextUtils;
import android.widget.RemoteViews;
import com.cappielloantonio.tempo.R;
import android.app.TaskStackBuilder;
-import android.app.PendingIntent;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import android.util.Log;
+import androidx.annotation.Nullable;
+
public class WidgetProvider extends AppWidgetProvider {
private static final String TAG = "TempoWidget";
public static final String ACT_PLAY_PAUSE = "tempo.widget.PLAY_PAUSE";
@@ -28,7 +31,7 @@ public class WidgetProvider extends AppWidgetProvider {
public void onUpdate(Context ctx, AppWidgetManager mgr, int[] ids) {
for (int id : ids) {
RemoteViews rv = WidgetUpdateManager.chooseBuild(ctx, id);
- attachIntents(ctx, rv, id);
+ attachIntents(ctx, rv, id, null, null, null);
mgr.updateAppWidget(id, rv);
}
}
@@ -50,16 +53,23 @@ public class WidgetProvider extends AppWidgetProvider {
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, android.os.Bundle newOptions) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
RemoteViews rv = WidgetUpdateManager.chooseBuild(context, appWidgetId);
- attachIntents(context, rv, appWidgetId);
+ attachIntents(context, rv, appWidgetId, null, null, null);
appWidgetManager.updateAppWidget(appWidgetId, rv);
WidgetUpdateManager.refreshFromController(context);
}
public static void attachIntents(Context ctx, RemoteViews rv) {
- attachIntents(ctx, rv, 0);
+ attachIntents(ctx, rv, 0, null, null, null);
}
public static void attachIntents(Context ctx, RemoteViews rv, int requestCodeBase) {
+ attachIntents(ctx, rv, requestCodeBase, null, null, null);
+ }
+
+ public static void attachIntents(Context ctx, RemoteViews rv, int requestCodeBase,
+ String songLink,
+ String albumLink,
+ String artistLink) {
PendingIntent playPause = PendingIntent.getBroadcast(
ctx,
requestCodeBase + 0,
@@ -97,9 +107,31 @@ public class WidgetProvider extends AppWidgetProvider {
rv.setOnClickPendingIntent(R.id.btn_shuffle, shuffle);
rv.setOnClickPendingIntent(R.id.btn_repeat, repeat);
- PendingIntent launch = TaskStackBuilder.create(ctx)
- .addNextIntentWithParentStack(new Intent(ctx, MainActivity.class))
- .getPendingIntent(requestCodeBase + 10, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent launch = buildMainActivityPendingIntent(ctx, requestCodeBase + 10, null);
rv.setOnClickPendingIntent(R.id.root, launch);
+
+ PendingIntent songPending = buildMainActivityPendingIntent(ctx, requestCodeBase + 20, songLink);
+ PendingIntent artistPending = buildMainActivityPendingIntent(ctx, requestCodeBase + 21, artistLink);
+ PendingIntent albumPending = buildMainActivityPendingIntent(ctx, requestCodeBase + 22, albumLink);
+
+ PendingIntent fallback = launch;
+ rv.setOnClickPendingIntent(R.id.album_art, songPending != null ? songPending : fallback);
+ rv.setOnClickPendingIntent(R.id.title, songPending != null ? songPending : fallback);
+ rv.setOnClickPendingIntent(R.id.subtitle,
+ artistPending != null ? artistPending : (songPending != null ? songPending : fallback));
+ rv.setOnClickPendingIntent(R.id.album, albumPending != null ? albumPending : fallback);
+ }
+
+ private static PendingIntent buildMainActivityPendingIntent(Context ctx, int requestCode, @Nullable String link) {
+ Intent intent;
+ if (!TextUtils.isEmpty(link)) {
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link), ctx, MainActivity.class);
+ } else {
+ intent = new Intent(ctx, MainActivity.class);
+ }
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(ctx);
+ stackBuilder.addNextIntentWithParentStack(intent);
+ return stackBuilder.getPendingIntent(requestCode, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
}
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java
index 4132511e..c1fa409b 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java
@@ -4,8 +4,9 @@ import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
-import android.text.TextUtils;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
@@ -17,6 +18,7 @@ import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.service.MediaService;
+import com.cappielloantonio.tempo.util.AssetLinkUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@@ -34,7 +36,10 @@ public final class WidgetUpdateManager {
boolean shuffleEnabled,
int repeatMode,
long positionMs,
- long durationMs) {
+ long durationMs,
+ String songLink,
+ String albumLink,
+ String artistLink) {
if (TextUtils.isEmpty(title)) title = ctx.getString(R.string.widget_not_playing);
if (TextUtils.isEmpty(artist)) artist = ctx.getString(R.string.widget_placeholder_subtitle);
if (TextUtils.isEmpty(album)) album = "";
@@ -46,7 +51,7 @@ public final class WidgetUpdateManager {
for (int id : ids) {
android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, album, art, playing,
timing.elapsedText, timing.totalText, timing.progress, shuffleEnabled, repeatMode, id);
- WidgetProvider.attachIntents(ctx, rv, id);
+ WidgetProvider.attachIntents(ctx, rv, id, songLink, albumLink, artistLink);
mgr.updateAppWidget(id, rv);
}
}
@@ -56,7 +61,7 @@ public final class WidgetUpdateManager {
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
for (int id : ids) {
android.widget.RemoteViews rv = chooseBuild(ctx, id);
- WidgetProvider.attachIntents(ctx, rv, id);
+ WidgetProvider.attachIntents(ctx, rv, id, null, null, null);
mgr.updateAppWidget(id, rv);
}
}
@@ -70,7 +75,10 @@ public final class WidgetUpdateManager {
boolean shuffleEnabled,
int repeatMode,
long positionMs,
- long durationMs) {
+ long durationMs,
+ String songLink,
+ String albumLink,
+ String artistLink) {
final Context appCtx = ctx.getApplicationContext();
final String t = TextUtils.isEmpty(title) ? appCtx.getString(R.string.widget_not_playing) : title;
final String a = TextUtils.isEmpty(artist) ? appCtx.getString(R.string.widget_placeholder_subtitle) : artist;
@@ -79,6 +87,9 @@ public final class WidgetUpdateManager {
final boolean sh = shuffleEnabled;
final int rep = repeatMode;
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
+ final String songLinkFinal = songLink;
+ final String albumLinkFinal = albumLink;
+ final String artistLinkFinal = artistLink;
if (!TextUtils.isEmpty(coverArtId)) {
CustomGlideRequest.loadAlbumArtBitmap(
@@ -93,7 +104,7 @@ public final class WidgetUpdateManager {
for (int id : ids) {
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, resource, p,
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
- WidgetProvider.attachIntents(appCtx, rv, id);
+ WidgetProvider.attachIntents(appCtx, rv, id, songLinkFinal, albumLinkFinal, artistLinkFinal);
mgr.updateAppWidget(id, rv);
}
}
@@ -105,7 +116,7 @@ public final class WidgetUpdateManager {
for (int id : ids) {
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p,
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
- WidgetProvider.attachIntents(appCtx, rv, id);
+ WidgetProvider.attachIntents(appCtx, rv, id, songLinkFinal, albumLinkFinal, artistLinkFinal);
mgr.updateAppWidget(id, rv);
}
}
@@ -117,7 +128,7 @@ public final class WidgetUpdateManager {
for (int id : ids) {
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p,
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
- WidgetProvider.attachIntents(appCtx, rv, id);
+ WidgetProvider.attachIntents(appCtx, rv, id, songLinkFinal, albumLinkFinal, artistLinkFinal);
mgr.updateAppWidget(id, rv);
}
}
@@ -133,6 +144,7 @@ public final class WidgetUpdateManager {
MediaController c = future.get();
androidx.media3.common.MediaItem mi = c.getCurrentMediaItem();
String title = null, artist = null, album = null, coverId = null;
+ String songLink = null, albumLink = null, artistLink = null;
if (mi != null && mi.mediaMetadata != null) {
if (mi.mediaMetadata.title != null) title = mi.mediaMetadata.title.toString();
if (mi.mediaMetadata.artist != null)
@@ -140,10 +152,26 @@ public final class WidgetUpdateManager {
if (mi.mediaMetadata.albumTitle != null)
album = mi.mediaMetadata.albumTitle.toString();
if (mi.mediaMetadata.extras != null) {
+ Bundle extras = mi.mediaMetadata.extras;
if (title == null) title = mi.mediaMetadata.extras.getString("title");
if (artist == null) artist = mi.mediaMetadata.extras.getString("artist");
if (album == null) album = mi.mediaMetadata.extras.getString("album");
- coverId = mi.mediaMetadata.extras.getString("coverArtId");
+ coverId = extras.getString("coverArtId");
+
+ songLink = extras.getString("assetLinkSong");
+ if (songLink == null) {
+ songLink = AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, extras.getString("id"));
+ }
+
+ albumLink = extras.getString("assetLinkAlbum");
+ if (albumLink == null) {
+ albumLink = AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, extras.getString("albumId"));
+ }
+
+ artistLink = extras.getString("assetLinkArtist");
+ if (artistLink == null) {
+ artistLink = AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras.getString("artistId"));
+ }
}
}
long position = c.getCurrentPosition();
@@ -159,7 +187,10 @@ public final class WidgetUpdateManager {
c.getShuffleModeEnabled(),
c.getRepeatMode(),
position,
- duration);
+ duration,
+ songLink,
+ albumLink,
+ artistLink);
c.release();
} catch (ExecutionException | InterruptedException ignored) {
}
diff --git a/app/src/main/res/drawable/ic_link.xml b/app/src/main/res/drawable/ic_link.xml
new file mode 100644
index 00000000..5592db28
--- /dev/null
+++ b/app/src/main/res/drawable/ic_link.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/layout/bottom_sheet_song_dialog.xml b/app/src/main/res/layout/bottom_sheet_song_dialog.xml
index 2105f941..7cf98440 100644
--- a/app/src/main/res/layout/bottom_sheet_song_dialog.xml
+++ b/app/src/main/res/layout/bottom_sheet_song_dialog.xml
@@ -68,6 +68,14 @@
+
+
-
\ No newline at end of file
+
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 99e2c90f..29747587 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
@@ -57,6 +57,17 @@
+
+
+ app:layout_constraintTop_toBottomOf="@+id/player_asset_link_row" />
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/view_asset_link_row.xml b/app/src/main/res/layout/view_asset_link_row.xml
new file mode 100644
index 00000000..7060db54
--- /dev/null
+++ b/app/src/main/res/layout/view_asset_link_row.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
new file mode 100644
index 00000000..c29aa81f
--- /dev/null
+++ b/app/src/main/res/values/ids.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2b8e025a..01f53610 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -410,6 +410,22 @@
Update share
Expiration date: %1$s
Sharing is not supported or not enabled
+ Tempo asset link
+ Song UID
+ Album UID
+ Artist UID
+ Playlist UID
+ Genre UID
+ Year UID
+ Asset UID
+ Unsupported asset link
+ Song could not be opened
+ Album could not be opened
+ Artist could not be opened
+ Playlist could not be opened
+ %1$s • %2$s
+ Copied %1$s to clipboard
+ Asset link: %1$s
Description
Expiration date
Cancel
diff --git a/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt
index 27214f90..28e6c561 100644
--- a/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt
+++ b/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt
@@ -20,6 +20,7 @@ import androidx.media3.session.*
import androidx.media3.session.MediaSession.ControllerInfo
import com.cappielloantonio.tempo.R
import com.cappielloantonio.tempo.ui.activity.MainActivity
+import com.cappielloantonio.tempo.util.AssetLinkUtil
import com.cappielloantonio.tempo.util.Constants
import com.cappielloantonio.tempo.util.DownloadUtil
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
@@ -421,7 +422,14 @@ class MediaService : MediaLibraryService() {
?: mi?.mediaMetadata?.extras?.getString("artist")
val album = mi?.mediaMetadata?.albumTitle?.toString()
?: mi?.mediaMetadata?.extras?.getString("album")
- val coverId = mi?.mediaMetadata?.extras?.getString("coverArtId")
+ val extras = mi?.mediaMetadata?.extras
+ val coverId = extras?.getString("coverArtId")
+ val songLink = extras?.getString("assetLinkSong")
+ ?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, extras?.getString("id"))
+ val albumLink = extras?.getString("assetLinkAlbum")
+ ?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, extras?.getString("albumId"))
+ val artistLink = extras?.getString("assetLinkArtist")
+ ?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId"))
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
WidgetUpdateManager.updateFromState(
@@ -434,7 +442,10 @@ class MediaService : MediaLibraryService() {
player.shuffleModeEnabled,
player.repeatMode,
position,
- duration
+ duration,
+ songLink,
+ albumLink,
+ artistLink
)
}
diff --git a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt
index 51292761..82675ba1 100644
--- a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt
+++ b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt
@@ -22,6 +22,7 @@ import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession.ControllerInfo
import com.cappielloantonio.tempo.repository.AutomotiveRepository
import com.cappielloantonio.tempo.ui.activity.MainActivity
+import com.cappielloantonio.tempo.util.AssetLinkUtil
import com.cappielloantonio.tempo.util.Constants
import com.cappielloantonio.tempo.util.DownloadUtil
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
@@ -262,7 +263,14 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
?: mi?.mediaMetadata?.extras?.getString("artist")
val album = mi?.mediaMetadata?.albumTitle?.toString()
?: mi?.mediaMetadata?.extras?.getString("album")
- val coverId = mi?.mediaMetadata?.extras?.getString("coverArtId")
+ val extras = mi?.mediaMetadata?.extras
+ val coverId = extras?.getString("coverArtId")
+ val songLink = extras?.getString("assetLinkSong")
+ ?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, extras?.getString("id"))
+ val albumLink = extras?.getString("assetLinkAlbum")
+ ?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, extras?.getString("albumId"))
+ val artistLink = extras?.getString("assetLinkArtist")
+ ?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId"))
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
WidgetUpdateManager.updateFromState(
@@ -275,7 +283,10 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
player.shuffleModeEnabled,
player.repeatMode,
position,
- duration
+ duration,
+ songLink,
+ albumLink,
+ artistLink
)
}