mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
feat: Make all objects in Tempo references for quick access (#158)
This commit is contained in:
commit
9c088a7e88
22 changed files with 1119 additions and 53 deletions
|
|
@ -42,6 +42,16 @@
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="asset"
|
||||||
|
android:scheme="tempo" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,33 @@ public class PlaylistRepository {
|
||||||
return listLivePlaylistSongs;
|
return listLivePlaylistSongs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<Playlist> getPlaylist(String id) {
|
||||||
|
MutableLiveData<Playlist> playlistLiveData = new MutableLiveData<>();
|
||||||
|
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getPlaylistClient()
|
||||||
|
.getPlaylist(id)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> 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<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
playlistLiveData.setValue(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return playlistLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
public void addSongToPlaylist(String playlistId, ArrayList<String> songsId) {
|
public void addSongToPlaylist(String playlistId, ArrayList<String> songsId) {
|
||||||
if (songsId.isEmpty()) {
|
if (songsId.isEmpty()) {
|
||||||
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_all_skipped), Toast.LENGTH_SHORT).show();
|
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_all_skipped), Toast.LENGTH_SHORT).show();
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ import com.cappielloantonio.tempo.ui.dialog.ConnectionAlertDialog;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.GithubTempoUpdateDialog;
|
import com.cappielloantonio.tempo.ui.dialog.GithubTempoUpdateDialog;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.ServerUnreachableDialog;
|
import com.cappielloantonio.tempo.ui.dialog.ServerUnreachableDialog;
|
||||||
import com.cappielloantonio.tempo.ui.fragment.PlayerBottomSheetFragment;
|
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.Constants;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
|
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
|
||||||
|
|
@ -60,6 +62,8 @@ public class MainActivity extends BaseActivity {
|
||||||
private BottomNavigationView bottomNavigationView;
|
private BottomNavigationView bottomNavigationView;
|
||||||
public NavController navController;
|
public NavController navController;
|
||||||
private BottomSheetBehavior bottomSheetBehavior;
|
private BottomSheetBehavior bottomSheetBehavior;
|
||||||
|
private AssetLinkNavigator assetLinkNavigator;
|
||||||
|
private AssetLinkUtil.AssetLink pendingAssetLink;
|
||||||
|
|
||||||
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
|
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
|
||||||
private Intent pendingDownloadPlaybackIntent;
|
private Intent pendingDownloadPlaybackIntent;
|
||||||
|
|
@ -76,6 +80,7 @@ public class MainActivity extends BaseActivity {
|
||||||
setContentView(view);
|
setContentView(view);
|
||||||
|
|
||||||
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
|
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
|
||||||
|
assetLinkNavigator = new AssetLinkNavigator(this);
|
||||||
|
|
||||||
connectivityStatusBroadcastReceiver = new ConnectivityStatusBroadcastReceiver(this);
|
connectivityStatusBroadcastReceiver = new ConnectivityStatusBroadcastReceiver(this);
|
||||||
connectivityStatusReceiverManager(true);
|
connectivityStatusReceiverManager(true);
|
||||||
|
|
@ -311,6 +316,24 @@ public class MainActivity extends BaseActivity {
|
||||||
public void goFromLogin() {
|
public void goFromLogin() {
|
||||||
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
||||||
goToHome();
|
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() {
|
public void quit() {
|
||||||
|
|
@ -443,6 +466,7 @@ public class MainActivity extends BaseActivity {
|
||||||
|| intent.hasExtra(Constants.EXTRA_DOWNLOAD_URI)) {
|
|| intent.hasExtra(Constants.EXTRA_DOWNLOAD_URI)) {
|
||||||
pendingDownloadPlaybackIntent = new Intent(intent);
|
pendingDownloadPlaybackIntent = new Intent(intent);
|
||||||
}
|
}
|
||||||
|
handleAssetLinkIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void consumePendingPlaybackIntent() {
|
private void consumePendingPlaybackIntent() {
|
||||||
|
|
@ -452,6 +476,35 @@ public class MainActivity extends BaseActivity {
|
||||||
playDownloadedMedia(intent);
|
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) {
|
private void playDownloadedMedia(Intent intent) {
|
||||||
String uriString = intent.getStringExtra(Constants.EXTRA_DOWNLOAD_URI);
|
String uriString = intent.getStringExtra(Constants.EXTRA_DOWNLOAD_URI);
|
||||||
if (TextUtils.isEmpty(uriString)) {
|
if (TextUtils.isEmpty(uriString)) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.dialog;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
@ -10,6 +11,7 @@ import androidx.media3.common.MediaMetadata;
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.DialogTrackInfoBinding;
|
import com.cappielloantonio.tempo.databinding.DialogTrackInfoBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
|
import com.cappielloantonio.tempo.util.AssetLinkUtil;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
@ -21,6 +23,11 @@ public class TrackInfoDialog extends DialogFragment {
|
||||||
private DialogTrackInfoBinding bind;
|
private DialogTrackInfoBinding bind;
|
||||||
|
|
||||||
private final MediaMetadata mediaMetadata;
|
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) {
|
public TrackInfoDialog(MediaMetadata mediaMetadata) {
|
||||||
this.mediaMetadata = mediaMetadata;
|
this.mediaMetadata = mediaMetadata;
|
||||||
|
|
@ -52,6 +59,8 @@ public class TrackInfoDialog extends DialogFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTrackInfo() {
|
private void setTrackInfo() {
|
||||||
|
genreLink = null;
|
||||||
|
yearLink = null;
|
||||||
bind.trakTitleInfoTextView.setText(mediaMetadata.title);
|
bind.trakTitleInfoTextView.setText(mediaMetadata.title);
|
||||||
bind.trakArtistInfoTextView.setText(
|
bind.trakArtistInfoTextView.setText(
|
||||||
mediaMetadata.artist != null
|
mediaMetadata.artist != null
|
||||||
|
|
@ -61,17 +70,41 @@ public class TrackInfoDialog extends DialogFragment {
|
||||||
: "");
|
: "");
|
||||||
|
|
||||||
if (mediaMetadata.extras != null) {
|
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
|
CustomGlideRequest.Builder
|
||||||
.from(requireContext(), mediaMetadata.extras.getString("coverArtId", ""), CustomGlideRequest.ResourceType.Song)
|
.from(requireContext(), mediaMetadata.extras.getString("coverArtId", ""), CustomGlideRequest.ResourceType.Song)
|
||||||
.build()
|
.build()
|
||||||
.into(bind.trackCoverInfoImageView);
|
.into(bind.trackCoverInfoImageView);
|
||||||
|
|
||||||
bind.titleValueSector.setText(mediaMetadata.extras.getString("title", getString(R.string.label_placeholder)));
|
bindAssetLink(bind.trackCoverInfoImageView, albumLink != null ? albumLink : songLink);
|
||||||
bind.albumValueSector.setText(mediaMetadata.extras.getString("album", getString(R.string.label_placeholder)));
|
bindAssetLink(bind.trakTitleInfoTextView, songLink);
|
||||||
bind.artistValueSector.setText(mediaMetadata.extras.getString("artist", getString(R.string.label_placeholder)));
|
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.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.yearValueSector.setText(yearValue != 0 ? String.valueOf(yearValue) : getString(R.string.label_placeholder));
|
||||||
bind.genreValueSector.setText(mediaMetadata.extras.getString("genre", 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.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.contentTypeValueSector.setText(mediaMetadata.extras.getString("contentType", getString(R.string.label_placeholder)));
|
||||||
bind.suffixValueSector.setText(mediaMetadata.extras.getString("suffix", 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.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.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));
|
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);
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
|
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
|
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
|
||||||
|
import com.cappielloantonio.tempo.util.AssetLinkUtil;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
|
|
@ -177,8 +178,35 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||||
|
|
||||||
bind.albumNameLabel.setText(album.getName());
|
bind.albumNameLabel.setText(album.getName());
|
||||||
bind.albumArtistLabel.setText(album.getArtist());
|
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.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));
|
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()) {
|
if (album.getGenre() != null && !album.getGenre().isEmpty()) {
|
||||||
bind.albumGenresTextview.setText(album.getGenre());
|
bind.albumGenresTextview.setText(album.getGenre());
|
||||||
|
|
@ -347,4 +375,23 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||||
private void setMediaBrowserListenableFuture() {
|
private void setMediaBrowserListenableFuture() {
|
||||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -195,6 +195,7 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
|
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
|
||||||
if (mediaBrowser.getMediaMetadata().extras != null) {
|
if (mediaBrowser.getMediaMetadata().extras != null) {
|
||||||
switch (mediaBrowser.getMediaMetadata().extras.getString("type", Constants.MEDIA_TYPE_MUSIC)) {
|
switch (mediaBrowser.getMediaMetadata().extras.getString("type", Constants.MEDIA_TYPE_MUSIC)) {
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,10 @@ import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RatingBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.ToggleButton;
|
import android.widget.ToggleButton;
|
||||||
import android.widget.RatingBar;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
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.RatingDialog;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.TrackInfoDialog;
|
import com.cappielloantonio.tempo.ui.dialog.TrackInfoDialog;
|
||||||
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
|
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
|
||||||
|
import com.cappielloantonio.tempo.util.AssetLinkUtil;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
||||||
import com.cappielloantonio.tempo.viewmodel.RatingViewModel;
|
import com.cappielloantonio.tempo.viewmodel.RatingViewModel;
|
||||||
import com.google.android.material.chip.Chip;
|
import com.google.android.material.chip.Chip;
|
||||||
|
import com.google.android.material.chip.ChipGroup;
|
||||||
import com.google.android.material.elevation.SurfaceColors;
|
import com.google.android.material.elevation.SurfaceColors;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
@ -76,6 +79,10 @@ public class PlayerControllerFragment extends Fragment {
|
||||||
private ImageButton playerTrackInfo;
|
private ImageButton playerTrackInfo;
|
||||||
private LinearLayout ratingContainer;
|
private LinearLayout ratingContainer;
|
||||||
private ImageButton equalizerButton;
|
private ImageButton equalizerButton;
|
||||||
|
private ChipGroup assetLinkChipGroup;
|
||||||
|
private Chip playerSongLinkChip;
|
||||||
|
private Chip playerAlbumLinkChip;
|
||||||
|
private Chip playerArtistLinkChip;
|
||||||
|
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||||
|
|
@ -139,6 +146,10 @@ public class PlayerControllerFragment extends Fragment {
|
||||||
songRatingBar = bind.getRoot().findViewById(R.id.song_rating_bar);
|
songRatingBar = bind.getRoot().findViewById(R.id.song_rating_bar);
|
||||||
ratingContainer = bind.getRoot().findViewById(R.id.rating_container);
|
ratingContainer = bind.getRoot().findViewById(R.id.rating_container);
|
||||||
equalizerButton = bind.getRoot().findViewById(R.id.player_open_equalizer_button);
|
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();
|
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
|
|| mediaMetadata.extras != null && Objects.equals(mediaMetadata.extras.getString("type"), Constants.MEDIA_TYPE_RADIO) && mediaMetadata.extras.getString("uri") != null
|
||||||
? View.VISIBLE
|
? View.VISIBLE
|
||||||
: View.GONE);
|
: View.GONE);
|
||||||
|
|
||||||
|
updateAssetLinkChips(mediaMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setMediaInfo(MediaMetadata 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) {
|
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
|
||||||
initPlaybackSpeedButton(mediaBrowser);
|
initPlaybackSpeedButton(mediaBrowser);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
|
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
|
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
|
||||||
|
import com.cappielloantonio.tempo.util.AssetLinkUtil;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.ExternalAudioReader;
|
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.HomeViewModel;
|
||||||
import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel;
|
import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel;
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
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 com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
@ -56,6 +59,13 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||||
|
|
||||||
private TextView downloadButton;
|
private TextView downloadButton;
|
||||||
private TextView removeButton;
|
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<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
|
|
@ -109,6 +119,11 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||||
TextView artistSong = view.findViewById(R.id.song_artist_text_view);
|
TextView artistSong = view.findViewById(R.id.song_artist_text_view);
|
||||||
artistSong.setText(songBottomSheetViewModel.getSong().getArtist());
|
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);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(songBottomSheetViewModel.getSong().getStarred() != null);
|
favoriteToggle.setChecked(songBottomSheetViewModel.getSong().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
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() {
|
private void initializeMediaBrowser() {
|
||||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<Child> liveData = songRepository.getSong(id);
|
||||||
|
Observer<Child> observer = new Observer<Child>() {
|
||||||
|
@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<AlbumID3> liveData = albumRepository.getAlbum(id);
|
||||||
|
Observer<AlbumID3> observer = new Observer<AlbumID3>() {
|
||||||
|
@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<ArtistID3> liveData = artistRepository.getArtist(id);
|
||||||
|
Observer<ArtistID3> observer = new Observer<ArtistID3>() {
|
||||||
|
@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<Playlist> liveData = playlistRepository.getPlaylist(id);
|
||||||
|
Observer<Playlist> observer = new Observer<Playlist>() {
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,9 @@ import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
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.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
|
@ -102,35 +105,76 @@ public class ExternalAudioWriter {
|
||||||
ExternalDownloadMetadataStore.remove(metadataKey);
|
ExternalDownloadMetadataStore.remove(metadataKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String scheme = mediaUri.getScheme();
|
|
||||||
if (scheme == null || (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https"))) {
|
String scheme = mediaUri.getScheme() != null ? mediaUri.getScheme().toLowerCase(Locale.ROOT) : "";
|
||||||
notifyFailure(context, "Unsupported media URI.");
|
|
||||||
ExternalDownloadMetadataStore.remove(metadataKey);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpURLConnection connection = null;
|
HttpURLConnection connection = null;
|
||||||
|
DocumentFile sourceDocument = null;
|
||||||
|
File sourceFile = null;
|
||||||
|
long remoteLength = -1;
|
||||||
|
String mimeType = null;
|
||||||
DocumentFile targetFile = 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();
|
try {
|
||||||
if (responseCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
|
if (scheme.equals("http") || scheme.equals("https")) {
|
||||||
notifyFailure(context, "Server returned " + responseCode);
|
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);
|
ExternalDownloadMetadataStore.remove(metadataKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String mimeType = connection.getContentType();
|
|
||||||
if (mimeType == null || mimeType.isEmpty()) {
|
if (mimeType == null || mimeType.isEmpty()) {
|
||||||
mimeType = "application/octet-stream";
|
mimeType = "application/octet-stream";
|
||||||
}
|
}
|
||||||
|
|
||||||
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
|
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()) {
|
if (extension == null || extension.isEmpty()) {
|
||||||
String suffix = child.getSuffix();
|
String suffix = child.getSuffix();
|
||||||
if (suffix != null && !suffix.isEmpty()) {
|
if (suffix != null && !suffix.isEmpty()) {
|
||||||
|
|
@ -146,7 +190,6 @@ public class ExternalAudioWriter {
|
||||||
String fileName = sanitized + "." + extension;
|
String fileName = sanitized + "." + extension;
|
||||||
|
|
||||||
DocumentFile existingFile = findFile(directory, fileName);
|
DocumentFile existingFile = findFile(directory, fileName);
|
||||||
long remoteLength = connection.getContentLengthLong();
|
|
||||||
Long recordedSize = ExternalDownloadMetadataStore.getSize(metadataKey);
|
Long recordedSize = ExternalDownloadMetadataStore.getSize(metadataKey);
|
||||||
if (existingFile != null && existingFile.exists()) {
|
if (existingFile != null && existingFile.exists()) {
|
||||||
long localLength = existingFile.length();
|
long localLength = existingFile.length();
|
||||||
|
|
@ -175,7 +218,7 @@ public class ExternalAudioWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri targetUri = targetFile.getUri();
|
Uri targetUri = targetFile.getUri();
|
||||||
try (InputStream in = connection.getInputStream();
|
try (InputStream in = openInputStream(context, mediaUri, scheme, connection, sourceFile);
|
||||||
OutputStream out = context.getContentResolver().openOutputStream(targetUri)) {
|
OutputStream out = context.getContentResolver().openOutputStream(targetUri)) {
|
||||||
if (out == null) {
|
if (out == null) {
|
||||||
notifyFailure(context, "Cannot open output stream.");
|
notifyFailure(context, "Cannot open output stream.");
|
||||||
|
|
@ -319,4 +362,32 @@ public class ExternalAudioWriter {
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,12 @@ public class MappingUtil {
|
||||||
bundle.putInt("originalWidth", media.getOriginalWidth() != null ? media.getOriginalWidth() : 0);
|
bundle.putInt("originalWidth", media.getOriginalWidth() != null ? media.getOriginalWidth() : 0);
|
||||||
bundle.putInt("originalHeight", media.getOriginalHeight() != null ? media.getOriginalHeight() : 0);
|
bundle.putInt("originalHeight", media.getOriginalHeight() != null ? media.getOriginalHeight() : 0);
|
||||||
bundle.putString("uri", uri.toString());
|
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()
|
return new MediaItem.Builder()
|
||||||
.setMediaId(media.getId())
|
.setMediaId(media.getId())
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,20 @@ import android.appwidget.AppWidgetManager;
|
||||||
import android.appwidget.AppWidgetProvider;
|
import android.appwidget.AppWidgetProvider;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.widget.RemoteViews;
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
|
|
||||||
import android.app.TaskStackBuilder;
|
import android.app.TaskStackBuilder;
|
||||||
import android.app.PendingIntent;
|
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public class WidgetProvider extends AppWidgetProvider {
|
public class WidgetProvider extends AppWidgetProvider {
|
||||||
private static final String TAG = "TempoWidget";
|
private static final String TAG = "TempoWidget";
|
||||||
public static final String ACT_PLAY_PAUSE = "tempo.widget.PLAY_PAUSE";
|
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) {
|
public void onUpdate(Context ctx, AppWidgetManager mgr, int[] ids) {
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
RemoteViews rv = WidgetUpdateManager.chooseBuild(ctx, id);
|
RemoteViews rv = WidgetUpdateManager.chooseBuild(ctx, id);
|
||||||
attachIntents(ctx, rv, id);
|
attachIntents(ctx, rv, id, null, null, null);
|
||||||
mgr.updateAppWidget(id, rv);
|
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) {
|
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, android.os.Bundle newOptions) {
|
||||||
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
|
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
|
||||||
RemoteViews rv = WidgetUpdateManager.chooseBuild(context, appWidgetId);
|
RemoteViews rv = WidgetUpdateManager.chooseBuild(context, appWidgetId);
|
||||||
attachIntents(context, rv, appWidgetId);
|
attachIntents(context, rv, appWidgetId, null, null, null);
|
||||||
appWidgetManager.updateAppWidget(appWidgetId, rv);
|
appWidgetManager.updateAppWidget(appWidgetId, rv);
|
||||||
WidgetUpdateManager.refreshFromController(context);
|
WidgetUpdateManager.refreshFromController(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void attachIntents(Context ctx, RemoteViews rv) {
|
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) {
|
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(
|
PendingIntent playPause = PendingIntent.getBroadcast(
|
||||||
ctx,
|
ctx,
|
||||||
requestCodeBase + 0,
|
requestCodeBase + 0,
|
||||||
|
|
@ -97,9 +107,31 @@ public class WidgetProvider extends AppWidgetProvider {
|
||||||
rv.setOnClickPendingIntent(R.id.btn_shuffle, shuffle);
|
rv.setOnClickPendingIntent(R.id.btn_shuffle, shuffle);
|
||||||
rv.setOnClickPendingIntent(R.id.btn_repeat, repeat);
|
rv.setOnClickPendingIntent(R.id.btn_repeat, repeat);
|
||||||
|
|
||||||
PendingIntent launch = TaskStackBuilder.create(ctx)
|
PendingIntent launch = buildMainActivityPendingIntent(ctx, requestCodeBase + 10, null);
|
||||||
.addNextIntentWithParentStack(new Intent(ctx, MainActivity.class))
|
|
||||||
.getPendingIntent(requestCodeBase + 10, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
rv.setOnClickPendingIntent(R.id.root, launch);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ import android.appwidget.AppWidgetManager;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.graphics.drawable.Drawable;
|
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.target.CustomTarget;
|
||||||
import com.bumptech.glide.request.transition.Transition;
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
|
|
@ -17,6 +18,7 @@ import androidx.media3.session.MediaController;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
|
import com.cappielloantonio.tempo.util.AssetLinkUtil;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
@ -34,7 +36,10 @@ public final class WidgetUpdateManager {
|
||||||
boolean shuffleEnabled,
|
boolean shuffleEnabled,
|
||||||
int repeatMode,
|
int repeatMode,
|
||||||
long positionMs,
|
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(title)) title = ctx.getString(R.string.widget_not_playing);
|
||||||
if (TextUtils.isEmpty(artist)) artist = ctx.getString(R.string.widget_placeholder_subtitle);
|
if (TextUtils.isEmpty(artist)) artist = ctx.getString(R.string.widget_placeholder_subtitle);
|
||||||
if (TextUtils.isEmpty(album)) album = "";
|
if (TextUtils.isEmpty(album)) album = "";
|
||||||
|
|
@ -46,7 +51,7 @@ public final class WidgetUpdateManager {
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, album, art, playing,
|
android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, album, art, playing,
|
||||||
timing.elapsedText, timing.totalText, timing.progress, shuffleEnabled, repeatMode, id);
|
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);
|
mgr.updateAppWidget(id, rv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,7 +61,7 @@ public final class WidgetUpdateManager {
|
||||||
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
android.widget.RemoteViews rv = chooseBuild(ctx, id);
|
android.widget.RemoteViews rv = chooseBuild(ctx, id);
|
||||||
WidgetProvider.attachIntents(ctx, rv, id);
|
WidgetProvider.attachIntents(ctx, rv, id, null, null, null);
|
||||||
mgr.updateAppWidget(id, rv);
|
mgr.updateAppWidget(id, rv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +75,10 @@ public final class WidgetUpdateManager {
|
||||||
boolean shuffleEnabled,
|
boolean shuffleEnabled,
|
||||||
int repeatMode,
|
int repeatMode,
|
||||||
long positionMs,
|
long positionMs,
|
||||||
long durationMs) {
|
long durationMs,
|
||||||
|
String songLink,
|
||||||
|
String albumLink,
|
||||||
|
String artistLink) {
|
||||||
final Context appCtx = ctx.getApplicationContext();
|
final Context appCtx = ctx.getApplicationContext();
|
||||||
final String t = TextUtils.isEmpty(title) ? appCtx.getString(R.string.widget_not_playing) : title;
|
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;
|
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 boolean sh = shuffleEnabled;
|
||||||
final int rep = repeatMode;
|
final int rep = repeatMode;
|
||||||
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
|
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
|
||||||
|
final String songLinkFinal = songLink;
|
||||||
|
final String albumLinkFinal = albumLink;
|
||||||
|
final String artistLinkFinal = artistLink;
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(coverArtId)) {
|
if (!TextUtils.isEmpty(coverArtId)) {
|
||||||
CustomGlideRequest.loadAlbumArtBitmap(
|
CustomGlideRequest.loadAlbumArtBitmap(
|
||||||
|
|
@ -93,7 +104,7 @@ public final class WidgetUpdateManager {
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, resource, p,
|
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, resource, p,
|
||||||
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
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);
|
mgr.updateAppWidget(id, rv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +116,7 @@ public final class WidgetUpdateManager {
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p,
|
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p,
|
||||||
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
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);
|
mgr.updateAppWidget(id, rv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +128,7 @@ public final class WidgetUpdateManager {
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p,
|
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p,
|
||||||
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
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);
|
mgr.updateAppWidget(id, rv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -133,6 +144,7 @@ public final class WidgetUpdateManager {
|
||||||
MediaController c = future.get();
|
MediaController c = future.get();
|
||||||
androidx.media3.common.MediaItem mi = c.getCurrentMediaItem();
|
androidx.media3.common.MediaItem mi = c.getCurrentMediaItem();
|
||||||
String title = null, artist = null, album = null, coverId = null;
|
String title = null, artist = null, album = null, coverId = null;
|
||||||
|
String songLink = null, albumLink = null, artistLink = null;
|
||||||
if (mi != null && mi.mediaMetadata != null) {
|
if (mi != null && mi.mediaMetadata != null) {
|
||||||
if (mi.mediaMetadata.title != null) title = mi.mediaMetadata.title.toString();
|
if (mi.mediaMetadata.title != null) title = mi.mediaMetadata.title.toString();
|
||||||
if (mi.mediaMetadata.artist != null)
|
if (mi.mediaMetadata.artist != null)
|
||||||
|
|
@ -140,10 +152,26 @@ public final class WidgetUpdateManager {
|
||||||
if (mi.mediaMetadata.albumTitle != null)
|
if (mi.mediaMetadata.albumTitle != null)
|
||||||
album = mi.mediaMetadata.albumTitle.toString();
|
album = mi.mediaMetadata.albumTitle.toString();
|
||||||
if (mi.mediaMetadata.extras != null) {
|
if (mi.mediaMetadata.extras != null) {
|
||||||
|
Bundle extras = mi.mediaMetadata.extras;
|
||||||
if (title == null) title = mi.mediaMetadata.extras.getString("title");
|
if (title == null) title = mi.mediaMetadata.extras.getString("title");
|
||||||
if (artist == null) artist = mi.mediaMetadata.extras.getString("artist");
|
if (artist == null) artist = mi.mediaMetadata.extras.getString("artist");
|
||||||
if (album == null) album = mi.mediaMetadata.extras.getString("album");
|
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();
|
long position = c.getCurrentPosition();
|
||||||
|
|
@ -159,7 +187,10 @@ public final class WidgetUpdateManager {
|
||||||
c.getShuffleModeEnabled(),
|
c.getShuffleModeEnabled(),
|
||||||
c.getRepeatMode(),
|
c.getRepeatMode(),
|
||||||
position,
|
position,
|
||||||
duration);
|
duration,
|
||||||
|
songLink,
|
||||||
|
albumLink,
|
||||||
|
artistLink);
|
||||||
c.release();
|
c.release();
|
||||||
} catch (ExecutionException | InterruptedException ignored) {
|
} catch (ExecutionException | InterruptedException ignored) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
app/src/main/res/drawable/ic_link.xml
Normal file
10
app/src/main/res/drawable/ic_link.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorOnSurfaceVariant"
|
||||||
|
android:pathData="M3.9,12c0,1.71 1.39,3.1 3.1,3.1h3v1.8h-3c-2.7,0 -4.9,-2.2 -4.9,-4.9s2.2,-4.9 4.9,-4.9h3v1.8h-3c-1.71,0 -3.1,1.39 -3.1,3.1zM7,13h10v-2H7v2zM17,6.9h-3v-1.8h3c2.7,0 4.9,2.2 4.9,4.9s-2.2,4.9 -4.9,4.9h-3v-1.8h3c1.71,0 3.1,-1.39 3.1,-3.1s-1.39,-3.1 -3.1,-3.1z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -68,6 +68,14 @@
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/song_asset_link_row"
|
||||||
|
layout="@layout/view_asset_link_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:paddingEnd="12dp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/option_linear_layout"
|
android:id="@+id/option_linear_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,17 @@
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/player_asset_link_row"
|
||||||
|
layout="@layout/view_asset_link_row"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/player_media_quality_sector" />
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/player_media_cover_view_pager"
|
android:id="@+id/player_media_cover_view_pager"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
@ -66,7 +77,7 @@
|
||||||
app:layout_constraintBottom_toTopOf="@id/guideline"
|
app:layout_constraintBottom_toTopOf="@id/guideline"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_media_quality_sector" />
|
app:layout_constraintTop_toBottomOf="@+id/player_asset_link_row" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Guideline
|
<androidx.constraintlayout.widget.Guideline
|
||||||
android:id="@+id/guideline"
|
android:id="@+id/guideline"
|
||||||
|
|
|
||||||
55
app/src/main/res/layout/view_asset_link_row.xml
Normal file
55
app/src/main/res/layout/view_asset_link_row.xml
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.chip.ChipGroup xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/asset_link_chip_group"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
app:singleLine="true"
|
||||||
|
app:selectionRequired="false"
|
||||||
|
app:singleSelection="false">
|
||||||
|
|
||||||
|
<com.google.android.material.chip.Chip
|
||||||
|
android:id="@+id/asset_link_song_chip"
|
||||||
|
style="@style/Widget.Material3.Chip.Assist"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checkable="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text=""
|
||||||
|
app:chipIcon="@drawable/ic_link"
|
||||||
|
app:chipIconTint="?attr/colorOnSurfaceVariant"
|
||||||
|
app:rippleColor="@color/ripple_material_light" />
|
||||||
|
|
||||||
|
<com.google.android.material.chip.Chip
|
||||||
|
android:id="@+id/asset_link_album_chip"
|
||||||
|
style="@style/Widget.Material3.Chip.Assist"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checkable="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text=""
|
||||||
|
app:chipIcon="@drawable/ic_link"
|
||||||
|
app:chipIconTint="?attr/colorOnSurfaceVariant"
|
||||||
|
app:rippleColor="@color/ripple_material_light" />
|
||||||
|
|
||||||
|
<com.google.android.material.chip.Chip
|
||||||
|
android:id="@+id/asset_link_artist_chip"
|
||||||
|
style="@style/Widget.Material3.Chip.Assist"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checkable="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text=""
|
||||||
|
app:chipIcon="@drawable/ic_link"
|
||||||
|
app:chipIconTint="?attr/colorOnSurfaceVariant"
|
||||||
|
app:rippleColor="@color/ripple_material_light" />
|
||||||
|
</com.google.android.material.chip.ChipGroup>
|
||||||
3
app/src/main/res/values/ids.xml
Normal file
3
app/src/main/res/values/ids.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<item name="tag_link_original_color" type="id" />
|
||||||
|
</resources>
|
||||||
|
|
@ -410,6 +410,22 @@
|
||||||
<string name="share_bottom_sheet_update">Update share</string>
|
<string name="share_bottom_sheet_update">Update share</string>
|
||||||
<string name="share_subtitle_item">Expiration date: %1$s</string>
|
<string name="share_subtitle_item">Expiration date: %1$s</string>
|
||||||
<string name="share_unsupported_error">Sharing is not supported or not enabled</string>
|
<string name="share_unsupported_error">Sharing is not supported or not enabled</string>
|
||||||
|
<string name="asset_link_clipboard_label">Tempo asset link</string>
|
||||||
|
<string name="asset_link_label_song">Song UID</string>
|
||||||
|
<string name="asset_link_label_album">Album UID</string>
|
||||||
|
<string name="asset_link_label_artist">Artist UID</string>
|
||||||
|
<string name="asset_link_label_playlist">Playlist UID</string>
|
||||||
|
<string name="asset_link_label_genre">Genre UID</string>
|
||||||
|
<string name="asset_link_label_year">Year UID</string>
|
||||||
|
<string name="asset_link_label_unknown">Asset UID</string>
|
||||||
|
<string name="asset_link_error_unsupported">Unsupported asset link</string>
|
||||||
|
<string name="asset_link_error_song">Song could not be opened</string>
|
||||||
|
<string name="asset_link_error_album">Album could not be opened</string>
|
||||||
|
<string name="asset_link_error_artist">Artist could not be opened</string>
|
||||||
|
<string name="asset_link_error_playlist">Playlist could not be opened</string>
|
||||||
|
<string name="asset_link_chip_text">%1$s • %2$s</string>
|
||||||
|
<string name="asset_link_copied_toast">Copied %1$s to clipboard</string>
|
||||||
|
<string name="asset_link_debug_toast">Asset link: %1$s</string>
|
||||||
<string name="share_update_dialog_hint_description">Description</string>
|
<string name="share_update_dialog_hint_description">Description</string>
|
||||||
<string name="share_update_dialog_hint_expiration_date">Expiration date</string>
|
<string name="share_update_dialog_hint_expiration_date">Expiration date</string>
|
||||||
<string name="share_update_dialog_negative_button">Cancel</string>
|
<string name="share_update_dialog_negative_button">Cancel</string>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import androidx.media3.session.*
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo
|
import androidx.media3.session.MediaSession.ControllerInfo
|
||||||
import com.cappielloantonio.tempo.R
|
import com.cappielloantonio.tempo.R
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||||
|
import com.cappielloantonio.tempo.util.AssetLinkUtil
|
||||||
import com.cappielloantonio.tempo.util.Constants
|
import com.cappielloantonio.tempo.util.Constants
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil
|
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||||
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
|
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
|
||||||
|
|
@ -421,7 +422,14 @@ class MediaService : MediaLibraryService() {
|
||||||
?: mi?.mediaMetadata?.extras?.getString("artist")
|
?: mi?.mediaMetadata?.extras?.getString("artist")
|
||||||
val album = mi?.mediaMetadata?.albumTitle?.toString()
|
val album = mi?.mediaMetadata?.albumTitle?.toString()
|
||||||
?: mi?.mediaMetadata?.extras?.getString("album")
|
?: 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 position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||||
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||||
WidgetUpdateManager.updateFromState(
|
WidgetUpdateManager.updateFromState(
|
||||||
|
|
@ -434,7 +442,10 @@ class MediaService : MediaLibraryService() {
|
||||||
player.shuffleModeEnabled,
|
player.shuffleModeEnabled,
|
||||||
player.repeatMode,
|
player.repeatMode,
|
||||||
position,
|
position,
|
||||||
duration
|
duration,
|
||||||
|
songLink,
|
||||||
|
albumLink,
|
||||||
|
artistLink
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import androidx.media3.session.MediaLibraryService
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo
|
import androidx.media3.session.MediaSession.ControllerInfo
|
||||||
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||||
|
import com.cappielloantonio.tempo.util.AssetLinkUtil
|
||||||
import com.cappielloantonio.tempo.util.Constants
|
import com.cappielloantonio.tempo.util.Constants
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil
|
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||||
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
|
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
|
||||||
|
|
@ -262,7 +263,14 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||||
?: mi?.mediaMetadata?.extras?.getString("artist")
|
?: mi?.mediaMetadata?.extras?.getString("artist")
|
||||||
val album = mi?.mediaMetadata?.albumTitle?.toString()
|
val album = mi?.mediaMetadata?.albumTitle?.toString()
|
||||||
?: mi?.mediaMetadata?.extras?.getString("album")
|
?: 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 position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||||
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||||
WidgetUpdateManager.updateFromState(
|
WidgetUpdateManager.updateFromState(
|
||||||
|
|
@ -275,7 +283,10 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||||
player.shuffleModeEnabled,
|
player.shuffleModeEnabled,
|
||||||
player.repeatMode,
|
player.repeatMode,
|
||||||
position,
|
position,
|
||||||
duration
|
duration,
|
||||||
|
songLink,
|
||||||
|
albumLink,
|
||||||
|
artistLink
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue