feat: adds sync starred albums functionality #66

This commit is contained in:
eddyizm 2025-08-30 09:04:25 -07:00
parent cc5abd150a
commit f854f49686
No known key found for this signature in database
GPG key ID: CF5F671829E8158A
9 changed files with 231 additions and 7 deletions

View file

@ -0,0 +1,81 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogStarredSyncBinding;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.StarredAlbumsSyncViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.stream.Collectors;
@OptIn(markerClass = UnstableApi.class)
public class StarredAlbumSyncDialog extends DialogFragment {
private StarredAlbumsSyncViewModel starredAlbumsSyncViewModel;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
DialogStarredSyncBinding bind = DialogStarredSyncBinding.inflate(getLayoutInflater());
starredAlbumsSyncViewModel = new ViewModelProvider(requireActivity()).get(StarredAlbumsSyncViewModel.class);
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.starred_album_sync_dialog_title)
.setPositiveButton(R.string.starred_sync_dialog_positive_button, null)
.setNeutralButton(R.string.starred_sync_dialog_neutral_button, null)
.setNegativeButton(R.string.starred_sync_dialog_negative_button, null)
.create();
}
@Override
public void onResume() {
super.onResume();
setButtonAction(requireContext());
}
private void setButtonAction(Context context) {
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(v -> {
starredAlbumsSyncViewModel.getStarredAlbumSongs(requireActivity()).observe(this, allSongs -> {
if (allSongs != null && !allSongs.isEmpty()) {
DownloadUtil.getDownloadTracker(context).download(
MappingUtil.mapDownloads(allSongs),
allSongs.stream().map(Download::new).collect(Collectors.toList())
);
}
dialog.dismiss();
});
});
Button neutralButton = dialog.getButton(Dialog.BUTTON_NEUTRAL);
neutralButton.setOnClickListener(v -> {
Preferences.setStarredAlbumsSyncEnabled(true);
dialog.dismiss();
});
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
negativeButton.setOnClickListener(v -> {
Preferences.setStarredAlbumsSyncEnabled(false);
dialog.dismiss();
});
}
}
}

View file

@ -31,6 +31,7 @@ import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
import com.cappielloantonio.tempo.ui.dialog.StarredAlbumSyncDialog;
import com.cappielloantonio.tempo.ui.dialog.StreamingCacheStorageDialog;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.Preferences;
@ -94,6 +95,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
actionLogout();
actionScan();
actionSyncStarredAlbums();
actionSyncStarredTracks();
actionChangeStreamingCacheStorage();
actionChangeDownloadStorage();
@ -263,6 +265,18 @@ public class SettingsFragment extends PreferenceFragmentCompat {
});
}
private void actionSyncStarredAlbums() {
findPreference("sync_starred_albums_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredAlbumSyncDialog dialog = new StarredAlbumSyncDialog();
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
private void actionChangeStreamingCacheStorage() {
findPreference("streaming_cache_storage").setOnPreferenceClickListener(preference -> {
StreamingCacheStorageDialog dialog = new StreamingCacheStorageDialog(new DialogClickCallback() {

View file

@ -102,7 +102,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
favoriteToggle.setOnClickListener(v -> {
albumBottomSheetViewModel.setFavorite();
albumBottomSheetViewModel.setFavorite(requireContext());
});
TextView playRadio = view.findViewById(R.id.play_radio_text_view);

View file

@ -37,6 +37,7 @@ object Preferences {
private const val WIFI_ONLY = "wifi_only"
private const val DATA_SAVING_MODE = "data_saving_mode"
private const val SERVER_UNREACHABLE = "server_unreachable"
private const val SYNC_STARRED_ALBUMS_FOR_OFFLINE_USE = "sync_starred_albums_for_offline_use"
private const val SYNC_STARRED_TRACKS_FOR_OFFLINE_USE = "sync_starred_tracks_for_offline_use"
private const val QUEUE_SYNCING = "queue_syncing"
private const val QUEUE_SYNCING_COUNTDOWN = "queue_syncing_countdown"
@ -301,6 +302,18 @@ object Preferences {
.apply()
}
@JvmStatic
fun isStarredAlbumsSyncEnabled(): Boolean {
return App.getInstance().preferences.getBoolean(SYNC_STARRED_ALBUMS_FOR_OFFLINE_USE, false)
}
@JvmStatic
fun setStarredAlbumsSyncEnabled(isStarredSyncEnabled: Boolean) {
App.getInstance().preferences.edit().putBoolean(
SYNC_STARRED_ALBUMS_FOR_OFFLINE_USE, isStarredSyncEnabled
).apply()
}
@JvmStatic
fun isStarredSyncEnabled(): Boolean {
return App.getInstance().preferences.getBoolean(SYNC_STARRED_TRACKS_FOR_OFFLINE_USE, false)

View file

@ -1,12 +1,15 @@
package com.cappielloantonio.tempo.viewmodel;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.interfaces.StarCallback;
import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository;
@ -16,10 +19,14 @@ 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.Share;
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.stream.Collectors;
public class AlbumBottomSheetViewModel extends AndroidViewModel {
private final AlbumRepository albumRepository;
@ -54,7 +61,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
return albumRepository.getAlbumTracks(album.getId());
}
public void setFavorite() {
public void setFavorite(Context context) {
if (album.getStarred() != null) {
if (NetworkUtil.isOffline()) {
removeFavoriteOffline();
@ -65,7 +72,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
if (NetworkUtil.isOffline()) {
setFavoriteOffline();
} else {
setFavoriteOnline();
setFavoriteOnline(context);
}
}
}
@ -83,7 +90,6 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
favoriteRepository.unstar(null, album.getId(), null, new StarCallback() {
@Override
public void onError() {
// album.setStarred(new Date());
favoriteRepository.starLater(null, album.getId(), null, false);
}
});
@ -96,15 +102,31 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
album.setStarred(new Date());
}
private void setFavoriteOnline() {
private void setFavoriteOnline(Context context) {
favoriteRepository.star(null, album.getId(), null, new StarCallback() {
@Override
public void onError() {
// album.setStarred(null);
favoriteRepository.starLater(null, album.getId(), null, true);
}
});
album.setStarred(new Date());
if (Preferences.isStarredAlbumsSyncEnabled()) {
AlbumRepository albumRepository = new AlbumRepository();
MutableLiveData<List<Child>> tracksLiveData = albumRepository.getAlbumTracks(album.getId());
tracksLiveData.observeForever(new Observer<List<Child>>() {
@Override
public void onChanged(List<Child> songs) {
if (songs != null && !songs.isEmpty()) {
DownloadUtil.getDownloadTracker(context).download(
MappingUtil.mapDownloads(songs),
songs.stream().map(Download::new).collect(Collectors.toList())
);
}
tracksLiveData.removeObserver(this);
}
});
}
}
}

View file

@ -0,0 +1,70 @@
package com.cappielloantonio.tempo.viewmodel;
import android.app.Application;
import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class StarredAlbumsSyncViewModel extends AndroidViewModel {
private final AlbumRepository albumRepository;
private final MutableLiveData<List<AlbumID3>> starredAlbums = new MutableLiveData<>(null);
private final MutableLiveData<List<Child>> starredAlbumSongs = new MutableLiveData<>(null);
public StarredAlbumsSyncViewModel(@NonNull Application application) {
super(application);
albumRepository = new AlbumRepository();
}
public LiveData<List<AlbumID3>> getStarredAlbums(LifecycleOwner owner) {
albumRepository.getStarredAlbums(false, -1).observe(owner, starredAlbums::postValue);
return starredAlbums;
}
public LiveData<List<Child>> getStarredAlbumSongs(Activity activity) {
albumRepository.getStarredAlbums(false, -1).observe((LifecycleOwner) activity, albums -> {
if (albums != null && !albums.isEmpty()) {
collectAllAlbumSongs(albums, starredAlbumSongs::postValue);
} else {
starredAlbumSongs.postValue(new ArrayList<>());
}
});
return starredAlbumSongs;
}
private void collectAllAlbumSongs(List<AlbumID3> albums, AlbumSongsCallback callback) {
List<Child> allSongs = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(albums.size());
for (AlbumID3 album : albums) {
albumRepository.getAlbumTracks(album.getId()).observeForever(songs -> {
if (songs != null) {
allSongs.addAll(songs);
}
latch.countDown();
if (latch.getCount() == 0) {
callback.onSongsCollected(allSongs);
}
});
}
albumRepository.removeObserver(this);
}
private interface AlbumSongsCallback {
void onSongsCollected(List<Child> songs);
}
}