Compare commits
14 Commits
5aa82bec70
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0abfa744ac | ||
| 597374fa3c | |||
| 1a13b66013 | |||
| ad32311de3 | |||
|
|
9bab266925 | ||
| 05f23b8ab1 | |||
|
|
d4d332ec3c | ||
| 0b9e5b75cf | |||
|
|
2a18f86051 | ||
| f8968766bf | |||
| 92ebba1d44 | |||
|
|
e2f82ccd12 | ||
|
|
8d24659bd7 | ||
|
|
2dd84e7c56 |
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -193,7 +193,7 @@
|
||||
</option>
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -68,12 +68,14 @@ android {
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
|
||||
namespace 'com.cappielloantonio.tempo'
|
||||
lint {
|
||||
baseline file('lint-baseline.xml')
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation files('../libs/lib-decoder-ffmpeg-release.aar')
|
||||
|
||||
// AndroidX
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0'
|
||||
@@ -84,6 +86,7 @@ dependencies {
|
||||
implementation 'androidx.room:room-runtime:2.8.4'
|
||||
implementation 'androidx.core:core-splashscreen:1.2.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.1'
|
||||
implementation 'androidx.mediarouter:mediarouter:1.8.1'
|
||||
|
||||
// Android Material
|
||||
implementation 'com.google.android.material:material:1.13.0'
|
||||
@@ -108,4 +111,7 @@ dependencies {
|
||||
implementation 'com.squareup.retrofit2:retrofit:3.0.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.3.2'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:3.0.0'
|
||||
|
||||
// Jellyfin
|
||||
implementation 'org.jellyfin.media3:media3-ffmpeg-decoder:1.8.0+1'
|
||||
}
|
||||
5972
app/lint-baseline.xml
Normal file
5972
app/lint-baseline.xml
Normal file
File diff suppressed because one or more lines are too long
@@ -73,6 +73,29 @@ public class PlaylistRepository {
|
||||
return listLivePlaylistSongs;
|
||||
}
|
||||
|
||||
public MutableLiveData<Boolean> isSongInPlaylist(String playlistId, String songId) {
|
||||
MutableLiveData<Boolean> songInPlayList = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.getPlaylist(playlistId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylist() != null && response.body().getSubsonicResponse().getPlaylist().getEntries() != null) {
|
||||
List<Child> songs = response.body().getSubsonicResponse().getPlaylist().getEntries();
|
||||
songInPlayList.setValue(songs.stream().anyMatch(s -> s.getId().equals(songId)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
}
|
||||
});
|
||||
|
||||
return songInPlayList;
|
||||
}
|
||||
|
||||
public void addSongToPlaylist(String playlistId, ArrayList<String> songsId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
|
||||
@@ -157,7 +157,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
|
||||
albums.sort(Comparator.comparing(AlbumID3::getName));
|
||||
break;
|
||||
case Constants.ALBUM_ORDER_BY_ARTIST:
|
||||
albums.sort(Comparator.comparing(AlbumID3::getArtist));
|
||||
albums.sort(Comparator.comparing(AlbumID3::getArtist, Comparator.nullsLast(Comparator.naturalOrder())));
|
||||
break;
|
||||
case Constants.ALBUM_ORDER_BY_YEAR:
|
||||
albums.sort(Comparator.comparing(AlbumID3::getYear));
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogPlaylistChooserBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
@@ -98,7 +99,23 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
|
||||
@Override
|
||||
public void onPlaylistClick(Bundle bundle) {
|
||||
Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT);
|
||||
playlistChooserViewModel.addSongToPlaylist(playlist.getId());
|
||||
dismiss();
|
||||
|
||||
playlistChooserViewModel.isSongInPlaylist(Objects.requireNonNull(playlist).getId(), requireActivity()).observe(requireActivity(), songInPlaylist -> {
|
||||
if (!songInPlaylist) {
|
||||
playlistChooserViewModel.addSongToPlaylist(playlist.getId());
|
||||
dismiss();
|
||||
} else {
|
||||
PlaylistDuplicateSongDialog playlistDuplicateSongDialog = new PlaylistDuplicateSongDialog(playlist,
|
||||
playlistChooserViewModel.getSongToAdd(),
|
||||
new DialogClickCallback() {
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
playlistChooserViewModel.addSongToPlaylist(playlist.getId());
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
playlistDuplicateSongDialog.show(getActivity().getSupportFragmentManager(), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogPlaylistDuplicateSongBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
public class PlaylistDuplicateSongDialog extends DialogFragment {
|
||||
|
||||
private final Playlist playlist;
|
||||
|
||||
private final Child song;
|
||||
|
||||
private final DialogClickCallback dialogClickCallback;
|
||||
|
||||
public PlaylistDuplicateSongDialog(Playlist playlist, Child song, DialogClickCallback dialogClickCallback) {
|
||||
this.playlist = playlist;
|
||||
this.song = song;
|
||||
this.dialogClickCallback = dialogClickCallback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
DialogPlaylistDuplicateSongBinding bind = DialogPlaylistDuplicateSongBinding.inflate(getLayoutInflater());
|
||||
bind.playlistDuplicateSongDialogSummary
|
||||
.setText(getResources().getString(R.string.playlist_duplicate_song_dialog_summary, song.getTitle(), playlist.getName()));
|
||||
|
||||
return new MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.playlist_duplicate_song_dialog_title)
|
||||
.setPositiveButton(R.string.playlist_duplicate_song_dialog_positive_button, null)
|
||||
.setNegativeButton(R.string.playlist_duplicate_song_dialog_negative_button, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
|
||||
|
||||
if (dialog != null) {
|
||||
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
positiveButton.setOnClickListener(v -> {
|
||||
dialogClickCallback.onPositiveClick();
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||
negativeButton.setOnClickListener(v -> {
|
||||
dialogClickCallback.onNegativeClick();
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,17 +129,22 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (isVisible() && getActivity() != null) {
|
||||
DownloadUtil.getDownloadTracker(requireContext()).download(
|
||||
MappingUtil.mapDownloads(songs),
|
||||
songs.stream().map(child -> {
|
||||
Download toDownload = new Download(child);
|
||||
toDownload.setPlaylistId(playlistPageViewModel.getPlaylist().getId());
|
||||
toDownload.setPlaylistName(playlistPageViewModel.getPlaylist().getName());
|
||||
return toDownload;
|
||||
}).collect(Collectors.toList())
|
||||
MappingUtil.mapDownloads(songs),
|
||||
songs.stream().map(child -> {
|
||||
Download toDownload = new Download(child);
|
||||
toDownload.setPlaylistId(playlistPageViewModel.getPlaylist().getId());
|
||||
toDownload.setPlaylistName(playlistPageViewModel.getPlaylist().getName());
|
||||
return toDownload;
|
||||
}).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_add_to_queue) {
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||
MediaManager.enqueue(mediaBrowserListenableFuture, songs, false);
|
||||
});
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_pin_playlist) {
|
||||
playlistPageViewModel.setPinned(true);
|
||||
return true;
|
||||
|
||||
@@ -175,7 +175,7 @@ object Preferences {
|
||||
|
||||
@JvmStatic
|
||||
fun isInUseServerAddressLocal(): Boolean {
|
||||
return getInUseServerAddress() == getLocalAddress()
|
||||
return getInUseServerAddress() == getLocalAddress() && !getLocalAddress().isNullOrEmpty()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -33,6 +33,13 @@ public class PlaylistChooserViewModel extends AndroidViewModel {
|
||||
return playlists;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> isSongInPlaylist(String playlistId, LifecycleOwner owner) {
|
||||
MutableLiveData<Boolean> songs = new MutableLiveData<>();
|
||||
|
||||
playlistRepository.isSongInPlaylist(playlistId, toAdd.getId()).observe(owner, songs::postValue);
|
||||
return songs;
|
||||
}
|
||||
|
||||
public void addSongToPlaylist(String playlistId) {
|
||||
playlistRepository.addSongToPlaylist(playlistId, new ArrayList(Collections.singletonList(toAdd.getId())));
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:visibility="gone"
|
||||
android:background="?attr/colorSurface"
|
||||
app:menu="@menu/bottom_nav_menu" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
15
app/src/main/res/layout/dialog_playlist_duplicate_song.xml
Normal file
15
app/src/main/res/layout/dialog_playlist_duplicate_song.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_duplicate_song_dialog_summary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -13,6 +13,13 @@
|
||||
android:icon="@drawable/ic_file_download"
|
||||
android:title="@string/menu_download_all_button"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_add_to_queue"
|
||||
android:icon="@drawable/ic_add"
|
||||
android:title="@string/menu_add_to_queue_button"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_pin_playlist"
|
||||
android:icon="@drawable/ic_add"
|
||||
|
||||
@@ -406,4 +406,8 @@
|
||||
<string name="track_info_summary_transcoding_codec">Tempo wird den Server bitten, die Datei zu transkodieren. Der vom Benutzer gewünschte Codec ist %1$s, die Bitrate wird dieselbe wie bei der original Datei sein. Die potentielle Transkodierung der Datei in das gewünschte Format ist vom Server abhängig. Dieser kann die Operation gegebenenfalls nicht unterstützen.</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">Tempo wird den Server bitten, die Bitrate der Datei zu erändern. Die vom Benutzer gewünschte Bitrate ist %1$s, der Codec der Originaldatei wird nicht verändert. Änderungen an der Bitrate der Datei werden vom Server ausgeführt, dieser kann die Operation gegebenenfalls nicht unterstützen.</string>
|
||||
<string name="track_info_summary_full_transcode">Die Anwendung wird den Server bitten die Datei zu transkodieren und die Bitrate zu verändern. Der vom Benutzer gewünschte Codec ist %1$s, mit der Bitrate %2$s. Änderungen am Codec und an der Bitrate der Datei werden vom Server ausgeführt, dieser kann die Operation gegebenenfalls nicht unterstützen.</string>
|
||||
<string name="playlist_duplicate_song_dialog_title">Doppelter Track</string>
|
||||
<string name="playlist_duplicate_song_dialog_summary">Der Track \"%1$s\" existiert bereits in der Playlist \"%2$s\"</string>
|
||||
<string name="playlist_duplicate_song_dialog_positive_button">Trotzdem hinzufügen</string>
|
||||
<string name="playlist_duplicate_song_dialog_negative_button">Nicht hinzufügen</string>
|
||||
</resources>
|
||||
@@ -157,6 +157,7 @@
|
||||
<string name="login_title">Subsonic servers</string>
|
||||
<string name="login_title_expanded">Subsonic servers</string>
|
||||
<string name="media_route_menu_title">Cast</string>
|
||||
<string name="menu_add_to_queue_button">Add to queue</string>
|
||||
<string name="menu_add_button">Add</string>
|
||||
<string name="menu_download_all_button">Download all</string>
|
||||
<string name="menu_download_label">Download</string>
|
||||
@@ -415,4 +416,8 @@
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">A special thanks goes to unDraw without whose illustrations we could not have made this application more beautiful.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="playlist_duplicate_song_dialog_title">Duplicate song</string>
|
||||
<string name="playlist_duplicate_song_dialog_summary">The song \"%1$s\" already exists in playlist \"%2$s\".</string>
|
||||
<string name="playlist_duplicate_song_dialog_positive_button">Add anyway</string>
|
||||
<string name="playlist_duplicate_song_dialog_negative_button">Don\'t add</string>
|
||||
</resources>
|
||||
|
||||
@@ -4,7 +4,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.13.1'
|
||||
classpath 'com.android.tools.build:gradle:8.13.2'
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21'
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user