feat: Make all objects in Tempo references for quick access

This commit is contained in:
le-firehawk 2025-10-09 00:08:10 +10:30
parent 89fa38f5a0
commit 6c637dcbcb
21 changed files with 1030 additions and 35 deletions

View file

@ -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);
}
});
}
}

View file

@ -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;
}
}
}

View file

@ -74,6 +74,12 @@ public class MappingUtil {
bundle.putInt("originalWidth", media.getOriginalWidth() != null ? media.getOriginalWidth() : 0);
bundle.putInt("originalHeight", media.getOriginalHeight() != null ? media.getOriginalHeight() : 0);
bundle.putString("uri", uri.toString());
bundle.putString("assetLinkSong", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, media.getId()));
bundle.putString("assetLinkAlbum", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, media.getAlbumId()));
bundle.putString("assetLinkArtist", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, media.getArtistId()));
bundle.putString("assetLinkGenre", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_GENRE, media.getGenre()));
Integer year = media.getYear();
bundle.putString("assetLinkYear", year != null && year != 0 ? AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_YEAR, String.valueOf(year)) : null);
return new MediaItem.Builder()
.setMediaId(media.getId())