25 Commits

Author SHA1 Message Date
Anna Teier
0abfa744ac chore: agp update 2025-12-12 16:40:01 +01:00
597374fa3c fix: missing mediarouter dependency 2025-12-08 21:52:29 +01:00
1a13b66013 fix: lint baseline 2025-12-08 21:48:46 +01:00
ad32311de3 fix: background color of BottomNavigationViw 2025-12-07 22:06:56 +01:00
fyr77
9bab266925 Feat: replace custom ffmpeg with jellyfin maven build 2025-12-07 21:47:07 +01:00
05f23b8ab1 Merge pull request 'Fix crash when sorting albums with a null artist' (#3) from fix-album-sort-crash into main
Reviewed-on: #3
2025-12-07 20:24:46 +00:00
Robin Slot
d4d332ec3c Fix crash when sorting albums with a null artist 2025-12-07 21:24:09 +01:00
0b9e5b75cf Merge pull request 'Add "Add to queue" menu button to Playlist Page' (#2) from add-playlist-to-queue-menu-button into main
Reviewed-on: #2
2025-12-07 20:20:09 +00:00
Matthew Simpson
2a18f86051 Add "Add to queue" menu button to Playlist Page 2025-12-07 21:17:11 +01:00
f8968766bf Merge pull request 'add-to-playlist-check-duplicate' (#1) from add-to-playlist-check-duplicate into main
Reviewed-on: #1
2025-12-07 20:04:00 +00:00
92ebba1d44 fix: isSonginPlayList condition 2025-12-07 20:54:46 +01:00
Jonas Resch
e2f82ccd12 Revert code format change 2025-12-07 20:46:39 +01:00
Jonas Resch
8d24659bd7 Add to playlist: check if song already exists in playlist and display a dialog if it does 2025-12-07 20:46:39 +01:00
Jonas Resch
2dd84e7c56 Fix: App crashes on second open when local address empty 2025-12-07 20:46:39 +01:00
5aa82bec70 fix: add compileSdk again 2025-12-07 20:38:19 +01:00
d293e3bcfe feat: dep updates 2025-12-07 19:34:07 +01:00
CappielloAntonio
9cf62c8c0c gradle: update gradle build tools 2025-01-31 11:05:39 +01:00
CappielloAntonio
330556ec33 Merge remote-tracking branch 'origin/main' 2025-01-31 10:32:53 +01:00
CappielloAntonio
4c8c5ce120 gradle: dependencies update 2025-01-31 10:32:39 +01:00
CappielloAntonio
55ae9a8442 fix: added missing decorators 2025-01-31 10:32:00 +01:00
CappielloAntonio
f8a53c7db2 Update README.md 2024-12-31 16:55:21 +01:00
CappielloAntonio
b58cae1ecd Update README.md 2024-12-31 16:51:37 +01:00
CappielloAntonio
e305f20811 fix: updated workflow 2024-12-31 12:54:28 +01:00
CappielloAntonio
c8c1bcfd3e fix: null checking 2024-12-30 17:23:55 +01:00
CappielloAntonio
e1c96d278f fix: null checking 2024-12-30 17:20:13 +01:00
33 changed files with 6201 additions and 54 deletions

View File

@@ -52,7 +52,7 @@ jobs:
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
- name: Make artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: app-release-signed
path: ${{steps.sign_apk.outputs.signedReleaseFile}}

1
.idea/gradle.xml generated
View File

@@ -13,7 +13,6 @@
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>

1
.idea/misc.xml generated
View File

@@ -192,6 +192,7 @@
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<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>

View File

@@ -8,6 +8,8 @@
<p align="center">
<a href="https://github.com/CappielloAntonio/tempo/releases"><img src="https://i.ibb.co/q0mdc4Z/get-it-on-github.png" width="200"></a>
</p>
<p align="center">
<a href="https://f-droid.org/packages/com.cappielloantonio.notquitemy.tempo"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" width="200"></a>
<a href="https://apt.izzysoft.de/fdroid/index/apk/com.cappielloantonio.tempo"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="200"></a>
</p>
@@ -18,6 +20,8 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
**If you find Tempo useful, please consider starring the project on GitHub. It would mean a lot to me and help promote the app to a wider audience.**
**Use the Github version of the app for full Android Auto and Chromecast support.**
## Features
- **Subsonic Integration**: Tempo seamlessly integrates with your Subsonic server, providing you with easy access to your entire music collection on the go.
- **Sleek and Intuitive UI**: Enjoy a clean and user-friendly interface designed to enhance your music listening experience, tailored to your preferences and listening history.
@@ -29,6 +33,7 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
- **Scrobbling Integration**: Optionally integrate Tempo with Last.fm to scrobble your played tracks, gather music insights, and further personalize your music recommendations, if supported by your Subsonic server.
- **Podcasts and Radio**: If your Subsonic server supports it, listen to podcasts and radio shows directly within Tempo, expanding your audio entertainment options.
- **Transcoding Support**: Activate transcoding of tracks on your Subsonic server, allowing you to set a transcoding profile for optimized streaming directly from the app. This feature requires support from your Subsonic server.
- **Android Auto Support**: Enjoy your favorite music on the go with full Android Auto integration, allowing you to seamlessly control and listen to your tracks directly from your mobile device while driving.
<p align="center">
<img src="mockup/feat/1_screenshot.png" width=200>

View File

@@ -3,15 +3,16 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
android {
compileSdk 35
buildToolsVersion = '35.0.0'
compileSdk 35
defaultConfig {
minSdkVersion 24
targetSdk 35
versionCode 25
versionName '3.8.1'
versionCode 26
versionName '3.9.0'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -67,44 +68,50 @@ 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.0'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.8.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.8.5'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.room:room-runtime:2.6.1'
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.9.6'
implementation 'androidx.navigation:navigation-ui-ktx:2.9.6'
implementation 'androidx.recyclerview:recyclerview:1.4.0'
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.10.0'
implementation 'com.google.android.material:material:1.13.0'
// Glide
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation 'com.github.bumptech.glide:annotations:4.16.0'
implementation 'com.github.bumptech.glide:glide:5.0.5'
implementation 'com.github.bumptech.glide:annotations:5.0.5'
// Media3
implementation 'androidx.media3:media3-session:1.5.1'
implementation 'androidx.media3:media3-common:1.5.1'
implementation 'androidx.media3:media3-exoplayer:1.5.1'
implementation 'androidx.media3:media3-ui:1.5.1'
implementation 'androidx.media3:media3-exoplayer-hls:1.5.1'
tempoImplementation 'androidx.media3:media3-cast:1.5.1'
playImplementation 'androidx.media3:media3-cast:1.5.1'
implementation 'androidx.media3:media3-session:1.8.0'
implementation 'androidx.media3:media3-common:1.8.0'
implementation 'androidx.media3:media3-exoplayer:1.8.0'
implementation 'androidx.media3:media3-ui:1.8.0'
implementation 'androidx.media3:media3-exoplayer-hls:1.8.0'
tempoImplementation 'androidx.media3:media3-cast:1.8.0'
playImplementation 'androidx.media3:media3-cast:1.8.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
annotationProcessor 'androidx.room:room-compiler:2.6.1'
annotationProcessor 'com.github.bumptech.glide:compiler:5.0.5'
annotationProcessor 'androidx.room:room-compiler:2.8.4'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
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

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,9 @@
package com.cappielloantonio.tempo.github.models
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class Assets(
@SerializedName("url")
var url: String? = null,

View File

@@ -1,7 +1,9 @@
package com.cappielloantonio.tempo.github.models
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class Author(
@SerializedName("login")
var login: String? = null,

View File

@@ -1,7 +1,9 @@
package com.cappielloantonio.tempo.github.models
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class LatestRelease(
@SerializedName("url")
var url: String? = null,

View File

@@ -1,7 +1,9 @@
package com.cappielloantonio.tempo.github.models
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class Reactions(
@SerializedName("url")
var url: String? = null,

View File

@@ -1,7 +1,9 @@
package com.cappielloantonio.tempo.github.models
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class Uploader(
@SerializedName("login")
var login: String? = null,

View File

@@ -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()

View File

@@ -40,7 +40,7 @@ class RetrofitClient(subsonic: Subsonic) {
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(getHttpLoggingInterceptor())
// .addInterceptor(getHttpLoggingInterceptor())
.addInterceptor(cacheUtil.offlineInterceptor)
// .addNetworkInterceptor(cacheUtil.onlineInterceptor)
.cache(getCache())

View File

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

View File

@@ -50,7 +50,7 @@ public class ArtistCatalogueAdapter extends RecyclerView.Adapter<ArtistCatalogue
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
artists.clear();
artists.addAll((List) results.values);
if (results.count > 0) artists.addAll((List) results.values);
notifyDataSetChanged();
}
};

View File

@@ -13,7 +13,6 @@ import com.cappielloantonio.tempo.databinding.ItemLibraryCatalogueGenreBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Genre;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections;
@@ -49,7 +48,7 @@ public class GenreCatalogueAdapter extends RecyclerView.Adapter<GenreCatalogueAd
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
genres.clear();
genres.addAll((List) results.values);
if (results.count > 0) genres.addAll((List) results.values);
notifyDataSetChanged();
}
};

View File

@@ -54,7 +54,7 @@ public class PlaylistHorizontalAdapter extends RecyclerView.Adapter<PlaylistHori
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
playlists.clear();
playlists.addAll((List) results.values);
if (results.count > 0) playlists.addAll((List) results.values);
notifyDataSetChanged();
}
};

View File

@@ -48,7 +48,7 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
podcastChannels.clear();
podcastChannels.addAll((List) results.values);
if (results.count > 0) podcastChannels.addAll((List) results.values);
notifyDataSetChanged();
}
};

View File

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

View File

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

View File

@@ -105,7 +105,7 @@ public class GenreCatalogueFragment extends Fragment implements ClickCallback {
genreCatalogueAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.genreCatalogueRecyclerView.setAdapter(genreCatalogueAdapter);
genreCatalogueViewModel.getGenreList().observe(getViewLifecycleOwner(), genres -> genreCatalogueAdapter.setItems(genres));
genreCatalogueViewModel.getGenreList().observe(getViewLifecycleOwner(), genres -> genreCatalogueAdapter.setItems(genres) );
bind.genreCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);

View File

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

View File

@@ -175,7 +175,7 @@ object Preferences {
@JvmStatic
fun isInUseServerAddressLocal(): Boolean {
return getInUseServerAddress() == getLocalAddress()
return getInUseServerAddress() == getLocalAddress() && !getLocalAddress().isNullOrEmpty()
}
@JvmStatic

View File

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

View File

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

View 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>

View File

@@ -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"

View File

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

View File

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

View File

@@ -4,8 +4,8 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.3'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0'
classpath 'com.android.tools.build:gradle:8.13.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21'
}
}

View File

@@ -1,6 +1,6 @@
#Wed Nov 06 17:17:57 CET 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Binary file not shown.