Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d426c08cdd | ||
|
|
972c32b9d8 | ||
|
|
a279e20a49 | ||
|
|
fe60fea928 | ||
|
|
a110faabe3 | ||
|
|
df2bf43492 | ||
|
|
b46fea6890 | ||
|
|
08b6379601 | ||
|
|
3fbadc2521 | ||
|
|
9e78caeda4 | ||
|
|
e072a49288 | ||
|
|
b89e18eebf | ||
|
|
63607794d6 |
15
CHANGELOG.md
15
CHANGELOG.md
@@ -2,10 +2,19 @@
|
|||||||
|
|
||||||
## Pending release...
|
## Pending release...
|
||||||
|
|
||||||
* chore: bringing in media service refactor previously reverted after more testing by @eddyizm in https://github.com/eddyizm/tempus/pull/286
|
* fix: updates starred syncing downloads to user defined directory by @eddyizm in https://github.com/eddyizm/tempus/pull/298
|
||||||
* fix: refactor start queue to put the db writing in the background by @eddyizm in https://github.com/eddyizm/tempus/pull/287
|
* fix: handle empty albums and null mappings by @eddyizm in https://github.com/eddyizm/tempus/pull/301
|
||||||
* Feat: playerqueue fab allows playqueue actions -> saving to playlist, download all, load queue, shuffle, clean queue by @eddyizm in https://github.com/eddyizm/tempus/pull/288
|
* feat: integrate sort recent searches chronologically by @J4mm3ris in https://github.com/eddyizm/tempus/pull/300
|
||||||
|
* feat: add heart to artist/album pages, fixed artist cover art failing by @eddyizm in https://github.com/eddyizm/tempus/pull/303
|
||||||
|
|
||||||
|
## [4.4.0](https://github.com/eddyizm/tempo/releases/tag/v4.4.0) (2025-11-29)
|
||||||
|
## What's Changed
|
||||||
|
* chore: bringing in media service refactor previously reverted after more testing by @eddyizm in https://github.com/eddyizm/tempus/pull/286
|
||||||
|
* fix: refactor start queue to put the db writing in the background to address instant mix bug by @eddyizm in https://github.com/eddyizm/tempus/pull/287
|
||||||
|
* Feat: playerqueue fab allows playqueue actions -> saving to playlist, download all, load queue, shuffle, clean queue by @eddyizm in https://github.com/eddyizm/tempus/pull/288
|
||||||
|
* chore(i18n): Update Polish translation by @skajmer in https://github.com/eddyizm/tempus/pull/291
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.3.0...v4.4.0
|
||||||
|
|
||||||
## [4.3.0](https://github.com/eddyizm/tempo/releases/tag/v4.3.0) (2025-11-23)
|
## [4.3.0](https://github.com/eddyizm/tempo/releases/tag/v4.3.0) (2025-11-23)
|
||||||
## What's Changed
|
## What's Changed
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<!-- Reproducible build -->
|
<!-- Reproducible build -->
|
||||||
[<img src="https://shields.rbtlog.dev/simple/com.eddyizm.degoogled.tempus" alt="RB Status">](https://shields.rbtlog.dev/com.eddyizm.degoogled.tempus)
|
<a href="https://shields.rbtlog.dev/com.eddyizm.degoogled.tempus"><img src="https://shields.rbtlog.dev/simple/com.eddyizm.degoogled.tempus" alt="RB Status"></a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ android {
|
|||||||
minSdkVersion 24
|
minSdkVersion 24
|
||||||
targetSdk 35
|
targetSdk 35
|
||||||
|
|
||||||
versionCode 9
|
versionCode 10
|
||||||
versionName '4.4.0'
|
versionName '4.5.0'
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
|
|
||||||
javaCompileOptions {
|
javaCompileOptions {
|
||||||
|
|||||||
1158
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/13.json
Normal file
1158
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/13.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
|||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Database(
|
@Database(
|
||||||
version = 12,
|
version = 13,
|
||||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class, SessionMediaItem.class, Playlist.class, LyricsCache.class},
|
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class, SessionMediaItem.class, Playlist.class, LyricsCache.class},
|
||||||
autoMigrations = {@AutoMigration(from = 10, to = 11), @AutoMigration(from = 11, to = 12)}
|
autoMigrations = {@AutoMigration(from = 10, to = 11), @AutoMigration(from = 11, to = 12)}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,9 +12,12 @@ import java.util.List;
|
|||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface RecentSearchDao {
|
public interface RecentSearchDao {
|
||||||
@Query("SELECT * FROM recent_search ORDER BY search DESC")
|
@Query("SELECT search FROM recent_search ORDER BY timestamp DESC")
|
||||||
List<String> getRecent();
|
List<String> getRecent();
|
||||||
|
|
||||||
|
@Query("SELECT search FROM recent_search ORDER BY search DESC")
|
||||||
|
List<String> getAlpha();
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
void insert(RecentSearch search);
|
void insert(RecentSearch search);
|
||||||
|
|
||||||
|
|||||||
@@ -13,5 +13,8 @@ import kotlinx.parcelize.Parcelize
|
|||||||
data class RecentSearch(
|
data class RecentSearch(
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@ColumnInfo(name = "search")
|
@ColumnInfo(name = "search")
|
||||||
var search: String
|
var search: String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "timestamp", defaultValue = "0")
|
||||||
|
var timestamp: Long
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|||||||
@@ -205,6 +205,8 @@ public class AlbumRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void getInstantMix(AlbumID3 album, int count, MediaCallback callback) {
|
public void getInstantMix(AlbumID3 album, int count, MediaCallback callback) {
|
||||||
|
Log.d("AlbumRepository", "Attempting getInstantMix for AlbumID: " + album.getId());
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getBrowsingClient()
|
.getBrowsingClient()
|
||||||
.getSimilarSongs2(album.getId(), count)
|
.getSimilarSongs2(album.getId(), count)
|
||||||
@@ -213,8 +215,17 @@ public class AlbumRepository {
|
|||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
List<Child> songs = new ArrayList<>();
|
List<Child> songs = new ArrayList<>();
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
if (response.isSuccessful()
|
||||||
songs.addAll(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
&& response.body() != null
|
||||||
|
&& response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
||||||
|
|
||||||
|
List<Child> similarSongs = response.body().getSubsonicResponse().getSimilarSongs2().getSongs();
|
||||||
|
|
||||||
|
if (similarSongs == null) {
|
||||||
|
Log.w("AlbumRepository", "API successful but 'songs' list was NULL for AlbumID: " + album.getId());
|
||||||
|
} else {
|
||||||
|
songs.addAll(similarSongs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.onLoadMedia(songs);
|
callback.onLoadMedia(songs);
|
||||||
@@ -298,4 +309,4 @@ public class AlbumRepository {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
|||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
|
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
@@ -186,7 +187,12 @@ public class SearchingRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
recent = recentSearchDao.getRecent();
|
if(Preferences.isSearchSortingChronologicallyEnabled()){
|
||||||
|
recent = recentSearchDao.getRecent();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
recent = recentSearchDao.getAlpha();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getRecent() {
|
public List<String> getRecent() {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import android.content.ComponentName;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -12,6 +12,7 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -60,12 +61,14 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
|||||||
private SongHorizontalAdapter songHorizontalAdapter;
|
private SongHorizontalAdapter songHorizontalAdapter;
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
|
/** @noinspection deprecation*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @noinspection deprecation*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
@@ -81,7 +84,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
|||||||
albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class);
|
albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class);
|
||||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||||
|
|
||||||
init();
|
init(view);
|
||||||
initAppBar();
|
initAppBar();
|
||||||
initAlbumInfoTextButton();
|
initAlbumInfoTextButton();
|
||||||
initAlbumNotes();
|
initAlbumNotes();
|
||||||
@@ -119,12 +122,13 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @noinspection deprecation*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
if (item.getItemId() == R.id.action_rate_album) {
|
if (item.getItemId() == R.id.action_rate_album) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
AlbumID3 album = albumPageViewModel.getAlbum().getValue();
|
AlbumID3 album = albumPageViewModel.getAlbum().getValue();
|
||||||
bundle.putParcelable(Constants.ALBUM_OBJECT, (Parcelable) album);
|
bundle.putParcelable(Constants.ALBUM_OBJECT, album);
|
||||||
RatingDialog dialog = new RatingDialog();
|
RatingDialog dialog = new RatingDialog();
|
||||||
dialog.setArguments(bundle);
|
dialog.setArguments(bundle);
|
||||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||||
@@ -159,8 +163,21 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init(View view) {
|
||||||
albumPageViewModel.setAlbum(getViewLifecycleOwner(), requireArguments().getParcelable(Constants.ALBUM_OBJECT));
|
AlbumID3 albumArg = requireArguments().getParcelable(Constants.ALBUM_OBJECT);
|
||||||
|
assert albumArg != null;
|
||||||
|
albumPageViewModel.setAlbum(getViewLifecycleOwner(), albumArg);
|
||||||
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
|
favoriteToggle.setChecked(albumArg.getStarred() != null);
|
||||||
|
|
||||||
|
favoriteToggle.setOnClickListener(v -> {
|
||||||
|
albumPageViewModel.setFavorite();
|
||||||
|
});
|
||||||
|
albumPageViewModel.getAlbum().observe(getViewLifecycleOwner(), album -> {
|
||||||
|
if (album != null) {
|
||||||
|
favoriteToggle.setChecked(album.getStarred() != null);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
|
|||||||
@@ -2,14 +2,18 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
@@ -40,6 +44,7 @@ import com.google.common.util.concurrent.ListenableFuture;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class ArtistPageFragment extends Fragment implements ClickCallback {
|
public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||||
@@ -63,7 +68,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
|||||||
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
|
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
|
||||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||||
|
|
||||||
init();
|
init(view);
|
||||||
initAppBar();
|
initAppBar();
|
||||||
initArtistInfo();
|
initArtistInfo();
|
||||||
initPlayButtons();
|
initPlayButtons();
|
||||||
@@ -100,7 +105,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init(View view) {
|
||||||
artistPageViewModel.setArtist(requireArguments().getParcelable(Constants.ARTIST_OBJECT));
|
artistPageViewModel.setArtist(requireArguments().getParcelable(Constants.ARTIST_OBJECT));
|
||||||
|
|
||||||
bind.mostStreamedSongTextViewClickable.setOnClickListener(v -> {
|
bind.mostStreamedSongTextViewClickable.setOnClickListener(v -> {
|
||||||
@@ -109,6 +114,10 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
|||||||
bundle.putParcelable(Constants.ARTIST_OBJECT, artistPageViewModel.getArtist());
|
bundle.putParcelable(Constants.ARTIST_OBJECT, artistPageViewModel.getArtist());
|
||||||
activity.navController.navigate(R.id.action_artistPageFragment_to_songListPageFragment, bundle);
|
activity.navController.navigate(R.id.action_artistPageFragment_to_songListPageFragment, bundle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
|
favoriteToggle.setChecked(artistPageViewModel.getArtist().getStarred() != null);
|
||||||
|
favoriteToggle.setOnClickListener(v -> artistPageViewModel.setFavorite(requireContext()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
@@ -133,10 +142,54 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
|||||||
if (bind != null)
|
if (bind != null)
|
||||||
bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE);
|
bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (getContext() != null && bind != null) CustomGlideRequest.Builder
|
if (getContext() != null && bind != null) {
|
||||||
.from(requireContext(), artistPageViewModel.getArtist().getId(), CustomGlideRequest.ResourceType.Artist)
|
ArtistID3 currentArtist = artistPageViewModel.getArtist();
|
||||||
.build()
|
String primaryId = currentArtist.getCoverArtId() != null && !currentArtist.getCoverArtId().trim().isEmpty()
|
||||||
.into(bind.artistBackdropImageView);
|
? currentArtist.getCoverArtId()
|
||||||
|
: currentArtist.getId();
|
||||||
|
|
||||||
|
final String fallbackId = (Objects.requireNonNull(primaryId).equals(currentArtist.getCoverArtId()) &&
|
||||||
|
currentArtist.getId() != null &&
|
||||||
|
!currentArtist.getId().equals(primaryId))
|
||||||
|
? currentArtist.getId()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), primaryId, CustomGlideRequest.ResourceType.Artist)
|
||||||
|
.build()
|
||||||
|
.listener(new com.bumptech.glide.request.RequestListener<Drawable>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable com.bumptech.glide.load.engine.GlideException e,
|
||||||
|
Object model,
|
||||||
|
@NonNull com.bumptech.glide.request.target.Target<Drawable> target,
|
||||||
|
boolean isFirstResource) {
|
||||||
|
if (e != null) {
|
||||||
|
e.getMessage();
|
||||||
|
if (e.getMessage().contains("400") && fallbackId != null) {
|
||||||
|
|
||||||
|
Log.d("ArtistCover", "Primary ID failed (400), trying fallback: " + fallbackId);
|
||||||
|
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), fallbackId, CustomGlideRequest.ResourceType.Artist)
|
||||||
|
.build()
|
||||||
|
.into(bind.artistBackdropImageView);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(@NonNull Drawable resource,
|
||||||
|
@NonNull Object model,
|
||||||
|
com.bumptech.glide.request.target.Target<Drawable> target,
|
||||||
|
@NonNull com.bumptech.glide.load.DataSource dataSource,
|
||||||
|
boolean isFirstResource) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(bind.artistBackdropImageView);
|
||||||
|
}
|
||||||
|
|
||||||
if (bind != null) bind.bioTextView.setText(normalizedBio);
|
if (bind != null) bind.bioTextView.setText(normalizedBio);
|
||||||
|
|
||||||
@@ -150,29 +203,24 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPlayButtons() {
|
private void initPlayButtons() {
|
||||||
bind.artistPageShuffleButton.setOnClickListener(v -> {
|
bind.artistPageShuffleButton.setOnClickListener(v -> artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> {
|
if (!songs.isEmpty()) {
|
||||||
if (!songs.isEmpty()) {
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
activity.setBottomSheetInPeek(true);
|
||||||
activity.setBottomSheetInPeek(true);
|
} else {
|
||||||
} else {
|
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
|
||||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
|
}
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
bind.artistPageRadioButton.setOnClickListener(v -> {
|
bind.artistPageRadioButton.setOnClickListener(v -> artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> {
|
||||||
artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> {
|
if (songs != null && !songs.isEmpty()) {
|
||||||
if (songs != null && !songs.isEmpty()) {
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
activity.setBottomSheetInPeek(true);
|
||||||
activity.setBottomSheetInPeek(true);
|
} else {
|
||||||
} else {
|
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_radio), Toast.LENGTH_SHORT).show();
|
||||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_radio), Toast.LENGTH_SHORT).show();
|
}
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initTopSongsView() {
|
private void initTopSongsView() {
|
||||||
|
|||||||
@@ -38,10 +38,10 @@ import com.cappielloantonio.tempo.model.HomeSector;
|
|||||||
import com.cappielloantonio.tempo.service.DownloaderManager;
|
import com.cappielloantonio.tempo.service.DownloaderManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
|
||||||
@@ -57,6 +57,8 @@ import com.cappielloantonio.tempo.ui.dialog.HomeRearrangementDialog;
|
|||||||
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
|
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
|
||||||
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.ExternalAudioWriter;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
@@ -66,8 +68,6 @@ import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
|||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import androidx.media3.common.MediaItem;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -279,51 +279,113 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initSyncStarredView() {
|
private void initSyncStarredView() {
|
||||||
if (Preferences.isStarredSyncEnabled() && Preferences.getDownloadDirectoryUri() == null) {
|
if (Preferences.isStarredSyncEnabled()) {
|
||||||
homeViewModel.getAllStarredTracks().observeForever(new Observer<List<Child>>() {
|
homeViewModel.getAllStarredTracks().observe(getViewLifecycleOwner(), new Observer<List<Child>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onChanged(List<Child> songs) {
|
public void onChanged(List<Child> songs) {
|
||||||
if (songs != null) {
|
if (songs != null && !songs.isEmpty()) {
|
||||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
int songsToSyncCount = 0;
|
||||||
List<String> toSync = new ArrayList<>();
|
List<String> toSyncSample = new ArrayList<>();
|
||||||
|
|
||||||
for (Child song : songs) {
|
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||||
if (!manager.isDownloaded(song.getId())) {
|
|
||||||
toSync.add(song.getTitle());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!toSync.isEmpty()) {
|
|
||||||
bind.homeSyncStarredCard.setVisibility(View.VISIBLE);
|
|
||||||
bind.homeSyncStarredTracksToSync.setText(String.join(", ", toSync));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
homeViewModel.getAllStarredTracks().removeObserver(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bind.homeSyncStarredCancel.setOnClickListener(v -> bind.homeSyncStarredCard.setVisibility(View.GONE));
|
|
||||||
|
|
||||||
bind.homeSyncStarredDownload.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
homeViewModel.getAllStarredTracks().observeForever(new Observer<List<Child>>() {
|
|
||||||
@Override
|
|
||||||
public void onChanged(List<Child> songs) {
|
|
||||||
if (songs != null) {
|
|
||||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||||
|
|
||||||
for (Child song : songs) {
|
for (Child song : songs) {
|
||||||
if (!manager.isDownloaded(song.getId())) {
|
if (!manager.isDownloaded(song.getId())) {
|
||||||
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
songsToSyncCount++;
|
||||||
|
if (toSyncSample.size() < 3) {
|
||||||
|
toSyncSample.add(song.getTitle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Child song : songs) {
|
||||||
|
if (ExternalAudioReader.getUri(song) == null) {
|
||||||
|
songsToSyncCount++;
|
||||||
|
if (toSyncSample.size() < 3) {
|
||||||
|
toSyncSample.add(song.getTitle());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
homeViewModel.getAllStarredTracks().removeObserver(this);
|
if (songsToSyncCount > 0) {
|
||||||
|
bind.homeSyncStarredCard.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
StringBuilder displayText = new StringBuilder();
|
||||||
|
if (!toSyncSample.isEmpty()) {
|
||||||
|
displayText.append(String.join(", ", toSyncSample));
|
||||||
|
if (songsToSyncCount > 3) {
|
||||||
|
displayText.append("...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String countText = getResources().getQuantityString(
|
||||||
|
R.plurals.home_sync_starred_songs_count,
|
||||||
|
songsToSyncCount,
|
||||||
|
songsToSyncCount
|
||||||
|
);
|
||||||
|
|
||||||
|
if (displayText.length() > 0) {
|
||||||
|
bind.homeSyncStarredTracksToSync.setText(displayText.toString() + "\n" + countText);
|
||||||
|
} else {
|
||||||
|
bind.homeSyncStarredTracksToSync.setText(countText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> reorder());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bind.homeSyncStarredCard.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bind.homeSyncStarredCancel.setOnClickListener(v -> {
|
||||||
|
bind.homeSyncStarredCard.setVisibility(View.GONE);
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> reorder());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bind.homeSyncStarredDownload.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
homeViewModel.getAllStarredTracks().observe(getViewLifecycleOwner(), new Observer<List<Child>>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(List<Child> songs) {
|
||||||
|
if (songs != null && !songs.isEmpty()) {
|
||||||
|
int downloadedCount = 0;
|
||||||
|
|
||||||
|
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||||
|
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||||
|
for (Child song : songs) {
|
||||||
|
if (!manager.isDownloaded(song.getId())) {
|
||||||
|
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
||||||
|
downloadedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Child song : songs) {
|
||||||
|
if (ExternalAudioReader.getUri(song) == null) {
|
||||||
|
ExternalAudioWriter.downloadToUserDirectory(requireContext(), song);
|
||||||
|
downloadedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadedCount > 0) {
|
||||||
|
Toast.makeText(requireContext(),
|
||||||
|
getResources().getQuantityString(R.plurals.songs_download_started, downloadedCount, downloadedCount),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bind.homeSyncStarredCard.setVisibility(View.GONE);
|
bind.homeSyncStarredCard.setVisibility(View.GONE);
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> reorder());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -331,6 +393,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initSyncStarredAlbumsView() {
|
private void initSyncStarredAlbumsView() {
|
||||||
|
|
||||||
if (Preferences.isStarredAlbumsSyncEnabled()) {
|
if (Preferences.isStarredAlbumsSyncEnabled()) {
|
||||||
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), new Observer<List<AlbumID3>>() {
|
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), new Observer<List<AlbumID3>>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -344,6 +407,9 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
bind.homeSyncStarredAlbumsCancel.setOnClickListener(v -> {
|
bind.homeSyncStarredAlbumsCancel.setOnClickListener(v -> {
|
||||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> reorder());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
bind.homeSyncStarredAlbumsDownload.setOnClickListener(v -> {
|
bind.homeSyncStarredAlbumsDownload.setOnClickListener(v -> {
|
||||||
@@ -351,24 +417,36 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
@Override
|
@Override
|
||||||
public void onChanged(List<Child> allSongs) {
|
public void onChanged(List<Child> allSongs) {
|
||||||
if (allSongs != null && !allSongs.isEmpty()) {
|
if (allSongs != null && !allSongs.isEmpty()) {
|
||||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
|
||||||
int songsToDownload = 0;
|
int songsToDownload = 0;
|
||||||
|
|
||||||
for (Child song : allSongs) {
|
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||||
if (!manager.isDownloaded(song.getId())) {
|
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||||
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
for (Child song : allSongs) {
|
||||||
songsToDownload++;
|
if (!manager.isDownloaded(song.getId())) {
|
||||||
|
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
||||||
|
songsToDownload++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Child song : allSongs) {
|
||||||
|
if (ExternalAudioReader.getUri(song) == null) {
|
||||||
|
ExternalAudioWriter.downloadToUserDirectory(requireContext(), song);
|
||||||
|
songsToDownload++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (songsToDownload > 0) {
|
if (songsToDownload > 0) {
|
||||||
Toast.makeText(requireContext(),
|
Toast.makeText(requireContext(),
|
||||||
getResources().getQuantityString(R.plurals.songs_download_started, songsToDownload, songsToDownload),
|
getResources().getQuantityString(R.plurals.songs_download_started, songsToDownload, songsToDownload),
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> reorder());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -379,33 +457,73 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
@Override
|
@Override
|
||||||
public void onChanged(List<Child> allSongs) {
|
public void onChanged(List<Child> allSongs) {
|
||||||
if (allSongs != null) {
|
if (allSongs != null) {
|
||||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
|
||||||
int songsToDownload = 0;
|
int songsToDownload = 0;
|
||||||
List<String> albumsNeedingSync = new ArrayList<>();
|
List<String> albumsNeedingSync = new ArrayList<>();
|
||||||
|
|
||||||
for (AlbumID3 album : albums) {
|
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||||
boolean albumNeedsSync = false;
|
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||||
// Check if any songs from this album need downloading
|
|
||||||
for (Child song : allSongs) {
|
for (AlbumID3 album : albums) {
|
||||||
if (song.getAlbumId() != null && song.getAlbumId().equals(album.getId()) &&
|
boolean albumNeedsSync = false;
|
||||||
!manager.isDownloaded(song.getId())) {
|
for (Child song : allSongs) {
|
||||||
songsToDownload++;
|
if (song.getAlbumId() != null && song.getAlbumId().equals(album.getId()) &&
|
||||||
albumNeedsSync = true;
|
!manager.isDownloaded(song.getId())) {
|
||||||
|
songsToDownload++;
|
||||||
|
albumNeedsSync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (albumNeedsSync) {
|
||||||
|
albumsNeedingSync.add(album.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (albumNeedsSync) {
|
} else {
|
||||||
albumsNeedingSync.add(album.getName());
|
for (AlbumID3 album : albums) {
|
||||||
|
boolean albumNeedsSync = false;
|
||||||
|
for (Child song : allSongs) {
|
||||||
|
if (song.getAlbumId() != null && song.getAlbumId().equals(album.getId()) &&
|
||||||
|
ExternalAudioReader.getUri(song) == null) {
|
||||||
|
songsToDownload++;
|
||||||
|
albumNeedsSync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (albumNeedsSync) {
|
||||||
|
albumsNeedingSync.add(album.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (songsToDownload > 0) {
|
if (songsToDownload > 0) {
|
||||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.VISIBLE);
|
bind.homeSyncStarredAlbumsCard.setVisibility(View.VISIBLE);
|
||||||
String message = getResources().getQuantityString(
|
|
||||||
R.plurals.home_sync_starred_albums_count,
|
StringBuilder displayText = new StringBuilder();
|
||||||
albumsNeedingSync.size(),
|
List<String> sampleAlbums = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.min(albumsNeedingSync.size(), 3); i++) {
|
||||||
|
sampleAlbums.add(albumsNeedingSync.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sampleAlbums.isEmpty()) {
|
||||||
|
displayText.append(String.join(", ", sampleAlbums));
|
||||||
|
if (albumsNeedingSync.size() > 3) {
|
||||||
|
displayText.append("...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String countText = getResources().getQuantityString(
|
||||||
|
R.plurals.home_sync_starred_albums_count,
|
||||||
|
albumsNeedingSync.size(),
|
||||||
albumsNeedingSync.size()
|
albumsNeedingSync.size()
|
||||||
);
|
);
|
||||||
bind.homeSyncStarredAlbumsToSync.setText(message);
|
|
||||||
|
if (displayText.length() > 0) {
|
||||||
|
bind.homeSyncStarredAlbumsToSync.setText(displayText.toString() + "\n" + countText);
|
||||||
|
} else {
|
||||||
|
bind.homeSyncStarredAlbumsToSync.setText(countText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> reorder());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@@ -428,6 +546,9 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
bind.homeSyncStarredArtistsCancel.setOnClickListener(v -> {
|
bind.homeSyncStarredArtistsCancel.setOnClickListener(v -> {
|
||||||
bind.homeSyncStarredArtistsCard.setVisibility(View.GONE);
|
bind.homeSyncStarredArtistsCard.setVisibility(View.GONE);
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> reorder());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
bind.homeSyncStarredArtistsDownload.setOnClickListener(v -> {
|
bind.homeSyncStarredArtistsDownload.setOnClickListener(v -> {
|
||||||
@@ -435,24 +556,36 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
@Override
|
@Override
|
||||||
public void onChanged(List<Child> allSongs) {
|
public void onChanged(List<Child> allSongs) {
|
||||||
if (allSongs != null && !allSongs.isEmpty()) {
|
if (allSongs != null && !allSongs.isEmpty()) {
|
||||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
|
||||||
int songsToDownload = 0;
|
int songsToDownload = 0;
|
||||||
|
|
||||||
for (Child song : allSongs) {
|
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||||
if (!manager.isDownloaded(song.getId())) {
|
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||||
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
for (Child song : allSongs) {
|
||||||
songsToDownload++;
|
if (!manager.isDownloaded(song.getId())) {
|
||||||
|
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
||||||
|
songsToDownload++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Child song : allSongs) {
|
||||||
|
if (ExternalAudioReader.getUri(song) == null) {
|
||||||
|
ExternalAudioWriter.downloadToUserDirectory(requireContext(), song);
|
||||||
|
songsToDownload++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (songsToDownload > 0) {
|
if (songsToDownload > 0) {
|
||||||
Toast.makeText(requireContext(),
|
Toast.makeText(requireContext(),
|
||||||
getResources().getQuantityString(R.plurals.songs_download_started, songsToDownload, songsToDownload),
|
getResources().getQuantityString(R.plurals.songs_download_started, songsToDownload, songsToDownload),
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bind.homeSyncStarredArtistsCard.setVisibility(View.GONE);
|
bind.homeSyncStarredArtistsCard.setVisibility(View.GONE);
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> reorder());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -463,33 +596,73 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
@Override
|
@Override
|
||||||
public void onChanged(List<Child> allSongs) {
|
public void onChanged(List<Child> allSongs) {
|
||||||
if (allSongs != null) {
|
if (allSongs != null) {
|
||||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
|
||||||
int songsToDownload = 0;
|
int songsToDownload = 0;
|
||||||
List<String> artistsNeedingSync = new ArrayList<>();
|
List<String> artistsNeedingSync = new ArrayList<>();
|
||||||
|
|
||||||
for (ArtistID3 artist : artists) {
|
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||||
boolean artistNeedsSync = false;
|
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||||
// Check if any songs from this artist need downloading
|
|
||||||
for (Child song : allSongs) {
|
for (ArtistID3 artist : artists) {
|
||||||
if (song.getArtistId() != null && song.getArtistId().equals(artist.getId()) &&
|
boolean artistNeedsSync = false;
|
||||||
!manager.isDownloaded(song.getId())) {
|
for (Child song : allSongs) {
|
||||||
songsToDownload++;
|
if (song.getArtistId() != null && song.getArtistId().equals(artist.getId()) &&
|
||||||
artistNeedsSync = true;
|
!manager.isDownloaded(song.getId())) {
|
||||||
|
songsToDownload++;
|
||||||
|
artistNeedsSync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (artistNeedsSync) {
|
||||||
|
artistsNeedingSync.add(artist.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (artistNeedsSync) {
|
} else {
|
||||||
artistsNeedingSync.add(artist.getName());
|
for (ArtistID3 artist : artists) {
|
||||||
|
boolean artistNeedsSync = false;
|
||||||
|
for (Child song : allSongs) {
|
||||||
|
if (song.getArtistId() != null && song.getArtistId().equals(artist.getId()) &&
|
||||||
|
ExternalAudioReader.getUri(song) == null) {
|
||||||
|
songsToDownload++;
|
||||||
|
artistNeedsSync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (artistNeedsSync) {
|
||||||
|
artistsNeedingSync.add(artist.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (songsToDownload > 0) {
|
if (songsToDownload > 0) {
|
||||||
bind.homeSyncStarredArtistsCard.setVisibility(View.VISIBLE);
|
bind.homeSyncStarredArtistsCard.setVisibility(View.VISIBLE);
|
||||||
String message = getResources().getQuantityString(
|
|
||||||
R.plurals.home_sync_starred_artists_count,
|
StringBuilder displayText = new StringBuilder();
|
||||||
artistsNeedingSync.size(),
|
List<String> sampleArtists = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.min(artistsNeedingSync.size(), 3); i++) {
|
||||||
|
sampleArtists.add(artistsNeedingSync.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sampleArtists.isEmpty()) {
|
||||||
|
displayText.append(String.join(", ", sampleArtists));
|
||||||
|
if (artistsNeedingSync.size() > 3) {
|
||||||
|
displayText.append("...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String countText = getResources().getQuantityString(
|
||||||
|
R.plurals.home_sync_starred_artists_count,
|
||||||
|
artistsNeedingSync.size(),
|
||||||
artistsNeedingSync.size()
|
artistsNeedingSync.size()
|
||||||
);
|
);
|
||||||
bind.homeSyncStarredArtistsToSync.setText(message);
|
|
||||||
|
if (displayText.length() > 0) {
|
||||||
|
bind.homeSyncStarredArtistsToSync.setText(displayText.toString() + "\n" + countText);
|
||||||
|
} else {
|
||||||
|
bind.homeSyncStarredArtistsToSync.setText(countText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> reorder());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bind.homeSyncStarredArtistsCard.setVisibility(View.GONE);
|
bind.homeSyncStarredArtistsCard.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@@ -497,7 +670,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDiscoverSongSlideView() {
|
private void initDiscoverSongSlideView() {
|
||||||
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_DISCOVERY)) return;
|
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_DISCOVERY)) return;
|
||||||
|
|
||||||
@@ -962,6 +1135,18 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
if (bind != null && homeViewModel.getHomeSectorList() != null) {
|
if (bind != null && homeViewModel.getHomeSectorList() != null) {
|
||||||
bind.homeLinearLayoutContainer.removeAllViews();
|
bind.homeLinearLayoutContainer.removeAllViews();
|
||||||
|
|
||||||
|
if (bind.homeSyncStarredCard.getVisibility() == View.VISIBLE) {
|
||||||
|
bind.homeLinearLayoutContainer.addView(bind.homeSyncStarredCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind.homeSyncStarredAlbumsCard.getVisibility() == View.VISIBLE) {
|
||||||
|
bind.homeLinearLayoutContainer.addView(bind.homeSyncStarredAlbumsCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind.homeSyncStarredArtistsCard.getVisibility() == View.VISIBLE) {
|
||||||
|
bind.homeLinearLayoutContainer.addView(bind.homeSyncStarredArtistsCard);
|
||||||
|
}
|
||||||
|
|
||||||
for (HomeSector sector : homeViewModel.getHomeSectorList()) {
|
for (HomeSector sector : homeViewModel.getHomeSectorList()) {
|
||||||
if (!sector.isVisible()) continue;
|
if (!sector.isVisible()) continue;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.util;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.OptIn;
|
import androidx.annotation.OptIn;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
@@ -35,84 +36,106 @@ public class MappingUtil {
|
|||||||
return mediaItems;
|
return mediaItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final String TAG = "MappingUtil";
|
||||||
|
|
||||||
public static MediaItem mapMediaItem(Child media) {
|
public static MediaItem mapMediaItem(Child media) {
|
||||||
Uri uri = getUri(media);
|
try {
|
||||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(media.getCoverArtId(), Preferences.getImageSize()));
|
Uri uri = getUri(media);
|
||||||
|
String coverArtId = media.getCoverArtId();
|
||||||
|
Uri artworkUri = null;
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
if (coverArtId != null) {
|
||||||
bundle.putString("id", media.getId());
|
artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, Preferences.getImageSize()));
|
||||||
bundle.putString("parentId", media.getParentId());
|
}
|
||||||
bundle.putBoolean("isDir", media.isDir());
|
|
||||||
bundle.putString("title", media.getTitle());
|
|
||||||
bundle.putString("album", media.getAlbum());
|
|
||||||
bundle.putString("artist", media.getArtist());
|
|
||||||
bundle.putInt("track", media.getTrack() != null ? media.getTrack() : 0);
|
|
||||||
bundle.putInt("year", media.getYear() != null ? media.getYear() : 0);
|
|
||||||
bundle.putString("genre", media.getGenre());
|
|
||||||
bundle.putString("coverArtId", media.getCoverArtId());
|
|
||||||
bundle.putLong("size", media.getSize() != null ? media.getSize() : 0);
|
|
||||||
bundle.putString("contentType", media.getContentType());
|
|
||||||
bundle.putString("suffix", media.getSuffix());
|
|
||||||
bundle.putString("transcodedContentType", media.getTranscodedContentType());
|
|
||||||
bundle.putString("transcodedSuffix", media.getTranscodedSuffix());
|
|
||||||
bundle.putInt("duration", media.getDuration() != null ? media.getDuration() : 0);
|
|
||||||
bundle.putInt("bitrate", media.getBitrate() != null ? media.getBitrate() : 0);
|
|
||||||
bundle.putInt("samplingRate", media.getSamplingRate() != null ? media.getSamplingRate() : 0);
|
|
||||||
bundle.putInt("bitDepth", media.getBitDepth() != null ? media.getBitDepth() : 0);
|
|
||||||
bundle.putString("path", media.getPath());
|
|
||||||
bundle.putBoolean("isVideo", media.isVideo());
|
|
||||||
bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0);
|
|
||||||
bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0);
|
|
||||||
bundle.putLong("playCount", media.getPlayCount() != null ? media.getPlayCount() : 0);
|
|
||||||
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getDiscNumber() : 0);
|
|
||||||
bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0);
|
|
||||||
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
|
|
||||||
bundle.putString("albumId", media.getAlbumId());
|
|
||||||
bundle.putString("artistId", media.getArtistId());
|
|
||||||
bundle.putString("type", Constants.MEDIA_TYPE_MUSIC);
|
|
||||||
bundle.putLong("bookmarkPosition", media.getBookmarkPosition() != null ? media.getBookmarkPosition() : 0);
|
|
||||||
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()
|
Bundle bundle = new Bundle();
|
||||||
.setMediaId(media.getId())
|
bundle.putString("id", media.getId());
|
||||||
.setMediaMetadata(
|
bundle.putString("parentId", media.getParentId());
|
||||||
new MediaMetadata.Builder()
|
bundle.putBoolean("isDir", media.isDir());
|
||||||
.setTitle(media.getTitle())
|
|
||||||
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
bundle.putString("title", media.getTitle());
|
||||||
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
bundle.putString("album", media.getAlbum());
|
||||||
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
bundle.putString("artist", media.getArtist());
|
||||||
.setAlbumTitle(media.getAlbum())
|
|
||||||
.setArtist(media.getArtist())
|
bundle.putInt("track", media.getTrack() != null ? media.getTrack() : 0);
|
||||||
.setArtworkUri(artworkUri)
|
bundle.putInt("year", media.getYear() != null ? media.getYear() : 0);
|
||||||
.setUserRating(new HeartRating(media.getStarred() != null))
|
bundle.putString("genre", media.getGenre());
|
||||||
.setSupportedCommands(
|
bundle.putString("coverArtId", coverArtId);
|
||||||
|
bundle.putLong("size", media.getSize() != null ? media.getSize() : 0);
|
||||||
|
bundle.putString("contentType", media.getContentType());
|
||||||
|
bundle.putString("suffix", media.getSuffix());
|
||||||
|
bundle.putString("transcodedContentType", media.getTranscodedContentType());
|
||||||
|
bundle.putString("transcodedSuffix", media.getTranscodedSuffix());
|
||||||
|
bundle.putInt("duration", media.getDuration() != null ? media.getDuration() : 0);
|
||||||
|
bundle.putInt("bitrate", media.getBitrate() != null ? media.getBitrate() : 0);
|
||||||
|
bundle.putInt("samplingRate", media.getSamplingRate() != null ? media.getSamplingRate() : 0);
|
||||||
|
bundle.putInt("bitDepth", media.getBitDepth() != null ? media.getBitDepth() : 0);
|
||||||
|
bundle.putString("path", media.getPath());
|
||||||
|
bundle.putBoolean("isVideo", media.isVideo());
|
||||||
|
bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0);
|
||||||
|
bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0);
|
||||||
|
bundle.putLong("playCount", media.getPlayCount() != null ? media.getPlayCount() : 0);
|
||||||
|
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getDiscNumber() : 0);
|
||||||
|
bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0);
|
||||||
|
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
|
||||||
|
bundle.putString("albumId", media.getAlbumId());
|
||||||
|
bundle.putString("artistId", media.getArtistId());
|
||||||
|
bundle.putString("type", Constants.MEDIA_TYPE_MUSIC);
|
||||||
|
bundle.putLong("bookmarkPosition", media.getBookmarkPosition() != null ? media.getBookmarkPosition() : 0);
|
||||||
|
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", media.getId() != null ? AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, media.getId()) : null);
|
||||||
|
bundle.putString("assetLinkAlbum", media.getAlbumId() != null ? AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, media.getAlbumId()) : null);
|
||||||
|
bundle.putString("assetLinkArtist", media.getArtistId() != null ? AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, media.getArtistId()) : null);
|
||||||
|
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())
|
||||||
|
.setMediaMetadata(
|
||||||
|
new MediaMetadata.Builder()
|
||||||
|
.setTitle(media.getTitle())
|
||||||
|
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||||
|
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||||
|
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||||
|
.setAlbumTitle(media.getAlbum())
|
||||||
|
.setArtist(media.getArtist())
|
||||||
|
.setArtworkUri(artworkUri)
|
||||||
|
.setUserRating(new HeartRating(media.getStarred() != null))
|
||||||
|
.setSupportedCommands(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_ON,
|
Constants.CUSTOM_COMMAND_TOGGLE_HEART_ON,
|
||||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_OFF
|
Constants.CUSTOM_COMMAND_TOGGLE_HEART_OFF
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.setExtras(bundle)
|
.setExtras(bundle)
|
||||||
.setIsBrowsable(false)
|
.setIsBrowsable(false)
|
||||||
.setIsPlayable(true)
|
.setIsPlayable(true)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setRequestMetadata(
|
.setRequestMetadata(
|
||||||
new MediaItem.RequestMetadata.Builder()
|
new MediaItem.RequestMetadata.Builder()
|
||||||
.setMediaUri(uri)
|
.setMediaUri(uri)
|
||||||
.setExtras(bundle)
|
.setExtras(bundle)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||||
.setUri(uri)
|
.setUri(uri)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
String id = media != null ? media.getId() : "NULL_MEDIA_OBJECT";
|
||||||
|
String title = media != null ? media.getTitle() : "N/A";
|
||||||
|
|
||||||
|
Log.e(TAG, "Instant Mix CRASH! Failed to map song to MediaItem. " +
|
||||||
|
"Problematic Song ID: " + id +
|
||||||
|
", Title: " + title +
|
||||||
|
". Inspect this song's Subsonic data for missing fields.", e);
|
||||||
|
throw new RuntimeException("Mapping failed for song ID: " + id, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MediaItem mapMediaItem(MediaItem old) {
|
public static MediaItem mapMediaItem(MediaItem old) {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ object Preferences {
|
|||||||
private const val ALBUM_SORT_ORDER = "album_sort_order"
|
private const val ALBUM_SORT_ORDER = "album_sort_order"
|
||||||
private const val DEFAULT_ALBUM_SORT_ORDER = Constants.ALBUM_ORDER_BY_NAME
|
private const val DEFAULT_ALBUM_SORT_ORDER = Constants.ALBUM_ORDER_BY_NAME
|
||||||
private const val ARTIST_SORT_BY_ALBUM_COUNT= "artist_sort_by_album_count"
|
private const val ARTIST_SORT_BY_ALBUM_COUNT= "artist_sort_by_album_count"
|
||||||
|
private const val SORT_SEARCH_CHRONOLOGICALLY= "sort_search_chronologically"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getServer(): String? {
|
fun getServer(): String? {
|
||||||
@@ -674,4 +675,9 @@ object Preferences {
|
|||||||
else
|
else
|
||||||
return Constants.ARTIST_ORDER_BY_NAME
|
return Constants.ARTIST_ORDER_BY_NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isSearchSortingChronologicallyEnabled(): Boolean {
|
||||||
|
return App.getInstance().preferences.getBoolean(SORT_SEARCH_CHRONOLOGICALLY, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,18 +8,23 @@ import androidx.lifecycle.LifecycleOwner;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AlbumPageViewModel extends AndroidViewModel {
|
public class AlbumPageViewModel extends AndroidViewModel {
|
||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
private String albumId;
|
private String albumId;
|
||||||
private String artistId;
|
private String artistId;
|
||||||
private final MutableLiveData<AlbumID3> album = new MutableLiveData<>(null);
|
private final MutableLiveData<AlbumID3> album = new MutableLiveData<>(null);
|
||||||
@@ -29,6 +34,7 @@ public class AlbumPageViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Child>> getAlbumSongLiveList() {
|
public LiveData<List<Child>> getAlbumSongLiveList() {
|
||||||
@@ -49,6 +55,61 @@ public class AlbumPageViewModel extends AndroidViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFavorite() {
|
||||||
|
AlbumID3 currentAlbum = album.getValue();
|
||||||
|
if (currentAlbum == null) return;
|
||||||
|
|
||||||
|
if (currentAlbum.getStarred() != null) {
|
||||||
|
if (NetworkUtil.isOffline()) {
|
||||||
|
removeFavoriteOffline(currentAlbum);
|
||||||
|
} else {
|
||||||
|
removeFavoriteOnline(currentAlbum);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (NetworkUtil.isOffline()) {
|
||||||
|
setFavoriteOffline(currentAlbum);
|
||||||
|
} else {
|
||||||
|
setFavoriteOnline(currentAlbum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOffline(AlbumID3 album) {
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, false);
|
||||||
|
album.setStarred(null);
|
||||||
|
this.album.postValue(album);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOnline(AlbumID3 album) {
|
||||||
|
favoriteRepository.unstar(null, album.getId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
album.setStarred(null);
|
||||||
|
this.album.postValue(album);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOffline(AlbumID3 album) {
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, true);
|
||||||
|
album.setStarred(new Date());
|
||||||
|
this.album.postValue(album);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOnline(AlbumID3 album) {
|
||||||
|
favoriteRepository.star(null, album.getId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
album.setStarred(new Date());
|
||||||
|
this.album.postValue(album);
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<ArtistID3> getArtist() {
|
public LiveData<ArtistID3> getArtist() {
|
||||||
return artistRepository.getArtistInfo(artistId);
|
return artistRepository.getArtistInfo(artistId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
package com.cappielloantonio.tempo.viewmodel;
|
package com.cappielloantonio.tempo.viewmodel;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistInfo2;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistInfo2;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class ArtistPageViewModel extends AndroidViewModel {
|
public class ArtistPageViewModel extends AndroidViewModel {
|
||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private ArtistID3 artist;
|
private ArtistID3 artist;
|
||||||
|
|
||||||
@@ -26,6 +40,7 @@ public class ArtistPageViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<AlbumID3>> getAlbumList() {
|
public LiveData<List<AlbumID3>> getAlbumList() {
|
||||||
@@ -55,4 +70,71 @@ public class ArtistPageViewModel extends AndroidViewModel {
|
|||||||
public void setArtist(ArtistID3 artist) {
|
public void setArtist(ArtistID3 artist) {
|
||||||
this.artist = artist;
|
this.artist = artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFavorite(Context context) {
|
||||||
|
if (artist.getStarred() != null) {
|
||||||
|
if (NetworkUtil.isOffline()) {
|
||||||
|
removeFavoriteOffline();
|
||||||
|
} else {
|
||||||
|
removeFavoriteOnline();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (NetworkUtil.isOffline()) {
|
||||||
|
setFavoriteOffline();
|
||||||
|
} else {
|
||||||
|
setFavoriteOnline(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOffline() {
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), false);
|
||||||
|
artist.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOnline() {
|
||||||
|
favoriteRepository.unstar(null, null, artist.getId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
artist.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOffline() {
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), true);
|
||||||
|
artist.setStarred(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOnline(Context context) {
|
||||||
|
favoriteRepository.star(null, null, artist.getId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
artist.setStarred(new Date());
|
||||||
|
|
||||||
|
if (Preferences.isStarredArtistsSyncEnabled()) {
|
||||||
|
artistRepository.getArtistAllSongs(artist.getId(), new ArtistRepository.ArtistSongsCallback() {
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
|
@Override
|
||||||
|
public void onSongsCollected(List<Child> songs) {
|
||||||
|
if (songs != null && !songs.isEmpty()) {
|
||||||
|
DownloadUtil.getDownloadTracker(context).download(
|
||||||
|
MappingUtil.mapDownloads(songs),
|
||||||
|
songs.stream().map(Download::new).collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Log.d("ArtistSync", "Artist sync preference is disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ public class SearchViewModel extends AndroidViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void insertNewSearch(String search) {
|
public void insertNewSearch(String search) {
|
||||||
searchingRepository.insert(new RecentSearch(search));
|
searchingRepository.insert(new RecentSearch(search, System.currentTimeMillis() / 1000L));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteRecentSearch(String search) {
|
public void deleteRecentSearch(String search) {
|
||||||
searchingRepository.delete(new RecentSearch(search));
|
searchingRepository.delete(new RecentSearch(search, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<String>> getSearchSuggestion(String query) {
|
public LiveData<List<String>> getSearchSuggestion(String query) {
|
||||||
|
|||||||
@@ -174,7 +174,6 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/album_notes_textview" />
|
app:layout_constraintTop_toBottomOf="@+id/album_notes_textview" />
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
@@ -188,43 +187,69 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/album_detail_view" />
|
app:layout_constraintTop_toBottomOf="@+id/album_detail_view" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/album_page_button_layout"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:paddingBottom="4dp"
|
android:paddingBottom="4dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
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/upper_button_divider">
|
app:layout_constraintTop_toBottomOf="@+id/upper_button_divider">
|
||||||
|
|
||||||
<Button
|
<LinearLayout
|
||||||
android:id="@+id/album_page_play_button"
|
android:id="@+id/album_page_button_layout"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:padding="10dp"
|
android:orientation="horizontal"
|
||||||
android:text="@string/album_page_play_button"
|
android:gravity="center_vertical">
|
||||||
android:textAllCaps="false"
|
|
||||||
app:icon="@drawable/ic_play"
|
<Button
|
||||||
app:iconGravity="textStart"
|
android:id="@+id/album_page_play_button"
|
||||||
app:iconPadding="18dp" />
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:text="@string/album_page_play_button"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:icon="@drawable/ic_play"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="18dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/album_page_shuffle_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:text="@string/album_page_shuffle_button"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:icon="@drawable/ic_shuffle"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="18dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ToggleButton
|
||||||
|
android:id="@+id/button_favorite"
|
||||||
|
android:layout_width="34dp"
|
||||||
|
android:layout_height="34dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="0dp"
|
||||||
|
android:background="@drawable/button_favorite_selector"
|
||||||
|
android:checked="false"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text=""
|
||||||
|
android:textOff=""
|
||||||
|
android:textOn="" />
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/album_page_shuffle_button"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:text="@string/album_page_shuffle_button"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
app:icon="@drawable/ic_shuffle"
|
|
||||||
app:iconGravity="textStart"
|
|
||||||
app:iconPadding="18dp" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -239,7 +264,8 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
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/album_page_button_layout" />
|
app:layout_constraintTop_toBottomOf="@id/album_page_button_layout"
|
||||||
|
tools:ignore="NotSibling" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/bottom_button_divider"
|
android:id="@+id/bottom_button_divider"
|
||||||
@@ -249,7 +275,7 @@
|
|||||||
android:layout_marginBottom="18dp"
|
android:layout_marginBottom="18dp"
|
||||||
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/album_page_button_layout" />
|
app:layout_constraintTop_toBottomOf="@+id/album_bio_label" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -63,40 +63,67 @@
|
|||||||
android:layout_marginEnd="18dp" />
|
android:layout_marginEnd="18dp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/album_page_button_layout"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:paddingBottom="4dp">
|
android:paddingBottom="4dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/artist_page_shuffle_button"
|
<LinearLayout
|
||||||
|
android:id="@+id/album_page_button_layout"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:padding="10dp"
|
android:orientation="horizontal"
|
||||||
android:text="@string/artist_page_shuffle_button"
|
android:gravity="center_vertical">
|
||||||
android:textAllCaps="false"
|
|
||||||
app:icon="@drawable/ic_shuffle"
|
<Button
|
||||||
app:iconGravity="textStart"
|
android:id="@+id/artist_page_shuffle_button"
|
||||||
app:iconPadding="18dp" />
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:text="@string/artist_page_shuffle_button"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:icon="@drawable/ic_shuffle"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="18dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/artist_page_radio_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:text="@string/artist_page_radio_button"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:icon="@drawable/ic_feed"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="18dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ToggleButton
|
||||||
|
android:id="@+id/button_favorite"
|
||||||
|
android:layout_width="34dp"
|
||||||
|
android:layout_height="34dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="0dp"
|
||||||
|
android:background="@drawable/button_favorite_selector"
|
||||||
|
android:checked="false"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text=""
|
||||||
|
android:textOff=""
|
||||||
|
android:textOn="" />
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/artist_page_radio_button"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:text="@string/artist_page_radio_button"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
app:icon="@drawable/ic_feed"
|
|
||||||
app:iconGravity="textStart"
|
|
||||||
app:iconPadding="18dp" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -133,6 +133,10 @@
|
|||||||
<string name="home_sync_starred_albums_subtitle">Albums marked with a star will be available offline</string>
|
<string name="home_sync_starred_albums_subtitle">Albums marked with a star will be available offline</string>
|
||||||
<string name="home_sync_starred_artists_title">Starred Artists Sync</string>
|
<string name="home_sync_starred_artists_title">Starred Artists Sync</string>
|
||||||
<string name="home_sync_starred_artists_subtitle">You have starred artists with music not downloaded</string>
|
<string name="home_sync_starred_artists_subtitle">You have starred artists with music not downloaded</string>
|
||||||
|
<plurals name="home_sync_starred_songs_count">
|
||||||
|
<item quantity="one">%d song needs sync</item>
|
||||||
|
<item quantity="other">%d songs need sync</item>
|
||||||
|
</plurals>
|
||||||
<string name="home_title_best_of">Best of</string>
|
<string name="home_title_best_of">Best of</string>
|
||||||
<string name="home_title_discovery">Discovery</string>
|
<string name="home_title_discovery">Discovery</string>
|
||||||
<string name="home_title_discovery_shuffle_all_button">Shuffle all</string>
|
<string name="home_title_discovery_shuffle_all_button">Shuffle all</string>
|
||||||
@@ -539,4 +543,7 @@
|
|||||||
<string name="folder_play_collecting">Collecting songs from folder…</string>
|
<string name="folder_play_collecting">Collecting songs from folder…</string>
|
||||||
<string name="folder_play_playing">Playing %d songs</string>
|
<string name="folder_play_playing">Playing %d songs</string>
|
||||||
<string name="folder_play_no_songs">No songs found in folder</string>
|
<string name="folder_play_no_songs">No songs found in folder</string>
|
||||||
|
|
||||||
|
<string name="search_sort_title">Sort recent searches chronologically</string>
|
||||||
|
<string name="search_sort_summary">If enabled, sort searches chronologically. Sort by name if disabled.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -122,6 +122,12 @@
|
|||||||
android:summary="@string/settings_artist_sort_by_album_count_summary"
|
android:summary="@string/settings_artist_sort_by_album_count_summary"
|
||||||
android:key="artist_sort_by_album_count" />
|
android:key="artist_sort_by_album_count" />
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:title="@string/search_sort_title"
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:summary="@string/search_sort_summary"
|
||||||
|
android:key="sort_search_chronologically" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/settings_title_playlist">
|
<PreferenceCategory app:title="@string/settings_title_playlist">
|
||||||
|
|||||||
4
fastlane/metadata/android/en-US/changelogs/10.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/10.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
* fix: updates starred syncing downloads to user defined directory
|
||||||
|
* fix: handle empty albums and null mappings
|
||||||
|
* feat: integrate sort recent searches chronologically
|
||||||
|
* feat: add heart to artist/album pages, fixed artist cover art failing
|
||||||
Reference in New Issue
Block a user