3 Commits

Author SHA1 Message Date
Jonas Resch
737017d662 Revert code format change 2024-06-10 18:07:53 +02:00
Jonas Resch
db2facc370 Add to playlist: check if song already exists in playlist and display a dialog if it does 2024-06-10 17:59:48 +02:00
Jonas Resch
0557b7ca6f Fix: App crashes on second open when local address empty 2024-06-10 14:18:52 +02:00
78 changed files with 514 additions and 9164 deletions

View File

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

2
.idea/compiler.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
<bytecodeTargetLevel target="17" />
</component>
</project>

2
.idea/gradle.xml generated
View File

@@ -4,7 +4,6 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
@@ -13,6 +12,7 @@
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>

3
.idea/misc.xml generated
View File

@@ -191,8 +191,7 @@
</map>
</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_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@@ -8,8 +8,6 @@
<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>
@@ -20,8 +18,6 @@ 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.
@@ -33,7 +29,6 @@ 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,16 +3,15 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
android {
buildToolsVersion = '35.0.0'
compileSdk 35
compileSdk = 34
buildToolsVersion = '34.0.0'
defaultConfig {
minSdkVersion 24
targetSdk 35
targetSdkVersion 34
versionCode 26
versionName '3.9.0'
versionCode 25
versionName '3.8.1'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -38,11 +37,6 @@ android {
dimension = "default"
applicationId "com.cappielloantonio.notquitemy.tempo"
}
play {
dimension = "default"
applicationId "com.cappielloantonio.play.tempo"
}
}
buildTypes {
@@ -65,11 +59,6 @@ android {
buildFeatures {
viewBinding true
buildConfig true
}
lintOptions {
baseline file("lint-baseline.xml")
}
namespace 'com.cappielloantonio.tempo'
@@ -77,41 +66,36 @@ android {
dependencies {
// AndroidX
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
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'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
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'
// Android Material
implementation 'com.google.android.material:material:1.13.0'
implementation 'com.google.android.material:material:1.10.0'
// Glide
implementation 'com.github.bumptech.glide:glide:5.0.5'
implementation 'com.github.bumptech.glide:annotations:5.0.5'
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation 'com.github.bumptech.glide:annotations:4.16.0'
// Media3
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'
implementation 'androidx.media3:media3-session:1.3.1'
implementation 'androidx.media3:media3-common:1.3.1'
implementation 'androidx.media3:media3-exoplayer:1.3.1'
implementation 'androidx.media3:media3-ui:1.3.1'
implementation 'androidx.media3:media3-exoplayer-hls:1.3.1'
tempoImplementation 'androidx.media3:media3-cast:1.3.1'
annotationProcessor 'com.github.bumptech.glide:compiler:5.0.5'
annotationProcessor 'androidx.room:room-compiler:2.8.4'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
annotationProcessor 'androidx.room:room-compiler:2.6.1'
// Retrofit
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'
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'
}

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,7 @@
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,9 +1,7 @@
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,9 +1,7 @@
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,9 +1,7 @@
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,9 +1,7 @@
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

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

@@ -4,8 +4,6 @@ import android.os.Parcelable
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.time.Instant
import java.time.LocalDate
import java.util.*
@Keep
@@ -19,12 +17,12 @@ open class AlbumID3 : Parcelable {
var coverArtId: String? = null
var songCount: Int? = 0
var duration: Int? = 0
var playCount: Long? = 0
var playCount: Long? = null
var created: Date? = null
var starred: Date? = null
var year: Int = 0
var genre: String? = null
var played: Date? = Date(0)
var played: String? = null
var userRating: Int? = 0
var recordLabels: List<RecordLabel>? = null
var musicBrainzId: String? = null

View File

@@ -2,8 +2,7 @@ package com.cappielloantonio.tempo.subsonic.utils;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import com.cappielloantonio.tempo.App;
@@ -40,19 +39,7 @@ public class CacheUtil {
private boolean isConnected() {
ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
Network network = connectivityManager.getActiveNetwork();
if (network != null) {
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
if (capabilities != null) {
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
}
}
}
return false;
NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
return (netInfo != null && netInfo.isConnected());
}
}

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, Comparator.nullsLast(Comparator.naturalOrder())));
albums.sort(Comparator.comparing(AlbumID3::getArtist));
break;
case Constants.ALBUM_ORDER_BY_YEAR:
albums.sort(Comparator.comparing(AlbumID3::getYear));
@@ -169,14 +169,6 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
albums.sort(Comparator.comparing(AlbumID3::getCreated));
Collections.reverse(albums);
break;
case Constants.ALBUM_ORDER_BY_RECENTLY_PLAYED:
albums.sort(Comparator.comparing(AlbumID3::getPlayed));
Collections.reverse(albums);
break;
case Constants.ALBUM_ORDER_BY_MOST_PLAYED:
albums.sort(Comparator.comparing(AlbumID3::getPlayCount));
Collections.reverse(albums);
break;
}
notifyDataSetChanged();

View File

@@ -3,8 +3,6 @@ package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
@@ -13,60 +11,22 @@ import com.cappielloantonio.tempo.databinding.ItemHorizontalAlbumBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontalAdapter.ViewHolder> implements Filterable {
public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private final boolean isOffline;
private List<AlbumID3> albumsFull;
private List<AlbumID3> albums;
private String currentFilter;
private final Filter filtering = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<AlbumID3> filteredList = new ArrayList<>();
if (constraint == null || constraint.length() == 0) {
filteredList.addAll(albumsFull);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
currentFilter = filterPattern;
for (AlbumID3 item : albumsFull) {
if (item.getName().toLowerCase().contains(filterPattern)) {
filteredList.add(item);
}
}
}
FilterResults results = new FilterResults();
results.values = filteredList;
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
albums = (List<AlbumID3>) results.values;
notifyDataSetChanged();
}
};
public AlbumHorizontalAdapter(ClickCallback click, boolean isOffline) {
this.click = click;
this.isOffline = isOffline;
this.albums = Collections.emptyList();
this.albumsFull = Collections.emptyList();
this.currentFilter = "";
}
@NonNull
@@ -95,16 +55,10 @@ public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontal
}
public void setItems(List<AlbumID3> albums) {
this.albumsFull = albums != null ? albums : Collections.emptyList();
filtering.filter(currentFilter);
this.albums = albums;
notifyDataSetChanged();
}
@Override
public Filter getFilter() {
return filtering;
}
public AlbumID3 getItem(int id) {
return albums.get(id);
}
@@ -141,21 +95,4 @@ public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontal
return true;
}
}
public void sort(String order) {
switch (order) {
case Constants.ALBUM_ORDER_BY_NAME:
albums.sort(Comparator.comparing(AlbumID3::getName));
break;
case Constants.ALBUM_ORDER_BY_MOST_RECENTLY_STARRED:
albums.sort(Comparator.comparing(AlbumID3::getStarred, Comparator.nullsLast(Comparator.reverseOrder())));
break;
case Constants.ALBUM_ORDER_BY_LEAST_RECENTLY_STARRED:
albums.sort(Comparator.comparing(AlbumID3::getStarred, Comparator.nullsLast(Comparator.naturalOrder())));
break;
}
notifyDataSetChanged();
}
}

View File

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

View File

@@ -4,8 +4,6 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
@@ -13,59 +11,21 @@ import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHorizontalArtistBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizontalAdapter.ViewHolder> implements Filterable {
public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private List<ArtistID3> artistsFull;
private List<ArtistID3> artists;
private String currentFilter;
private final Filter filtering = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<ArtistID3> filteredList = new ArrayList<>();
if (constraint == null || constraint.length() == 0) {
filteredList.addAll(artistsFull);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
currentFilter = filterPattern;
for (ArtistID3 item : artistsFull) {
if (item.getName().toLowerCase().contains(filterPattern)) {
filteredList.add(item);
}
}
}
FilterResults results = new FilterResults();
results.values = filteredList;
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
artists = (List<ArtistID3>) results.values;
notifyDataSetChanged();
}
};
public ArtistHorizontalAdapter(ClickCallback click) {
this.click = click;
this.artists = Collections.emptyList();
this.artistsFull = Collections.emptyList();
this.currentFilter = "";
}
@NonNull
@@ -99,16 +59,10 @@ public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizont
}
public void setItems(List<ArtistID3> artists) {
this.artistsFull = artists != null ? artists : Collections.emptyList();
filtering.filter(currentFilter);
this.artists = artists;
notifyDataSetChanged();
}
@Override
public Filter getFilter() {
return filtering;
}
public ArtistID3 getItem(int id) {
return artists.get(id);
}
@@ -155,21 +109,4 @@ public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizont
return true;
}
}
public void sort(String order) {
switch (order) {
case Constants.ARTIST_ORDER_BY_NAME:
artists.sort(Comparator.comparing(ArtistID3::getName));
break;
case Constants.ARTIST_ORDER_BY_MOST_RECENTLY_STARRED:
artists.sort(Comparator.comparing(ArtistID3::getStarred, Comparator.nullsLast(Comparator.reverseOrder())));
break;
case Constants.ARTIST_ORDER_BY_LEAST_RECENTLY_STARRED:
artists.sort(Comparator.comparing(ArtistID3::getStarred, Comparator.nullsLast(Comparator.naturalOrder())));
break;
}
notifyDataSetChanged();
}
}

View File

@@ -13,6 +13,7 @@ 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;
@@ -48,7 +49,7 @@ public class GenreCatalogueAdapter extends RecyclerView.Adapter<GenreCatalogueAd
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
genres.clear();
if (results.count > 0) genres.addAll((List) results.values);
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();
if (results.count > 0) playlists.addAll((List) results.values);
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();
if (results.count > 0) podcastChannels.addAll((List) results.values);
podcastChannels.addAll((List) results.values);
notifyDataSetChanged();
}
};

View File

@@ -40,7 +40,7 @@ public class ShareHorizontalAdapter extends RecyclerView.Adapter<ShareHorizontal
public void onBindViewHolder(ViewHolder holder, int position) {
Share share = shares.get(position);
holder.item.shareTitleTextView.setText(share.getDescription());
holder.item.shareTitleTextView.setText(MusicUtil.getReadableString(share.getDescription()));
holder.item.shareSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.share_subtitle_item, UIUtil.getReadableDate(share.getExpires())));
if (share.getEntries() != null && !share.getEntries().isEmpty()) CustomGlideRequest.Builder

View File

@@ -4,8 +4,6 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
@@ -26,60 +24,24 @@ import com.cappielloantonio.tempo.util.Preferences;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@UnstableApi
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> implements Filterable {
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private final boolean showCoverArt;
private final boolean showAlbum;
private final AlbumID3 album;
private List<Child> songsFull;
private List<Child> songs;
private String currentFilter;
private final Filter filtering = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<Child> filteredList = new ArrayList<>();
if (constraint == null || constraint.length() == 0) {
filteredList.addAll(songsFull);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
currentFilter = filterPattern;
for (Child item : songsFull) {
if (item.getTitle().toLowerCase().contains(filterPattern)) {
filteredList.add(item);
}
}
}
FilterResults results = new FilterResults();
results.values = filteredList;
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
songs = (List<Child>) results.values;
notifyDataSetChanged();
}
};
public SongHorizontalAdapter(ClickCallback click, boolean showCoverArt, boolean showAlbum, AlbumID3 album) {
this.click = click;
this.showCoverArt = showCoverArt;
this.showAlbum = showAlbum;
this.songs = Collections.emptyList();
this.songsFull = Collections.emptyList();
this.currentFilter = "";
this.album = album;
}
@@ -133,10 +95,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
)
) {
holder.item.differentDiskDividerSector.setVisibility(View.VISIBLE);
if (songs.get(position).getDiscNumber() != null && !Objects.requireNonNull(songs.get(position).getDiscNumber()).toString().isBlank()) {
holder.item.discTitleTextView.setText(holder.itemView.getContext().getString(R.string.disc_titleless, songs.get(position).getDiscNumber().toString()));
}
holder.item.discTitleTextView.setText(holder.itemView.getContext().getString(R.string.disc_titleless, songs.get(position).getDiscNumber().toString()));
if (album.getDiscTitles() != null) {
Optional<DiscTitle> discTitle = album.getDiscTitles().stream().filter(title -> Objects.equals(title.getDisc(), songs.get(position).getDiscNumber())).findFirst();
@@ -173,8 +132,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
}
public void setItems(List<Child> songs) {
this.songsFull = songs != null ? songs : Collections.emptyList();
filtering.filter(currentFilter);
this.songs = songs != null ? songs : Collections.emptyList();
notifyDataSetChanged();
}
@@ -188,11 +146,6 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
return position;
}
@Override
public Filter getFilter() {
return filtering;
}
public Child getItem(int id) {
return songs.get(id);
}
@@ -231,20 +184,4 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
return true;
}
}
public void sort(String order) {
switch (order) {
case Constants.MEDIA_BY_TITLE:
songs.sort(Comparator.comparing(Child::getTitle));
break;
case Constants.MEDIA_MOST_RECENTLY_STARRED:
songs.sort(Comparator.comparing(Child::getStarred, Comparator.nullsLast(Comparator.reverseOrder())));
break;
case Constants.MEDIA_LEAST_RECENTLY_STARRED:
songs.sort(Comparator.comparing(Child::getStarred, Comparator.nullsLast(Comparator.naturalOrder())));
break;
}
notifyDataSetChanged();
}
}

View File

@@ -101,7 +101,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT);
playlistChooserViewModel.isSongInPlaylist(Objects.requireNonNull(playlist).getId(), requireActivity()).observe(requireActivity(), songInPlaylist -> {
if (!songInPlaylist) {
if (songInPlaylist) {
playlistChooserViewModel.addSongToPlaylist(playlist.getId());
dismiss();
} else {

View File

@@ -103,8 +103,8 @@ public class ServerSignupDialog extends DialogFragment {
serverName = Objects.requireNonNull(bind.serverNameTextView.getText()).toString().trim();
username = Objects.requireNonNull(bind.usernameTextView.getText()).toString().trim();
password = bind.lowSecurityCheckbox.isChecked() ? MusicUtil.passwordHexEncoding(Objects.requireNonNull(bind.passwordTextView.getText()).toString()) : Objects.requireNonNull(bind.passwordTextView.getText()).toString();
server = bind.serverTextView.getText() != null && !bind.serverTextView.getText().toString().trim().isBlank() ? bind.serverTextView.getText().toString().trim() : null;
localAddress = bind.localAddressTextView.getText() != null && !bind.localAddressTextView.getText().toString().trim().isBlank() ? bind.localAddressTextView.getText().toString().trim() : null;
server = Objects.requireNonNull(bind.serverTextView.getText()).toString().trim();
localAddress = Objects.requireNonNull(bind.localAddressTextView.getText()).toString().trim();
lowSecurity = bind.lowSecurityCheckbox.isChecked();
if (TextUtils.isEmpty(serverName)) {

View File

@@ -187,12 +187,6 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
} else if (menuItem.getItemId() == R.id.menu_album_sort_recently_added) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_RECENTLY_ADDED);
return true;
} else if (menuItem.getItemId() == R.id.menu_album_sort_recently_played) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_RECENTLY_PLAYED);
return true;
} else if (menuItem.getItemId() == R.id.menu_album_sort_most_played) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_MOST_PLAYED);
return true;
}
return false;

View File

@@ -1,21 +1,11 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
@@ -27,14 +17,12 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentAlbumListPageBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumListPageViewModel;
import java.util.List;
@OptIn(markerClass = UnstableApi.class)
public class AlbumListPageFragment extends Fragment implements ClickCallback {
private FragmentAlbumListPageBinding bind;
@@ -43,12 +31,6 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
private AlbumListPageViewModel albumListPageViewModel;
private AlbumHorizontalAdapter albumHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
@@ -104,10 +86,7 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
@@ -118,7 +97,6 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
});
}
@SuppressLint("ClickableViewAccessibility")
private void initAlbumListView() {
bind.albumListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.albumListRecyclerView.setHasFixedSize(true);
@@ -129,99 +107,7 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
);
bind.albumListRecyclerView.setAdapter(albumHorizontalAdapter);
albumListPageViewModel.getAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
albumHorizontalAdapter.setItems(albums);
setAlbumListPageSubtitle(albums);
setAlbumListPageSorter();
});
bind.albumListRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.albumListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_horizontal_album_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
albumHorizontalAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_horizontal_album_sort_name) {
albumHorizontalAdapter.sort(Constants.ALBUM_ORDER_BY_NAME);
return true;
} else if (menuItem.getItemId() == R.id.menu_horizontal_album_sort_most_recently_starred) {
albumHorizontalAdapter.sort(Constants.ALBUM_ORDER_BY_MOST_RECENTLY_STARRED);
return true;
} else if (menuItem.getItemId() == R.id.menu_horizontal_album_sort_least_recently_starred) {
albumHorizontalAdapter.sort(Constants.ALBUM_ORDER_BY_LEAST_RECENTLY_STARRED);
return true;
}
return false;
});
popup.show();
}
private void setAlbumListPageSubtitle(List<AlbumID3> albums) {
switch (albumListPageViewModel.title) {
case Constants.ALBUM_RECENTLY_PLAYED:
case Constants.ALBUM_MOST_PLAYED:
case Constants.ALBUM_RECENTLY_ADDED:
bind.pageSubtitleLabel.setText(albums.size() < albumListPageViewModel.maxNumber ?
getString(R.string.generic_list_page_count, albums.size()) :
getString(R.string.generic_list_page_count_unknown, albumListPageViewModel.maxNumber)
);
break;
case Constants.ALBUM_STARRED:
bind.pageSubtitleLabel.setText(getString(R.string.generic_list_page_count, albums.size()));
break;
}
}
private void setAlbumListPageSorter() {
switch (albumListPageViewModel.title) {
case Constants.ALBUM_RECENTLY_PLAYED:
case Constants.ALBUM_MOST_PLAYED:
case Constants.ALBUM_RECENTLY_ADDED:
bind.albumListSortImageView.setVisibility(View.GONE);
break;
case Constants.ALBUM_STARRED:
bind.albumListSortImageView.setVisibility(View.VISIBLE);
break;
}
albumListPageViewModel.getAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> albumHorizontalAdapter.setItems(albums));
}
@Override

View File

@@ -182,7 +182,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
if (albumInfo != null) {
if (bind != null) bind.albumNotesTextview.setVisibility(View.VISIBLE);
if (bind != null)
bind.albumNotesTextview.setText(MusicUtil.forceReadableString(albumInfo.getNotes()));
bind.albumNotesTextview.setText(MusicUtil.getReadableString(albumInfo.getNotes()));
if (bind != null && albumInfo.getLastFmUrl() != null && !albumInfo.getLastFmUrl().isEmpty()) {
bind.albumNotesTextview.setOnClickListener(v -> {

View File

@@ -1,21 +1,11 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
@@ -26,15 +16,11 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentArtistListPageBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.ArtistListPageViewModel;
import java.util.List;
@UnstableApi
public class ArtistListPageFragment extends Fragment implements ClickCallback {
private FragmentArtistListPageBinding bind;
@@ -44,12 +30,6 @@ public class ArtistListPageFragment extends Fragment implements ClickCallback {
private ArtistHorizontalAdapter artistHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
@@ -89,10 +69,7 @@ public class ArtistListPageFragment extends Fragment implements ClickCallback {
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.artistInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
@@ -103,100 +80,18 @@ public class ArtistListPageFragment extends Fragment implements ClickCallback {
});
}
@SuppressLint("ClickableViewAccessibility")
private void initArtistListView() {
bind.artistListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.artistListRecyclerView.setHasFixedSize(true);
artistHorizontalAdapter = new ArtistHorizontalAdapter(this);
bind.artistListRecyclerView.setAdapter(artistHorizontalAdapter);
artistListPageViewModel.getArtistList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
artistHorizontalAdapter.setItems(artists);
setArtistListPageSubtitle(artists);
setArtistListPageSorter();
});
bind.artistListRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.artistListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_horizontal_artist_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
artistHorizontalAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_horizontal_artist_sort_name) {
artistHorizontalAdapter.sort(Constants.ARTIST_ORDER_BY_NAME);
return true;
} else if (menuItem.getItemId() == R.id.menu_horizontal_artist_sort_most_recently_starred) {
artistHorizontalAdapter.sort(Constants.ARTIST_ORDER_BY_MOST_RECENTLY_STARRED);
return true;
} else if (menuItem.getItemId() == R.id.menu_horizontal_artist_sort_least_recently_starred) {
artistHorizontalAdapter.sort(Constants.ARTIST_ORDER_BY_LEAST_RECENTLY_STARRED);
return true;
}
return false;
});
popup.show();
}
private void setArtistListPageSubtitle(List<ArtistID3> artists) {
switch (artistListPageViewModel.title) {
case Constants.ARTIST_STARRED:
case Constants.ARTIST_DOWNLOADED:
bind.pageSubtitleLabel.setText(getString(R.string.generic_list_page_count, artists.size()));
break;
}
}
private void setArtistListPageSorter() {
switch (artistListPageViewModel.title) {
case Constants.ARTIST_STARRED:
case Constants.ARTIST_DOWNLOADED:
bind.artistListSortImageView.setVisibility(View.VISIBLE);
break;
}
artistListPageViewModel.getArtistList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> artistHorizontalAdapter.setItems(artists));
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
Navigation.findNavController(requireView()).navigate(R.id.albumListPageFragment, bundle);
}
@Override

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

@@ -123,7 +123,6 @@ public class LoginFragment extends Fragment implements ClickCallback {
systemRepository.checkUserCredential(new SystemCallback() {
@Override
public void onError(Exception exception) {
Preferences.switchInUseServerAddress();
resetServerPreference();
Toast.makeText(requireContext(), exception.getMessage(), Toast.LENGTH_SHORT).show();
}

View File

@@ -156,7 +156,6 @@ public class PlayerBottomSheetFragment extends Fragment {
private void setMetadata(MediaMetadata mediaMetadata) {
if (mediaMetadata.extras != null) {
playerBottomSheetViewModel.setLiveMedia(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("id"));
playerBottomSheetViewModel.setLiveAlbum(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("albumId"));
playerBottomSheetViewModel.setLiveArtist(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("artistId"));
playerBottomSheetViewModel.setLiveDescription(mediaMetadata.extras.getString("description", null));

View File

@@ -76,7 +76,6 @@ public class PlayerControllerFragment extends Fragment {
initQuickActionView();
initCoverLyricsSlideView();
initMediaListenable();
initMediaLabelButton();
initArtistLabelButton();
return view;
@@ -300,19 +299,6 @@ public class PlayerControllerFragment extends Fragment {
});
}
private void initMediaLabelButton() {
playerBottomSheetViewModel.getLiveAlbum().observe(getViewLifecycleOwner(), album -> {
if (album != null) {
playerMediaTitleLabel.setOnClickListener(view -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, album);
NavHostFragment.findNavController(this).navigate(R.id.albumPageFragment, bundle);
activity.collapseBottomSheetDelayed();
});
}
});
}
private void initArtistLabelButton() {
playerBottomSheetViewModel.getLiveArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {

View File

@@ -1,7 +1,6 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -9,9 +8,6 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -62,28 +58,8 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.playlist_page_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
songHorizontalAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
initMenuOption(menu);
}
@@ -129,22 +105,17 @@ 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;
@@ -181,19 +152,11 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
bind.playlistSongCountLabel.setText(getString(R.string.playlist_song_count, playlistPageViewModel.getPlaylist().getSongCount()));
bind.playlistDurationLabel.setText(getString(R.string.playlist_duration, MusicUtil.getReadableDurationString(playlistPageViewModel.getPlaylist().getDuration(), false)));
bind.animToolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
Objects.requireNonNull(bind.animToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void initMusicButton() {
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) {

View File

@@ -1,22 +1,12 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
@@ -32,15 +22,14 @@ import com.cappielloantonio.tempo.helper.recyclerview.PaginationScrollListener;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.SongListPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.List;
@UnstableApi
public class SongListPageFragment extends Fragment implements ClickCallback {
@@ -56,12 +45,6 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
private boolean isLoading = true;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
@@ -155,10 +138,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
}
if (bind != null)
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
if (bind != null)
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
@@ -173,8 +153,6 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
private void initButtons() {
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) {
setSongListPageSorter();
bind.songListShuffleImageView.setOnClickListener(v -> {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(25, songs.size())), 0);
@@ -184,7 +162,6 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
});
}
@SuppressLint("ClickableViewAccessibility")
private void initSongListView() {
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songListRecyclerView.setHasFixedSize(true);
@@ -194,7 +171,6 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
isLoading = false;
songHorizontalAdapter.setItems(songs);
setSongListPageSubtitle(songs);
});
bind.songListRecyclerView.addOnScrollListener(new PaginationScrollListener((LinearLayoutManager) bind.songListRecyclerView.getLayoutManager()) {
@@ -209,101 +185,6 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
return isLoading;
}
});
bind.songListRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.songListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_song_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
songHorizontalAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_song_sort_name) {
songHorizontalAdapter.sort(Constants.MEDIA_BY_TITLE);
return true;
} else if (menuItem.getItemId() == R.id.menu_song_sort_most_recently_starred) {
songHorizontalAdapter.sort(Constants.MEDIA_MOST_RECENTLY_STARRED);
return true;
} else if (menuItem.getItemId() == R.id.menu_song_sort_least_recently_starred) {
songHorizontalAdapter.sort(Constants.MEDIA_LEAST_RECENTLY_STARRED);
return true;
}
return false;
});
popup.show();
}
private void setSongListPageSubtitle(List<Child> children) {
switch (songListPageViewModel.title) {
case Constants.MEDIA_BY_GENRE:
bind.pageSubtitleLabel.setText(children.size() < songListPageViewModel.maxNumberByGenre ?
getString(R.string.generic_list_page_count, children.size()) :
getString(R.string.generic_list_page_count_unknown, songListPageViewModel.maxNumberByGenre)
);
break;
case Constants.MEDIA_BY_YEAR:
bind.pageSubtitleLabel.setText(children.size() < songListPageViewModel.maxNumberByYear ?
getString(R.string.generic_list_page_count, children.size()) :
getString(R.string.generic_list_page_count_unknown, songListPageViewModel.maxNumberByYear)
);
break;
case Constants.MEDIA_BY_ARTIST:
case Constants.MEDIA_BY_GENRES:
case Constants.MEDIA_STARRED:
bind.pageSubtitleLabel.setText(getString(R.string.generic_list_page_count, children.size()));
break;
}
}
private void setSongListPageSorter() {
switch (songListPageViewModel.title) {
case Constants.MEDIA_BY_GENRE:
case Constants.MEDIA_BY_YEAR:
bind.songListSortImageView.setVisibility(View.GONE);
break;
case Constants.MEDIA_BY_ARTIST:
case Constants.MEDIA_BY_GENRES:
case Constants.MEDIA_STARRED:
bind.songListSortImageView.setVisibility(View.VISIBLE);
break;
}
}
private void initializeMediaBrowser() {
@@ -316,7 +197,6 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
@Override
public void onMediaClick(Bundle bundle) {
hideKeyboard(requireView());
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}

View File

@@ -31,17 +31,11 @@ object Constants {
const val ALBUM_ORDER_BY_YEAR = "ALBUM_ORDER_BY_YEAR"
const val ALBUM_ORDER_BY_RANDOM = "ALBUM_ORDER_BY_RANDOM"
const val ALBUM_ORDER_BY_RECENTLY_ADDED = "ALBUM_ORDER_BY_RECENTLY_ADDED"
const val ALBUM_ORDER_BY_RECENTLY_PLAYED = "ALBUM_ORDER_BY_RECENTLY_PLAYED"
const val ALBUM_ORDER_BY_MOST_PLAYED = "ALBUM_ORDER_BY_MOST_PLAYED"
const val ALBUM_ORDER_BY_MOST_RECENTLY_STARRED = "ALBUM_ORDER_BY_MOST_RECENTLY_STARRED"
const val ALBUM_ORDER_BY_LEAST_RECENTLY_STARRED = "ALBUM_ORDER_BY_LEAST_RECENTLY_STARRED"
const val ARTIST_DOWNLOADED = "ARTIST_DOWNLOADED"
const val ARTIST_STARRED = "ARTIST_STARRED"
const val ARTIST_ORDER_BY_NAME = "ARTIST_ORDER_BY_NAME"
const val ARTIST_ORDER_BY_RANDOM = "ARTIST_ORDER_BY_RANDOM"
const val ARTIST_ORDER_BY_MOST_RECENTLY_STARRED = "ARTIST_ORDER_BY_MOST_RECENTLY_STARRED"
const val ARTIST_ORDER_BY_LEAST_RECENTLY_STARRED = "ARTIST_ORDER_BY_LEAST_RECENTLY_STARRED"
const val GENRE_ORDER_BY_NAME = "GENRE_ORDER_BY_NAME"
const val GENRE_ORDER_BY_RANDOM = "GENRE_ORDER_BY_RANDOM"
@@ -80,9 +74,6 @@ object Constants {
const val MEDIA_MIX = "MEDIA_MIX"
const val MEDIA_CHRONOLOGY = "MEDIA_CHRONOLOGY"
const val MEDIA_BEST_OF = "MEDIA_BEST_OF"
const val MEDIA_BY_TITLE = "MEDIA_BY_TITLE"
const val MEDIA_MOST_RECENTLY_STARRED = "MEDIA_MOST_RECENTLY_STARRED"
const val MEDIA_LEAST_RECENTLY_STARRED = "MEDIA_LEAST_RECENTLY_STARRED"
const val DOWNLOAD_URI = "rest/download"

View File

@@ -53,7 +53,8 @@ public final class DownloadUtil {
private static DownloadNotificationHelper downloadNotificationHelper;
public static boolean useExtensionRenderers() {
return true;
// return true;
return false;
}
public static RenderersFactory buildRenderersFactory(Context context, boolean preferExtensionRenderer) {

View File

@@ -157,7 +157,7 @@ public class MusicUtil {
}
public static String getReadableAudioQualityString(Child child) {
if (!Preferences.showAudioQuality() || child.getBitrate() == null) return "";
if (!Preferences.showAudioQuality()) return "";
return "" +
" " +
@@ -179,14 +179,6 @@ public class MusicUtil {
}
}
public static String getReadableTrackNumber(Context context, Integer trackNumber) {
if (trackNumber != null) {
return String.valueOf(trackNumber);
}
return context.getString(R.string.label_placeholder);
}
public static String getReadableString(String string) {
if (string != null) {
return Html.fromHtml(string, Html.FROM_HTML_MODE_COMPACT).toString();
@@ -195,6 +187,14 @@ public class MusicUtil {
return "";
}
public static String getReadableTrackNumber(Context context, Integer trackNumber) {
if (trackNumber != null) {
return String.valueOf(trackNumber);
}
return context.getString(R.string.label_placeholder);
}
public static String forceReadableString(String string) {
if (string != null) {
return getReadableString(string)

View File

@@ -2,8 +2,6 @@ package com.cappielloantonio.tempo.util;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import com.cappielloantonio.tempo.App;
@@ -13,15 +11,9 @@ public class NetworkUtil {
ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
Network network = connectivityManager.getActiveNetwork();
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (network != null) {
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
if (capabilities != null) {
return !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) || !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
}
}
return networkInfo == null || !networkInfo.isConnected();
}
return true;

View File

@@ -68,6 +68,7 @@ object Preferences {
@JvmStatic
fun getServer(): String? {
Log.d("Preferences++", "getServer()")
return App.getInstance().preferences.getString(SERVER, null)
}
@@ -168,9 +169,7 @@ object Preferences {
@JvmStatic
fun getInUseServerAddress(): String? {
return App.getInstance().preferences.getString(IN_USE_SERVER_ADDRESS, null)
?.takeIf { it.isNotBlank() }
?: getServer()
return App.getInstance().preferences.getString(IN_USE_SERVER_ADDRESS, getServer())
}
@JvmStatic

View File

@@ -28,8 +28,6 @@ public class AlbumListPageViewModel extends AndroidViewModel {
private MutableLiveData<List<AlbumID3>> albumList;
public int maxNumber = 500;
public AlbumListPageViewModel(@NonNull Application application) {
super(application);
@@ -42,20 +40,20 @@ public class AlbumListPageViewModel extends AndroidViewModel {
switch (title) {
case Constants.ALBUM_RECENTLY_PLAYED:
albumRepository.getAlbums("recent", maxNumber, null, null).observe(owner, albums -> albumList.setValue(albums));
albumRepository.getAlbums("recent", 500, null, null).observe(owner, albums -> albumList.setValue(albums));
break;
case Constants.ALBUM_MOST_PLAYED:
albumRepository.getAlbums("frequent", maxNumber, null, null).observe(owner, albums -> albumList.setValue(albums));
albumRepository.getAlbums("frequent", 500, null, null).observe(owner, albums -> albumList.setValue(albums));
break;
case Constants.ALBUM_RECENTLY_ADDED:
albumRepository.getAlbums("newest", maxNumber, null, null).observe(owner, albums -> albumList.setValue(albums));
albumRepository.getAlbums("newest", 500, null, null).observe(owner, albums -> albumList.setValue(albums));
break;
case Constants.ALBUM_STARRED:
albumList = albumRepository.getStarredAlbums(false, -1);
break;
case Constants.ALBUM_NEW_RELEASES:
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
albumRepository.getAlbums("byYear", maxNumber, currentYear, currentYear).observe(owner, albums -> {
albumRepository.getAlbums("byYear", 500, currentYear, currentYear).observe(owner, albums -> {
albums.sort(Comparator.comparing(AlbumID3::getCreated).reversed());
albumList.postValue(albums.subList(0, Math.min(20, albums.size())));
});

View File

@@ -14,13 +14,11 @@ import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.interfaces.StarCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.model.Queue;
import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.FavoriteRepository;
import com.cappielloantonio.tempo.repository.OpenRepository;
import com.cappielloantonio.tempo.repository.QueueRepository;
import com.cappielloantonio.tempo.repository.SongRepository;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.LyricsList;
@@ -42,7 +40,6 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
private static final String TAG = "PlayerBottomSheetViewModel";
private final SongRepository songRepository;
private final AlbumRepository albumRepository;
private final ArtistRepository artistRepository;
private final QueueRepository queueRepository;
private final FavoriteRepository favoriteRepository;
@@ -51,7 +48,6 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
private final MutableLiveData<LyricsList> lyricsListLiveData = new MutableLiveData<>(null);
private final MutableLiveData<String> descriptionLiveData = new MutableLiveData<>(null);
private final MutableLiveData<Child> liveMedia = new MutableLiveData<>(null);
private final MutableLiveData<AlbumID3> liveAlbum = new MutableLiveData<>(null);
private final MutableLiveData<ArtistID3> liveArtist = new MutableLiveData<>(null);
private final MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(null);
private boolean lyricsSyncState = true;
@@ -61,7 +57,6 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
super(application);
songRepository = new SongRepository();
albumRepository = new AlbumRepository();
artistRepository = new ArtistRepository();
queueRepository = new QueueRepository();
favoriteRepository = new FavoriteRepository();
@@ -167,23 +162,6 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
}
}
public LiveData<AlbumID3> getLiveAlbum() {
return liveAlbum;
}
public void setLiveAlbum(LifecycleOwner owner, String mediaType, String AlbumId) {
if (mediaType != null) {
switch (mediaType) {
case Constants.MEDIA_TYPE_MUSIC:
albumRepository.getAlbum(AlbumId).observe(owner, liveAlbum::postValue);
break;
case Constants.MEDIA_TYPE_PODCAST:
liveAlbum.postValue(null);
break;
}
}
}
public LiveData<ArtistID3> getLiveArtist() {
return liveArtist;
}

View File

@@ -36,8 +36,6 @@ public class SongListPageViewModel extends AndroidViewModel {
public ArrayList<String> filterNames = new ArrayList<>();
public int year = 0;
public int maxNumberByYear = 500;
public int maxNumberByGenre = 100;
public SongListPageViewModel(@NonNull Application application) {
super(application);
@@ -60,7 +58,7 @@ public class SongListPageViewModel extends AndroidViewModel {
songList = songRepository.getSongsByGenres(filters);
break;
case Constants.MEDIA_BY_YEAR:
songList = songRepository.getRandomSample(maxNumberByYear, year, year + 10);
songList = songRepository.getRandomSample(500, year, year + 10);
break;
case Constants.MEDIA_STARRED:
songList = songRepository.getStarredSongs(false, -1);
@@ -75,9 +73,9 @@ public class SongListPageViewModel extends AndroidViewModel {
case Constants.MEDIA_BY_GENRE:
int songCount = songList.getValue() != null ? songList.getValue().size() : 0;
if (songCount > 0 && songCount % maxNumberByGenre != 0) return;
if (songCount > 0 && songCount % 100 != 0) return;
int page = songCount / maxNumberByGenre;
int page = songCount / 100;
songRepository.getSongsByGenre(genre.getGenre(), page).observe(owner, children -> {
if (children != null && !children.isEmpty()) {
List<Child> currentMedia = songList.getValue();

View File

@@ -36,7 +36,6 @@
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

@@ -33,48 +33,17 @@
<TextView
android:id="@+id/page_title_label"
style="@style/TitleLarge"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="4dp"
android:paddingEnd="16dp"
android:paddingBottom="24dp"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/page_subtitle_label"
style="@style/TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:paddingBottom="24dp"
app:layout_constraintTop_toTopOf="@id/page_title_label"
app:layout_constraintBottom_toBottomOf="@id/page_title_label"
app:layout_constraintStart_toEndOf="@id/page_title_label" />
<Button
android:id="@+id/album_list_sort_image_view"
style="@style/Widget.Material3.Button.TonalButton.Icon"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
app:cornerRadius="30dp"
android:visibility="gone"
app:icon="@drawable/ic_sort_list"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>

View File

@@ -33,48 +33,17 @@
<TextView
android:id="@+id/page_title_label"
style="@style/TitleLarge"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="4dp"
android:paddingEnd="16dp"
android:paddingBottom="24dp"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/page_subtitle_label"
style="@style/TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:paddingBottom="24dp"
app:layout_constraintTop_toTopOf="@id/page_title_label"
app:layout_constraintBottom_toBottomOf="@id/page_title_label"
app:layout_constraintStart_toEndOf="@id/page_title_label" />
<Button
android:id="@+id/artist_list_sort_image_view"
style="@style/Widget.Material3.Button.TonalButton.Icon"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
app:cornerRadius="30dp"
android:visibility="gone"
app:icon="@drawable/ic_sort_list"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>

View File

@@ -33,48 +33,17 @@
<TextView
android:id="@+id/page_title_label"
style="@style/TitleLarge"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="4dp"
android:paddingEnd="16dp"
android:paddingBottom="24dp"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/song_list_shuffle_image_view"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/page_subtitle_label"
style="@style/TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:paddingBottom="24dp"
app:layout_constraintTop_toTopOf="@id/page_title_label"
app:layout_constraintBottom_toBottomOf="@id/page_title_label"
app:layout_constraintStart_toEndOf="@id/page_title_label" />
<Button
android:id="@+id/song_list_sort_image_view"
style="@style/Widget.Material3.Button.TonalButton.Icon"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="12dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:visibility="gone"
app:cornerRadius="30dp"
app:icon="@drawable/ic_sort_list"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/song_list_shuffle_image_view" />
<Button
android:id="@+id/song_list_shuffle_image_view"
style="@style/Widget.Material3.Button.TonalButton.Icon"

View File

@@ -27,9 +27,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:paddingEnd="12dp"
android:layout_marginEnd="12dp"
android:singleLine="true"
tools:text="@string/label_placeholder"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/different_disk_divider"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -1,25 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search"
android:title="@string/menu_search_button"
app:actionViewClass="android.widget.SearchView"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_download_playlist"
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

@@ -15,10 +15,4 @@
<item
android:id="@+id/menu_album_sort_recently_added"
android:title="@string/menu_sort_recently_added" />
<item
android:id="@+id/menu_album_sort_recently_played"
android:title="@string/menu_sort_recently_played" />
<item
android:id="@+id/menu_album_sort_most_played"
android:title="@string/menu_sort_most_played" />
</menu>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_horizontal_album_sort_name"
android:title="@string/menu_sort_name" />
<item
android:id="@+id/menu_horizontal_album_sort_most_recently_starred"
android:title="@string/menu_sort_most_recently_starred" />
<item
android:id="@+id/menu_horizontal_album_sort_least_recently_starred"
android:title="@string/menu_sort_least_recently_starred" />
</menu>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_horizontal_artist_sort_name"
android:title="@string/menu_sort_name" />
<item
android:id="@+id/menu_horizontal_artist_sort_most_recently_starred"
android:title="@string/menu_sort_most_recently_starred" />
<item
android:id="@+id/menu_horizontal_artist_sort_least_recently_starred"
android:title="@string/menu_sort_least_recently_starred" />
</menu>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_song_sort_name"
android:title="@string/menu_sort_name" />
<item
android:id="@+id/menu_song_sort_most_recently_starred"
android:title="@string/menu_sort_most_recently_starred" />
<item
android:id="@+id/menu_song_sort_least_recently_starred"
android:title="@string/menu_sort_least_recently_starred" />
</menu>

View File

@@ -8,4 +8,5 @@
android:title="@string/menu_search_button"
app:actionViewClass="android.widget.SearchView"
app:showAsAction="ifRoom" />
</menu>

View File

@@ -23,10 +23,7 @@
<string name="album_list_page_title">Alben</string>
<string name="album_page_extra_info_button">Ähnliches</string>
<string name="album_page_play_button">Wiedergabe</string>
<string name="album_page_release_date_label">Veröffentlicht am %1$s</string>
<string name="album_page_release_dates_label">Veröffentlicht am %1$s, ursprünglich %2$s</string>
<string name="album_page_shuffle_button">Zufällige Wiedergabe</string>
<string name="album_page_tracks_count_and_duration">%1$d Tracks • %2$d Minuten</string>
<string name="app_name">Tempo</string>
<string name="artist_adapter_radio_station_starting">Suche…</string>
<string name="artist_bottom_sheet_instant_mix">Instant mix</string>
@@ -55,23 +52,15 @@
<string name="connection_alert_dialog_positive_button">OK</string>
<string name="connection_alert_dialog_summary">Der Zugriff auf den Subsonic server ohne Wi-Fi Verbindung ist deaktiviert. Du kannst das in den App-Einstellungen ändern.</string>
<string name="connection_alert_dialog_title">Wi-Fi ist nicht verbuden</string>
<string name="content_description_shuffle_button">Mischen</string>
<string name="delete_download_storage_dialog_negative_button">Abbrechen</string>
<string name="delete_download_storage_dialog_positive_button">Weiter</string>
<string name="delete_download_storage_dialog_summary">Wenn Du weitermachst werden alle zuvor heruntergeladenen Inhalte gelöscht.</string>
<string name="delete_download_storage_dialog_title">Heruntergeladene Inhalte löschen</string>
<string name="description_empty_title">Keine Beschreibung verfügbar</string>
<string name="disc_titlefull">Disk %1$s - %2$s</string>
<string name="disc_titleless">Disk %1$s</string>
<string name="download_directory_dialog_negative_button">Abbrechen</string>
<string name="download_directory_dialog_positive_button">Herunterladen</string>
<string name="download_directory_dialog_summary">Alle Tracks in diesem Verzeichnis werden heruntergeladen. Tracks in Unterverzeichnisses werden nicht heruntergeladen.</string>
<string name="download_directory_dialog_title">Tracks herunterladen</string>
<string name="download_info_empty_subtitle">Wenn Du einen Track heruntergeladen hast findest Du ihn hier</string>
<string name="download_info_empty_title">Bisher keine Downloads!</string>
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s items</string>
<string name="download_item_single_subtitle_formatter">%1$s items</string>
<string name="download_shuffle_all_subtitle">Alle mischen</string>
<string name="download_storage_dialog_sub_summary">Neustart der Anwendung ist nötig.</string>
<string name="download_storage_dialog_summary">Das Ändern des Speicherorts löscht alle Inhalte im zuvor gewählten Speicherort.</string>
<string name="download_storage_dialog_title">Wähle den Speicherort aus</string>
@@ -92,16 +81,6 @@
<string name="filter_title_expanded">Genres filtern</string>
<string name="genre_catalogue_title">Genre Übersicht</string>
<string name="genre_catalogue_title_expanded">Genres durchsuchen</string>
<string name="github_update_dialog_negative_button">Später erinnern</string>
<string name="github_update_dialog_neutral_button">Unterstütze mich</string>
<string name="github_update_dialog_positive_button">Jetzt herunterladen</string>
<string name="github_update_dialog_summary">Es gibt eine neue Version der App auf Github.</string>
<string name="github_update_dialog_title">Update verfügbar</string>
<string name="home_rearrangement_dialog_negative_button">Abbrechen</string>
<string name="home_rearrangement_dialog_neutral_button">Zurücksetzen</string>
<string name="home_rearrangement_dialog_positive_button">Sichern</string>
<string name="home_rearrangement_dialog_title">Startseite anpassen</string>
<string name="home_rearrangement_dialog_subtitle">Die Anwendung muss neu gestartet werden, um die Änderungen auszuführen.</string>
<string name="home_subtitle_best_of">Top Tracks Deiner Lieblingskünstler</string>
<string name="home_subtitle_made_for_you">Ein Mix von einem deiner Lieblingslieder erstellen</string>
<string name="home_subtitle_new_internet_radio_station">Radio hinzufügen</string>
@@ -118,14 +97,11 @@
<string name="home_title_last_played">Zuletzt gespielt</string>
<string name="home_title_last_played_see_all_button">Alle zeigen</string>
<string name="home_title_last_week">Letzte Woche</string>
<string name="home_title_last_month">Letzter Monat</string>
<string name="home_title_last_year">Letztes Jahr</string>
<string name="home_title_made_for_you">Wie für Dich gemacht</string>
<string name="home_title_most_played">Oft gespielt</string>
<string name="home_title_most_played_see_all_button">Alle zeigen</string>
<string name="home_title_new_releases">Neue Releases</string>
<string name="home_title_newest_podcasts">Neueste Podcasts</string>
<string name="home_title_pinned_playlists">Playlisten</string>
<string name="home_title_podcast_channels">Kanäle</string>
<string name="home_title_podcast_channels_see_all_button">Alle zeigen</string>
<string name="home_title_radio_station">Radio Stationen</string>
@@ -139,7 +115,6 @@
<string name="home_title_starred_tracks">★ Lieblingslieder</string>
<string name="home_title_starred_tracks_see_all_button">Alle zeigen</string>
<string name="home_title_top_songs">Deine Top Songs</string>
<string name="home_option_reorganize">Neu anordnen</string>
<string name="library_title_album">Alben</string>
<string name="library_title_album_see_all_button">Alle zeigen</string>
<string name="library_title_artist">Künstler</string>
@@ -164,21 +139,14 @@
<string name="menu_group_by_track">Track</string>
<string name="menu_group_by_year">Jahr</string>
<string name="menu_home_label">Start</string>
<string name="menu_last_week_name">Letzte Woche</string>
<string name="menu_last_month_name">Letzter Monat</string>
<string name="menu_last_year_name">Letztes Jahr</string>
<string name="menu_library_label">Sammlung</string>
<string name="menu_search_button">Suche</string>
<string name="menu_settings_button">Einstellungen</string>
<string name="menu_sort_artist">Künstler</string>
<string name="menu_sort_name">Name</string>
<string name="menu_sort_random">Zufall</string>
<string name="menu_sort_recently_added">Vor kurzem hinzugefügt</string>
<string name="menu_pin_button">Zur Startseite hinzufügen</string>
<string name="menu_unpin_button">Von Startseite entfernen</string>
<string name="menu_sort_year">Jahr</string>
<string name="player_playback_speed">%1$.2fx</string>
<string name="player_queue_clean_all_button">Warteschlange leeren</string>
<string name="player_server_priority">Server Priorität</string>
<string name="playlist_catalogue_title">Playlisten</string>
<string name="playlist_catalogue_title_expanded">Playlisten durchsuchen</string>
@@ -188,7 +156,6 @@
<string name="playlist_chooser_dialog_title">Zu einer Playliste hinzufügen</string>
<string name="playlist_counted_tracks">%1$d Tracks • %2$s</string>
<string name="playlist_duration">Länge • %1$s</string>
<string name="playlist_editor_dialog_action_delete_toast">Zum Löschen lange drücken</string>
<string name="playlist_editor_dialog_hint_name">Name der Playliste</string>
<string name="playlist_editor_dialog_negative_button">Abbrechen</string>
<string name="playlist_editor_dialog_neutral_button">Löschen</string>
@@ -234,8 +201,6 @@
<string name="search_title_artist">Künstler</string>
<string name="search_title_song">Tracks</string>
<string name="server_signup_dialog_action_low_security">Niedrige Sicherheit</string>
<string name="server_signup_dialog_action_delete_toast">Zum Löschen lange drücken</string>
<string name="server_signup_dialog_hint_local_address">Lokale URL</string>
<string name="server_signup_dialog_hint_name">Server Name</string>
<string name="server_signup_dialog_hint_password">Passwort</string>
<string name="server_signup_dialog_hint_url">Server URL</string>
@@ -251,7 +216,6 @@
<string name="server_unreachable_dialog_title">Server nicht erreichbar</string>
<string name="settings_about_summary">Tempo ist ein nativ für Android entwickelter, leichtgewichtiger Open-Source Client für Subsonic.</string>
<string name="settings_about_title">Über</string>
<string name="settings_always_on_display">Immer anzeigen</string>
<string name="settings_audio_transcode_download_format">Transkodierungs-Format</string>
<string name="settings_audio_transcode_download_priority_summary">Diese Option deaktiviert die Transkodierungssettings für Downloads.</string>
<string name="settings_audio_transcode_download_priority_title">Transkodierungseinstellungen des Servers für Downloads bevorzugen.</string>
@@ -262,10 +226,6 @@
<string name="settings_audio_transcode_priority_summary">Diese Option deaktiviert die weiter unten folgenden Transkodierungseinstellungen.</string>
<string name="settings_audio_transcode_priority_title">Transkodierungseinstellungen des Servers bevorzugen</string>
<string name="settings_audio_transcode_priority_toast">Servereinstellungen zur Transkodierung des Tracks werden bevorzugt</string>
<string name="settings_buffering_strategy">Strategie zum Zwischenspeichern</string>
<string name="settings_buffering_strategy_summary">Zum Anwenden muss die Anwendung neugestartet werden.</string>
<string name="settings_continuous_play_summary">Nach dem Ende der Playliste ähnliche Songs abspielen.</string>
<string name="settings_continuous_play_title">Kontinuierliche Wiedergabe</string>
<string name="settings_audio_transcode_format_download">Transkodierungs-Format für Downloads</string>
<string name="settings_audio_transcode_format_mobile">Transkodierungsformat im mobilen Netz</string>
<string name="settings_audio_transcode_format_wifi">Transkodierungsformat im Wi-Fi</string>
@@ -291,10 +251,6 @@
<string name="settings_music_directory_summary">Zeige den Bereich für Musikverzeichnisse. Der Server muss das Feature unterstützen.</string>
<string name="settings_podcast">Podcasts anzeigen</string>
<string name="settings_podcast_summary">Zeige den Bereich für Podcasts.</string>
<string name="settings_audio_quality">Zeige Audioqualität</string>
<string name="settings_audio_quality_summary">Die Bitrate und das Audio-Format werden für jeden einzelnen Track angezeigt.</string>
<string name="settings_item_rating">Track Bewertung anzeigen</string>
<string name="settings_item_rating_summary">Zeigt die Bewertung an und ob der Track als Favorit gekennzeichnet ist.</string>
<string name="settings_queue_syncing_countdown">Timer synchronisieren</string>
<string name="settings_queue_syncing_summary">Der Benutzer kann seine Warteschlange speichern und beim Neustart der Anwendung wiederherstellen.</string>
<string name="settings_queue_syncing_title">Warteschlange für diesen User synchronisieren</string>
@@ -306,17 +262,10 @@
<string name="settings_rounded_corner_size_summary">Definiert den Eckenradius.</string>
<string name="settings_rounded_corner_summary">Abgerundete Ecken für alle gerenderten Cover. Anwendungsneustart ist notwendig.</string>
<string name="settings_scan_title">Sammlung scannen</string>
<string name="settings_scrobble_title">Musik Scrobbeln aktivieren</string>
<string name="settings_share_title">Teilen von Musik aktivieren</string>
<string name="settings_streaming_cache_size">Größe des Streaming-Caches</string>
<string name="settings_streaming_cache_storage_title">Streaming cache Speicher</string>
<string name="settings_sub_summary_scrobble">Scrobbeln setzt voraus, dass der Server dies unterstützt.</string>
<string name="settings_summary_skip_min_star_rating">Tracks unterhalb einer bestimmten Bewertung werden beim Abspielen eines Künstlerradios, eines Instant-Mix und beim zufälligen Mischen aller Tracks ignoriert.</string>
<string name="settings_summary_replay_gain">Replay-Gain ist ein Feature, das die Lautstärke von Tracks für ein konsistentes Hörerlebnis anpasst. Diese Einstellung funktioniert nur, wenn Tracks die entsprechenden Metadaten haben.</string>
<string name="settings_summary_scrobble">Scrobbeln ist ein Feature, das es erlaubt, Informationen über abgespielte Munsik an einen Musikserver zu senden. Diese Informationen können für personalisierte Empfehlungen genutzt werden.</string>
<string name="settings_summary_share">Diese Option erlaubt es dem Benutzer, Musik mit einem Link zu teilen. Die Funktionalität muss vom Server unterstützt und aktiviert sein und ist auf einzelne Titel, Alben und Wiedergabelisten beschränkt.</string>
<string name="settings_summary_syncing">Den Zustand der Warteschlange synchronisieren. Das beinhaltet die Tracks in der Warteschlange, den aktuell gespielten Track und die Position innerhalb dieses Tracks. Der Server muss dieses Feature unterstützen.</string>
<string name="settings_summary_streaming_cache_size">%1$s \Momentan benutzt: %2$s MiB</string>
<string name="settings_summary_transcoding">Priorität des Transkodierungsmodus. \"Direktes Abspielen\" ändert die Bitrate der Dateien nicht.</string>
<string name="settings_summary_transcoding_estimate_content_length">When der Titel während des Abspielens transkodiert wird, When the file is transcoded on the fly, zeigt die App normalerweise keine Titellänge an. Es ist möglich einen Server, der dies unterstützt, zu bitten, die Titellänge zu schätzen. Die Antwortzeiten könnten sich verlängern.</string>
<string name="settings_summary_transcoding_download">Transkodierte Medien herunterladen. Diese Option deaktiviert den Download-Endpoint und benutzt stattdessen die folgenden Einstellungen. \n\n If \"Transkodierungs-Format\" ist auf \"Direktes Abspielen\" gesetzt, Die Bitrate des Tracks wird nicht geändert.</string>
@@ -325,11 +274,7 @@
<string name="settings_theme">Design</string>
<string name="settings_title_data">Daten</string>
<string name="settings_title_general">Allgemein</string>
<string name="settings_title_rating">Bewertung</string>
<string name="settings_title_replay_gain">Replay Gain</string>
<string name="settings_title_scrobble">Scrobble</string>
<string name="settings_title_skip_min_star_rating">Tracks basierend auf ihrer Wertung ignorieren</string>
<string name="settings_title_skip_min_star_rating_dialog">Tracks mit einer Bewertung von:</string>
<string name="settings_title_share">Teilen</string>
<string name="settings_title_syncing">Sychronisierung</string>
<string name="settings_title_transcoding">Transkodierung</string>
@@ -374,12 +319,6 @@
<string name="starred_sync_dialog_positive_button">Weiter und Herunterladen</string>
<string name="starred_sync_dialog_summary">Das Herunterladen deiner Lieblingslieder kann viel Datenvolumen verbrauchen.</string>
<string name="starred_sync_dialog_title">Lieblingslieder synchronisieren</string>
<string name="streaming_cache_storage_dialog_sub_summary">Anwendung neustarten, um die Änderungen anzuwenden.</string>
<string name="streaming_cache_storage_dialog_summary">Das Ändern des Speicherorts kann zum Verlust von zuvor gecachter Daten am neuen Speicherort führen.</string>
<string name="streaming_cache_storage_dialog_title">Speicherort auswählen</string>
<string name="streaming_cache_storage_external_dialog_positive_button">Extern</string>
<string name="streaming_cache_storage_internal_dialog_negative_button">Intern</string>
<string name="support_url">https://buymeacoffee.com/a.cappiello</string>
<string name="undraw_page">unDraw</string>
<string name="undraw_thanks">Besonders möchten wir uns bei unDraw bedanken, durch deren Illustrationen wir diese App so schön machen konnten.</string>
<string name="undraw_url">https://undraw.co/</string>

View File

@@ -78,7 +78,7 @@
<string name="downloaded_bottom_sheet_shuffle">Mélanger</string>
<string name="empty_string" />
<string name="error_required">Requis</string>
<string name="error_server_prefix">préfixe http ou https requis</string>
<string name="error_server_prefix">prefix http ou https requis</string>
<string name="exo_download_notification_channel_name">Téléchargements</string>
<string name="filter_info_selection">Sélectionnez deux filtres ou plus</string>
<string name="filter_title">Filtrer</string>
@@ -201,7 +201,7 @@
<string name="rating_dialog_positive_button">Enregistrer</string>
<string name="rating_dialog_title">Noter</string>
<string name="search_hint">Rechercher des titres, artistes ou albums</string>
<string name="search_info_minimum_characters">Entrez 3 caractères minimum</string>
<string name="search_info_minimum_characters">Entrez 3 charactères minimum</string>
<string name="search_title_album">Albums</string>
<string name="search_title_artist">Artistes</string>
<string name="search_title_song">Pistes</string>
@@ -222,7 +222,7 @@
<string name="settings_about_summary">Tempo est un client open source et léger pour Subsonic, développé et build nativement pour Android.</string>
<string name="settings_about_title">À propos</string>
<string name="settings_audio_transcode_download_format">Format de transcodage</string>
<string name="settings_audio_transcode_download_priority_summary">Si activé, Tempo ne forcera pas le téléchargement de la piste avec les paramètres de transcodage ci-dessous.</string>
<string name="settings_audio_transcode_download_priority_summary">Si activé, Tempo ne forcera pas le téléchargement de la piste avec les paramètre de transcodage ci-dessous.</string>
<string name="settings_audio_transcode_download_priority_title">Prioriser les paramètres du serveurs, utilisés pour le streaming, dans les téléchargements</string>
<string name="settings_audio_transcode_download_summary">Si activé, Tempo téléchargera les pistes transcodées.</string>
<string name="settings_audio_transcode_download_title">Télécharger les pistes transcodées</string>
@@ -310,7 +310,7 @@
<string name="share_update_dialog_positive_button">Enregistrer</string>
<string name="share_update_dialog_title">Partager</string>
<string name="song_bottom_sheet_add_to_playlist">Ajouter à une playlist</string>
<string name="song_bottom_sheet_add_to_queue">Ajouter à la file d\'attente</string>
<string name="song_bottom_sheet_add_to_queue">Ajouter à la fil d\'attente</string>
<string name="song_bottom_sheet_download">Télécharger</string>
<string name="song_bottom_sheet_error_retrieving_album">Erreur de récupération de l\'album</string>
<string name="song_bottom_sheet_error_retrieving_artist">Erreur de récupération de l\'artiste</string>

View File

@@ -1,257 +0,0 @@
<resources>
<string-array name="theme_list_titles">
<item>Chiaro</item>
<item>Scuro</item>
<item>Predefinito del sistema</item>
</string-array>
<string-array name="theme_list_values">
<item>light</item>
<item>dark</item>
<item>default</item>
</string-array>
<string-array name="pref_cache_size_titles">
<item>Alto</item>
<item>Medio</item>
<item>Basso</item>
</string-array>
<string-array name="pref_cache_size_values">
<item>500</item>
<item>250</item>
<item>125</item>
</string-array>
<string-array name="pref_image_size_titles">
<item>Alto</item>
<item>Medio</item>
<item>Basso</item>
</string-array>
<string-array name="pref_image_size_values">
<item>-1</item>
<item>500</item>
<item>300</item>
</string-array>
<string-array name="streaming_cache_size_titles">
<item>Disabilitato</item>
<item>128 MiB</item>
<item>256 MiB</item>
<item>512 MiB</item>
<item>1024 MiB</item>
</string-array>
<string-array name="streaming_cache_size_values">
<item>0</item>
<item>128</item>
<item>256</item>
<item>512</item>
<item>1024</item>
</string-array>
<string-array name="max_bitrate_wifi_list_titles">
<item>Originale</item>
<item>32 kbps</item>
<item>48 kbps</item>
<item>64 kbps</item>
<item>80 kbps</item>
<item>96 kbps</item>
<item>112 kbps</item>
<item>128 kbps</item>
<item>160 kbps</item>
<item>192 kbps</item>
<item>256 kbps</item>
<item>320 kbps</item>
</string-array>
<string-array name="max_bitrate_wifi_list_values">
<item>0</item>
<item>32</item>
<item>48</item>
<item>64</item>
<item>80</item>
<item>96</item>
<item>112</item>
<item>128</item>
<item>160</item>
<item>192</item>
<item>256</item>
<item>320</item>
</string-array>
<string-array name="max_bitrate_mobile_list_titles">
<item>Originale</item>
<item>32 kbps</item>
<item>48 kbps</item>
<item>64 kbps</item>
<item>80 kbps</item>
<item>96 kbps</item>
<item>112 kbps</item>
<item>128 kbps</item>
<item>160 kbps</item>
<item>192 kbps</item>
<item>256 kbps</item>
<item>320 kbps</item>
</string-array>
<string-array name="max_bitrate_mobile_list_values">
<item>0</item>
<item>32</item>
<item>48</item>
<item>64</item>
<item>80</item>
<item>96</item>
<item>112</item>
<item>128</item>
<item>160</item>
<item>192</item>
<item>256</item>
<item>320</item>
</string-array>
<string-array name="max_bitrate_download_list_titles">
<item>Originale</item>
<item>32 kbps</item>
<item>48 kbps</item>
<item>64 kbps</item>
<item>80 kbps</item>
<item>96 kbps</item>
<item>112 kbps</item>
<item>128 kbps</item>
<item>160 kbps</item>
<item>192 kbps</item>
<item>256 kbps</item>
<item>320 kbps</item>
</string-array>
<string-array name="max_bitrate_download_list_values">
<item>0</item>
<item>32</item>
<item>48</item>
<item>64</item>
<item>80</item>
<item>96</item>
<item>112</item>
<item>128</item>
<item>160</item>
<item>192</item>
<item>256</item>
<item>320</item>
</string-array>
<string-array name="audio_transcode_format_wifi_list_titles">
<item>Riproduzione diretta</item>
<item>Opus</item>
<item>AAC</item>
<item>Mp3</item>
<item>Flac</item>
</string-array>
<string-array name="audio_transcode_format_wifi_list_values">
<item>raw</item>
<item>opus</item>
<item>aac</item>
<item>mp3</item>
<item>flac</item>
</string-array>
<string-array name="audio_transcode_format_mobile_list_titles">
<item>Riproduzione diretta</item>
<item>Opus</item>
<item>AAC</item>
<item>Mp3</item>
<item>Flac</item>
</string-array>
<string-array name="audio_transcode_format_mobile_list_values">
<item>raw</item>
<item>opus</item>
<item>aac</item>
<item>mp3</item>
<item>flac</item>
</string-array>
<string-array name="audio_transcode_format_download_list_titles">
<item>Download diretto</item>
<item>Opus</item>
<item>AAC</item>
<item>Mp3</item>
<item>Flac</item>
</string-array>
<string-array name="audio_transcode_format_download_list_values">
<item>raw</item>
<item>opus</item>
<item>aac</item>
<item>mp3</item>
<item>flac</item>
</string-array>
<string-array name="queue_syncing_countdown_titles">
<item>Dieci secondi</item>
<item>Cinque secondi</item>
<item>Due secondi</item>
</string-array>
<string-array name="queue_syncing_countdown_values">
<item>10</item>
<item>5</item>
<item>2</item>
</string-array>
<string-array name="rounded_corner_size_titles">
<item>Alto</item>
<item>Medio</item>
<item>Basso</item>
</string-array>
<string-array name="rounded_corner_size_values">
<item>18</item>
<item>12</item>
<item>6</item>
</string-array>
<string-array name="replay_gain_titles">
<item>Disabilitato</item>
<item>Traccia</item>
<item>Album</item>
<item>Auto</item>
</string-array>
<string-array name="replay_gain_values">
<item>disabled</item>
<item>track</item>
<item>album</item>
<item>auto</item>
</string-array>
<string-array name="transcoded_download_option_list_titles">
<item>Non transcodificare</item>
<item>Impostazioni del server</item>
<item>Formato transcodifica Wi-Fi</item>
<item>Formato transcodifica mobile</item>
</string-array>
<string-array name="transcoded_download_option_list_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
<string-array name="buffering_strategy_titles">
<item>Minimo</item>
<item>Moderato</item>
<item>Aggressivo</item>
<item>Estremo</item>
</string-array>
<string-array name="buffering_strategy_values">
<item>.1</item>
<item>1</item>
<item>4</item>
<item>8</item>
</string-array>
<string-array name="skip_min_star_rating_titles">
<item>Minimo 0 stelle</item>
<item>Minimo 1 stella</item>
<item>Minimo 2 stelle</item>
<item>Minimo 3 stelle</item>
<item>Minimo 4 stelle</item>
</string-array>
<string-array name="skip_min_star_rating_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</string-array>
</resources>

View File

@@ -1,412 +0,0 @@
<resources>
<string name="activity_battery_optimizations_conclusion">Se hai problemi, visita https://dontkillmyapp.com. Qui trovi istruzioni dettagliate su come disabilitare le funzionalità di risparmio energetico che potrebbero influire sulle prestazioni dell\'app.</string>
<string name="activity_battery_optimizations_summary">Per favore, disabilita le ottimizzazioni della batteria per la riproduzione multimediale quando lo schermo è spento.</string>
<string name="activity_battery_optimizations_title">Ottimizzazioni della Batteria</string>
<string name="activity_info_offline_mode">Modalità offline</string>
<string name="album_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
<string name="album_bottom_sheet_download_all">Scarica tutto</string>
<string name="album_bottom_sheet_go_to_artist">Vai all\'artista</string>
<string name="album_bottom_sheet_instant_mix">Mix istantaneo</string>
<string name="album_bottom_sheet_play_next">Riproduci successivo</string>
<string name="album_bottom_sheet_remove_all">Rimuovi tutto</string>
<string name="album_bottom_sheet_share">Condividi</string>
<string name="album_bottom_sheet_shuffle">Riproduzione casuale</string>
<string name="album_catalogue_title">Album</string>
<string name="album_catalogue_title_expanded">Sfoglia Album</string>
<string name="album_error_retrieving_artist">Errore nel recupero dell\'artista</string>
<string name="album_list_page_downloaded">Album scaricati</string>
<string name="album_list_page_most_played">Album più riprodotti</string>
<string name="album_list_page_new_releases">Nuove uscite</string>
<string name="album_list_page_recently_added">Album aggiunti di recente</string>
<string name="album_list_page_recently_played">Album riprodotti di recente</string>
<string name="album_list_page_starred">Album preferiti</string>
<string name="album_list_page_title">Album</string>
<string name="album_page_extra_info_button">Simili a questo</string>
<string name="album_page_play_button">Riproduci</string>
<string name="album_page_release_date_label">Rilasciato il %1$s</string>
<string name="album_page_release_dates_label">Rilasciato il %1$s, originariamente il %2$s</string>
<string name="album_page_shuffle_button">Riproduzione casuale</string>
<string name="album_page_tracks_count_and_duration">%1$d brani • %2$d minuti</string>
<string name="app_name">Tempo</string>
<string name="artist_adapter_radio_station_starting">Ricerca in corso…</string>
<string name="artist_bottom_sheet_instant_mix">Mix istantaneo</string>
<string name="artist_bottom_sheet_shuffle">Riproduzione casuale</string>
<string name="artist_catalogue_title">Artisti</string>
<string name="artist_catalogue_title_expanded">Sfoglia Artisti</string>
<string name="artist_error_retrieving_radio">Errore nel recupero della radio dell\'artista</string>
<string name="artist_error_retrieving_tracks">Errore nel recupero dei brani dell\'artista</string>
<string name="artist_list_page_downloaded">Artisti scaricati</string>
<string name="artist_list_page_starred">Artisti preferiti</string>
<string name="artist_list_page_title">Artisti</string>
<string name="artist_page_radio_button">Radio</string>
<string name="artist_page_shuffle_button">Riproduzione casuale</string>
<string name="artist_page_switch_layout_button">Cambia layout</string>
<string name="artist_page_title_album_more_like_this_button">Simili a questo</string>
<string name="artist_page_title_album_section">Album</string>
<string name="artist_page_title_biography_more_button">Altro</string>
<string name="artist_page_title_biography_section">Biografia</string>
<string name="artist_page_title_most_streamed_song_section">Brani più ascoltati</string>
<string name="artist_page_title_most_streamed_song_see_all_button">Vedi tutto</string>
<string name="battery_optimization_negative_button">Ignora</string>
<string name="battery_optimization_neutral_button">Non chiedere di nuovo</string>
<string name="battery_optimization_positive_button">Disabilita</string>
<string name="connection_alert_dialog_negative_button">Annulla</string>
<string name="connection_alert_dialog_neutral_button">Attiva risparmio dati</string>
<string name="connection_alert_dialog_positive_button">OK</string>
<string name="connection_alert_dialog_summary">L\'accesso al server Subsonic è stato limitato alle connessioni Wi-Fi. Per evitare che questo avviso riappaia, disabilita il controllo connessione nelle impostazioni dell\'app.</string>
<string name="connection_alert_dialog_title">Wi-Fi non connesso</string>
<string name="content_description_shuffle_button">Riproduzione casuale</string>
<string name="delete_download_storage_dialog_negative_button">Annulla</string>
<string name="delete_download_storage_dialog_positive_button">Continua</string>
<string name="delete_download_storage_dialog_summary">Attenzione, procedendo questa azione eliminerà definitivamente tutti gli elementi scaricati da tutti i server.</string>
<string name="delete_download_storage_dialog_title">Elimina elementi salvati</string>
<string name="description_empty_title">Descrizione non disponibile</string>
<string name="disc_titlefull">Disco %1$s - %2$s</string>
<string name="disc_titleless">Disco %1$s</string>
<string name="download_directory_dialog_negative_button">Annulla</string>
<string name="download_directory_dialog_positive_button">Scarica</string>
<string name="download_directory_dialog_summary">Tutti i brani in questa cartella verranno scaricati. I brani nelle sottocartelle non verranno scaricati.</string>
<string name="download_directory_dialog_title">Scarica i brani</string>
<string name="download_info_empty_subtitle">Una volta scaricato un brano, lo troverai qui</string>
<string name="download_info_empty_title">Nessun download ancora!</string>
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s elementi</string>
<string name="download_item_single_subtitle_formatter">%1$s elementi</string>
<string name="download_shuffle_all_subtitle">Riproduzione casuale di tutto</string>
<string name="download_storage_dialog_sub_summary">Per rendere effettive le modifiche, riavvia l\'app.</string>
<string name="download_storage_dialog_summary">Cambiare la destinazione dei file scaricati da una memoria all\'altra eliminerà immediatamente tutti i file scaricati precedentemente nella vecchia memoria.</string>
<string name="download_storage_dialog_title">Seleziona opzione di memoria</string>
<string name="download_storage_external_dialog_positive_button">Esterna</string>
<string name="download_storage_internal_dialog_negative_button">Interna</string>
<string name="download_title_section">Download</string>
<string name="downloaded_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
<string name="downloaded_bottom_sheet_play_next">Riproduci successivo</string>
<string name="downloaded_bottom_sheet_remove">Rimuovi</string>
<string name="downloaded_bottom_sheet_remove_all">Rimuovi tutto</string>
<string name="downloaded_bottom_sheet_shuffle">Riproduzione casuale</string>
<string name="empty_string" />
<string name="error_required">Obbligatorio</string>
<string name="error_server_prefix">Prefisso http o https richiesto</string>
<string name="exo_download_notification_channel_name">Download</string>
<string name="filter_info_selection">Seleziona due o più filtri</string>
<string name="filter_title">Filtro</string>
<string name="filter_title_expanded">Filtra Generi</string>
<string name="genre_catalogue_title">Catalogo dei Generi</string>
<string name="genre_catalogue_title_expanded">Sfoglia Generi</string>
<string name="github_update_dialog_negative_button">Ricordamelo più tardi</string>
<string name="github_update_dialog_neutral_button">Supportami</string>
<string name="github_update_dialog_positive_button">Scarica ora</string>
<string name="github_update_dialog_summary">È disponibile una nuova versione dell\'app su Github.</string>
<string name="github_update_dialog_title">Aggiornamento disponibile</string>
<string name="home_rearrangement_dialog_negative_button">Annulla</string>
<string name="home_rearrangement_dialog_neutral_button">Reimposta</string>
<string name="home_rearrangement_dialog_positive_button">Salva</string>
<string name="home_rearrangement_dialog_title">Riorganizza home</string>
<string name="home_rearrangement_dialog_subtitle">Si prega di notare che per rendere effettive le modifiche è necessario riavviare l\'applicazione.</string>
<string name="home_subtitle_best_of">Le migliori canzoni dei tuoi artisti preferiti</string>
<string name="home_subtitle_made_for_you">Inizia un mix da una canzone che ti è piaciuta</string>
<string name="home_subtitle_new_internet_radio_station">Aggiungi una nuova radio</string>
<string name="home_subtitle_new_podcast_channel">Aggiungi un nuovo canale podcast</string>
<string name="home_sync_starred_cancel">Annulla</string>
<string name="home_sync_starred_download">Scarica</string>
<string name="home_sync_starred_subtitle">Scaricare questi brani potrebbe comportare un uso significativo di dati</string>
<string name="home_sync_starred_title">Sembra che ci siano brani da sincronizzare con una stella</string>
<string name="home_title_best_of">Il meglio di</string>
<string name="home_title_discovery">Scoperta</string>
<string name="home_title_discovery_shuffle_all_button">Mescola tutto</string>
<string name="home_title_flashback">Flashback</string>
<string name="home_title_internet_radio_station">Stazioni radio internet</string>
<string name="home_title_last_played">Ultimi ascolti</string>
<string name="home_title_last_played_see_all_button">Vedi tutto</string>
<string name="home_title_last_week">La scorsa settimana</string>
<string name="home_title_last_month">Il mese scorso</string>
<string name="home_title_last_year">L\'anno scorso</string>
<string name="home_title_made_for_you">Fatto per te</string>
<string name="home_title_most_played">Più ascoltati</string>
<string name="home_title_most_played_see_all_button">Vedi tutto</string>
<string name="home_title_new_releases">Nuove uscite</string>
<string name="home_title_newest_podcasts">Podcast più recenti</string>
<string name="home_title_pinned_playlists">Playlist</string>
<string name="home_title_podcast_channels">Canali</string>
<string name="home_title_podcast_channels_see_all_button">Vedi tutto</string>
<string name="home_title_radio_station">Stazioni radio</string>
<string name="home_title_recently_added">Aggiunti di recente</string>
<string name="home_title_recently_added_see_all_button">Vedi tutto</string>
<string name="home_title_shares">Condivisioni</string>
<string name="home_title_starred_albums">★ Album con stella</string>
<string name="home_title_starred_albums_see_all_button">Vedi tutto</string>
<string name="home_title_starred_artists">★ Artisti con stella</string>
<string name="home_title_starred_artists_see_all_button">Vedi tutto</string>
<string name="home_title_starred_tracks">★ Brani con stella</string>
<string name="home_title_starred_tracks_see_all_button">Vedi tutto</string>
<string name="home_title_top_songs">I tuoi migliori brani</string>
<string name="home_option_reorganize">Riorganizza</string>
<string name="label_dot_separator" translatable="false"></string>
<string name="label_placeholder" translatable="false">--</string>
<string name="library_title_album">Album</string>
<string name="library_title_album_see_all_button">Vedi tutto</string>
<string name="library_title_artist">Artisti</string>
<string name="library_title_artist_see_all_button">Vedi tutto</string>
<string name="library_title_genre">Generi</string>
<string name="library_title_genre_see_all_button">Vedi tutto</string>
<string name="library_title_music_folder">Cartelle musicali</string>
<string name="library_title_playlist">Playlist</string>
<string name="library_title_playlist_see_all_button">Vedi tutto</string>
<string name="login_empty">Nessun server aggiunto</string>
<string name="login_title">Server Subsonic</string>
<string name="login_title_expanded">Server Subsonic</string>
<string name="media_route_menu_title">Trasmetti</string>
<string name="menu_add_button">Aggiungi</string>
<string name="menu_download_all_button">Scarica tutto</string>
<string name="menu_download_label">Scarica</string>
<string name="menu_filter_all">Tutti</string>
<string name="menu_filter_download">Scaricati</string>
<string name="menu_group_by_album">Album</string>
<string name="menu_group_by_artist">Artista</string>
<string name="menu_group_by_genre">Genere</string>
<string name="menu_group_by_track">Brano</string>
<string name="menu_group_by_year">Anno</string>
<string name="menu_home_label">Home</string>
<string name="menu_last_week_name">La scorsa settimana</string>
<string name="menu_last_month_name">Il mese scorso</string>
<string name="menu_last_year_name">L\'anno scorso</string>
<string name="menu_library_label">Libreria</string>
<string name="menu_search_button">Cerca</string>
<string name="menu_settings_button">Impostazioni</string>
<string name="menu_sort_artist">Artista</string>
<string name="menu_sort_name">Nome</string>
<string name="menu_sort_random">Casuale</string>
<string name="menu_sort_recently_added">Aggiunti di recente</string>
<string name="menu_pin_button">Aggiungi alla schermata home</string>
<string name="menu_unpin_button">Rimuovi dalla schermata home</string>
<string name="menu_sort_year">Anno</string>
<string name="player_playback_speed">%1$.2fx</string>
<string name="player_queue_clean_all_button">Svuota coda di riproduzione</string>
<string name="player_server_priority">Priorità server</string>
<string name="playlist_catalogue_title">Catalogo playlist</string>
<string name="playlist_catalogue_title_expanded">Sfoglia le playlist</string>
<string name="playlist_chooser_dialog_empty">Nessuna playlist creata</string>
<string name="playlist_chooser_dialog_negative_button">Annulla</string>
<string name="playlist_chooser_dialog_neutral_button">Crea</string>
<string name="playlist_chooser_dialog_title">Aggiungi a una playlist</string>
<string name="playlist_counted_tracks">%1$d brani • %2$s</string>
<string name="playlist_duration">Durata • %1$s</string>
<string name="playlist_editor_dialog_action_delete_toast">Premi a lungo per eliminare</string>
<string name="playlist_editor_dialog_hint_name">Nome della playlist</string>
<string name="playlist_editor_dialog_negative_button">Annulla</string>
<string name="playlist_editor_dialog_neutral_button">Elimina</string>
<string name="playlist_editor_dialog_positive_button">Salva</string>
<string name="playlist_editor_dialog_title">Modifica playlist</string>
<string name="playlist_page_play_button">Riproduci</string>
<string name="playlist_page_shuffle_button">Mescola</string>
<string name="playlist_song_count">Playlist • %1$d brani</string>
<string name="podcast_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
<string name="podcast_bottom_sheet_delete">Elimina</string>
<string name="podcast_bottom_sheet_download">Scarica</string>
<string name="podcast_bottom_sheet_go_to_channel">Vai al canale</string>
<string name="podcast_bottom_sheet_play_next">Riproduci dopo</string>
<string name="podcast_bottom_sheet_remove">Rimuovi</string>
<string name="podcast_channel_catalogue_title">Canali</string>
<string name="podcast_channel_catalogue_title_expanded">Sfoglia Canali</string>
<string name="podcast_channel_editor_dialog_hint_rss_url">URL RSS</string>
<string name="podcast_channel_editor_dialog_title">Canale Podcast</string>
<string name="podcast_channel_page_title_description_section">Descrizione</string>
<string name="podcast_channel_page_title_episode_section">Episodi</string>
<string name="podcast_channel_page_title_no_episode_available">Nessun episodio disponibile</string>
<string name="podcast_episode_download_request_snackbar">La tua richiesta è stata inviata al server</string>
<string name="podcast_info_empty_button">Clicca per nascondere la sezione\nGli effetti saranno visibili al riavvio</string>
<string name="podcast_info_empty_subtitle">Una volta aggiunto un canale, lo troverai qui</string>
<string name="podcast_info_empty_title">Nessun podcast trovato!</string>
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
<string name="radio_editor_dialog_hint_homepage_url">URL Homepage Radio</string>
<string name="radio_editor_dialog_hint_name">Nome Radio</string>
<string name="radio_editor_dialog_hint_stream_url">URL Stream Radio</string>
<string name="radio_editor_dialog_negative_button">Annulla</string>
<string name="radio_editor_dialog_neutral_button">Elimina</string>
<string name="radio_editor_dialog_positive_button">Salva</string>
<string name="radio_editor_dialog_title">Stazione Radio Internet</string>
<string name="radio_station_info_empty_button">Clicca per nascondere la sezione\nGli effetti saranno visibili al riavvio</string>
<string name="radio_station_info_empty_subtitle">Una volta aggiunta una stazione radio, la troverai qui</string>
<string name="radio_station_info_empty_title">Nessuna stazione trovata!</string>
<string name="rating_dialog_negative_button">Annulla</string>
<string name="rating_dialog_positive_button">Salva</string>
<string name="rating_dialog_title">Valuta</string>
<string name="search_hint">Cerca titolo, artisti o album</string>
<string name="search_info_minimum_characters">Inserisci almeno tre caratteri</string>
<string name="search_title_album">Album</string>
<string name="search_title_artist">Artisti</string>
<string name="search_title_song">Brani</string>
<string name="server_signup_dialog_action_low_security">Bassa sicurezza</string>
<string name="server_signup_dialog_action_delete_toast">Premi a lungo per eliminare</string>
<string name="server_signup_dialog_hint_local_address">URL locale</string>
<string name="server_signup_dialog_hint_name">Nome Server</string>
<string name="server_signup_dialog_hint_password">Password</string>
<string name="server_signup_dialog_hint_url">URL Server</string>
<string name="server_signup_dialog_hint_username">Nome utente</string>
<string name="server_signup_dialog_negative_button">Annulla</string>
<string name="server_signup_dialog_neutral_button">Elimina</string>
<string name="server_signup_dialog_positive_button">Salva</string>
<string name="server_signup_dialog_title">Aggiungi server</string>
<string name="server_unreachable_dialog_negative_button">Annulla</string>
<string name="server_unreachable_dialog_neutral_button">Vai al login</string>
<string name="server_unreachable_dialog_positive_button">Continua comunque</string>
<string name="server_unreachable_dialog_summary">Il server richiesto non è disponibile. Se scegli di continuare, questo messaggio non apparirà per la prossima ora.</string>
<string name="server_unreachable_dialog_title">Server irraggiungibile</string>
<string name="settings_about_summary">Tempo è un client musicale open source e leggero per Subsonic, progettato e costruito nativamente per Android.</string>
<string name="settings_about_title">Informazioni</string>
<string name="settings_always_on_display">Sempre attivo</string>
<string name="settings_audio_transcode_download_format">Formato transcodifica</string>
<string name="settings_audio_transcode_download_priority_summary">Se abilitato, Tempo non forzerà il download del brano con le impostazioni di transcodifica sottostanti.</string>
<string name="settings_audio_transcode_download_priority_title">Dare priorità alle impostazioni del server per lo streaming nei download</string>
<string name="settings_audio_transcode_download_summary">Se abilitato, Tempo scaricherà i brani transcodificati.</string>
<string name="settings_audio_transcode_download_title">Scarica brani transcodificati</string>
<string name="settings_audio_transcode_estimate_content_length_summary">Se abilitato, verrà richiesto al server di fornire la durata stimata del brano.</string>
<string name="settings_audio_transcode_estimate_content_length_title">Stima della lunghezza del contenuto</string>
<string name="settings_audio_transcode_format_download">Formato transcodifica per download</string>
<string name="settings_audio_transcode_format_mobile">Formato transcodifica su mobile</string>
<string name="settings_audio_transcode_format_wifi">Formato transcodifica su Wi-Fi</string>
<string name="settings_audio_transcode_priority_summary">Se abilitato, Tempo non forzerà lo streaming del brano con le impostazioni di transcodifica sottostanti.</string>
<string name="settings_audio_transcode_priority_title">Dare priorità alle impostazioni di transcodifica del server</string>
<string name="settings_audio_transcode_priority_toast">Priorità di transcodifica del brano assegnata al server</string>
<string name="settings_buffering_strategy">Strategia di buffering</string>
<string name="settings_buffering_strategy_summary">Perché la modifica abbia effetto è necessario riavviare manualmente l\'app.</string>
<string name="settings_continuous_play_summary">Consente alla musica di continuare a suonare dopo la fine di una playlist, riproducendo brani simili</string>
<string name="settings_continuous_play_title">Riproduzione continua</string>
<string name="settings_covers_cache">Dimensione della cache delle copertine</string>
<string name="settings_data_saving_mode_summary">Per ridurre il consumo di dati, evita di scaricare le copertine.</string>
<string name="settings_data_saving_mode_title">Limita utilizzo dei dati mobili</string>
<string name="settings_delete_download_storage_summary">Continuando, tutti gli elementi salvati verranno eliminati in modo irreversibile.</string>
<string name="settings_delete_download_storage_title">Elimina elementi salvati</string>
<string name="settings_download_storage_title">Archivio download</string>
<string name="settings_equalizer_summary">Regola le impostazioni audio</string>
<string name="settings_equalizer_title">Equalizzatore</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string>
<string name="settings_github_summary">Segui lo sviluppo</string>
<string name="settings_github_title">Github</string>
<string name="settings_image_size">Imposta risoluzione delle immagini</string>
<string name="settings_language">Lingua</string>
<string name="settings_logout_title">Esci</string>
<string name="settings_max_bitrate_download">Bitrate per download</string>
<string name="settings_max_bitrate_mobile">Bitrate su mobile</string>
<string name="settings_max_bitrate_wifi">Bitrate su Wi-Fi</string>
<string name="settings_media_cache">Dimensione della cache dei file multimediali</string>
<string name="settings_music_directory">Mostra directory musicali</string>
<string name="settings_music_directory_summary">Se abilitato, mostra la sezione delle directory musicali. Nota che per la navigazione nelle cartelle è necessario che il server supporti questa funzionalità.</string>
<string name="settings_podcast">Mostra podcast</string>
<string name="settings_podcast_summary">Se abilitato, mostra la sezione podcast. Riavvia l\'app per rendere effettive le modifiche.</string>
<string name="settings_audio_quality">Mostra qualità audio</string>
<string name="settings_audio_quality_summary">Il bitrate e il formato audio saranno mostrati per ogni traccia.</string>
<string name="settings_item_rating">Mostra valutazione</string>
<string name="settings_item_rating_summary">Se abilitato, verrà mostrata la valutazione dell\'elemento e se è contrassegnato come preferito.</string>
<string name="settings_queue_syncing_countdown">Timer sincronizzazione</string>
<string name="settings_queue_syncing_summary">Se abilitato, l\'utente avrà la possibilità di salvare la propria coda di riproduzione e potrà caricare lo stato all\'apertura dell\'applicazione.</string>
<string name="settings_queue_syncing_title">Sincronizza coda di riproduzione per questo utente</string>
<string name="settings_radio">Mostra radio</string>
<string name="settings_radio_summary">Se abilitato, mostra la sezione radio. Riavvia l\'app per applicare completamente le modifiche.</string>
<string name="settings_replay_gain">Imposta modalità di guadagno di riproduzione</string>
<string name="settings_rounded_corner">Angoli arrotondati</string>
<string name="settings_rounded_corner_size">Dimensione angoli</string>
<string name="settings_rounded_corner_size_summary">Imposta la magnitudine dell\'angolo di curvatura.</string>
<string name="settings_rounded_corner_summary">Se abilitato, imposta un angolo di curvatura per tutte le copertine visualizzate. Le modifiche avranno effetto al riavvio.</string>
<string name="settings_scan_title">Scansiona libreria</string>
<string name="settings_scrobble_title">Abilita scrobbling musicale</string>
<string name="settings_share_title">Abilita condivisione musicale</string>
<string name="settings_streaming_cache_size">Dimensione cache streaming</string>
<string name="settings_streaming_cache_storage_title">Archiviazione cache streaming</string>
<string name="settings_sub_summary_scrobble">È importante notare che lo scrobbling si basa anche sul fatto che il server sia abilitato a ricevere questi dati.</string>
<string name="settings_summary_skip_min_star_rating">Quando si ascolta la radio di un artista, un mix istantaneo o quando si mescolano tutti i brani, i brani sotto una certa valutazione dell\'utente verranno ignorati.</string>
<string name="settings_summary_replay_gain">Il guadagno di riproduzione è una funzionalità che consente di regolare il livello del volume delle tracce audio per un\'esperienza di ascolto coerente. Questa impostazione è efficace solo se la traccia contiene i metadati necessari.</string>
<string name="settings_summary_scrobble">Lo scrobbling è una funzionalità che consente al tuo dispositivo di inviare informazioni sulle canzoni che ascolti al server musicale. Queste informazioni aiutano a creare raccomandazioni personalizzate in base alle tue preferenze musicali.</string>
<string name="settings_summary_share">Permette all\'utente di condividere musica tramite un link. La funzionalità deve essere supportata e abilitata sul server ed è limitata a brani, album e playlist singoli.</string>
<string name="settings_summary_syncing">Restituisce lo stato della coda di riproduzione per questo utente. Ciò include i brani nella coda di riproduzione, il brano attualmente in riproduzione e la posizione all\'interno di questo brano. Il server deve supportare questa funzionalità.</string>
<string name="settings_summary_streaming_cache_size">%1$s \nAttualmente in uso: %2$s MiB</string>
<string name="settings_summary_transcoding">Priorità data alla modalità di transcoding. Se impostato su "Riproduzione diretta", il bitrate del file non verrà modificato.</string>
<string name="settings_summary_transcoding_download">Scarica media transcodificati. Se abilitato, l\'endpoint di download non verrà utilizzato, ma le impostazioni seguenti. \n\n Se "Formato di transcodifica per i download" è impostato su "Download diretto", il bitrate del file non verrà modificato.</string>
<string name="settings_summary_transcoding_estimate_content_length">Quando il file viene transcodificato al volo, il client di solito non mostra la lunghezza della traccia. È possibile richiedere ai server che supportano la funzionalità di stimare la durata della traccia in riproduzione, ma i tempi di risposta possono essere più lunghi.</string>
<string name="settings_sync_starred_tracks_for_offline_use_summary">Se abilitato, le tracce contrassegnate verranno scaricate per l\'uso offline.</string>
<string name="settings_sync_starred_tracks_for_offline_use_title">Sincronizza tracce contrassegnate per uso offline</string>
<string name="settings_theme">Tema</string>
<string name="settings_title_data">Dati</string>
<string name="settings_title_general">Generale</string>
<string name="settings_title_rating">Valutazione</string>
<string name="settings_title_replay_gain">Guadagno di riproduzione</string>
<string name="settings_title_scrobble">Scrobble</string>
<string name="settings_title_skip_min_star_rating">Ignora brani in base alla valutazione</string>
<string name="settings_title_skip_min_star_rating_dialog">Brani con una valutazione di:</string>
<string name="settings_title_share">Condividi</string>
<string name="settings_title_syncing">Sincronizzazione</string>
<string name="settings_title_transcoding">Transcoding</string>
<string name="settings_title_transcoding_download">Download di Transcoding</string>
<string name="settings_title_ui">Interfaccia utente</string>
<string name="settings_transcoded_download">Download transcodificato</string>
<string name="settings_version_summary" translatable="false">3.1.0</string>
<string name="settings_version_title">Versione</string>
<string name="settings_wifi_only_summary">Chiedi conferma all\'utente prima di effettuare streaming su rete mobile.</string>
<string name="settings_wifi_only_title">Streaming solo tramite Wi-Fi avviso</string>
<string name="share_bottom_sheet_copy_link">Copia link</string>
<string name="share_bottom_sheet_delete">Elimina condivisione</string>
<string name="share_bottom_sheet_update">Aggiorna condivisione</string>
<string name="share_subtitle_item">Data di scadenza: %1$s</string>
<string name="share_unsupported_error">La condivisione non è supportata o non è abilitata</string>
<string name="share_update_dialog_hint_description">Descrizione</string>
<string name="share_update_dialog_hint_expiration_date">Data di scadenza</string>
<string name="share_update_dialog_negative_button">Annulla</string>
<string name="share_update_dialog_positive_button">Salva</string>
<string name="share_update_dialog_title">Condividi</string>
<string name="song_bottom_sheet_add_to_playlist">Aggiungi alla playlist</string>
<string name="song_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
<string name="song_bottom_sheet_download">Scarica</string>
<string name="song_bottom_sheet_error_retrieving_album">Errore nel recupero dell\'album</string>
<string name="song_bottom_sheet_error_retrieving_artist">Errore nel recupero dell\'artista</string>
<string name="song_bottom_sheet_go_to_album">Vai all\'album</string>
<string name="song_bottom_sheet_go_to_artist">Vai all\'artista</string>
<string name="song_bottom_sheet_instant_mix">Mix istantaneo</string>
<string name="song_bottom_sheet_play_next">Riproduci dopo</string>
<string name="song_bottom_sheet_rate">Valuta</string>
<string name="song_bottom_sheet_remove">Rimuovi</string>
<string name="song_bottom_sheet_share">Condividi</string>
<string name="song_list_page_downloaded">Scaricato</string>
<string name="song_list_page_most_played">Tracce più riprodotte</string>
<string name="song_list_page_recently_added">Tracce aggiunte di recente</string>
<string name="song_list_page_recently_played">Tracce riprodotte di recente</string>
<string name="song_list_page_starred">Tracce contrassegnate</string>
<string name="song_list_page_top">Le migliori tracce di %1$s</string>
<string name="song_list_page_year">Anno %1$d</string>
<string name="song_subtitle_formatter">%1$s • %2$s %3$s</string>
<string name="starred_sync_dialog_negative_button">Annulla</string>
<string name="starred_sync_dialog_neutral_button">Continua</string>
<string name="starred_sync_dialog_positive_button">Continua e scarica</string>
<string name="starred_sync_dialog_summary">Il download delle tracce contrassegnate potrebbe richiedere una grande quantità di dati.</string>
<string name="starred_sync_dialog_title">Sincronizza tracce contrassegnate</string>
<string name="streaming_cache_storage_dialog_sub_summary">Per rendere effettive le modifiche, riavvia l\'app.</string>
<string name="streaming_cache_storage_dialog_summary">Cambiare la destinazione dei file memorizzati nella cache da un\'unità di archiviazione a un\'altra può comportare la cancellazione di eventuali file memorizzati nella cache in precedenza nell\'altra unità di archiviazione.</string>
<string name="streaming_cache_storage_dialog_title">Seleziona opzione di archiviazione</string>
<string name="streaming_cache_storage_external_dialog_positive_button">Esterno</string>
<string name="streaming_cache_storage_internal_dialog_negative_button">Interno</string>
<string name="support_url">https://buymeacoffee.com/a.cappiello</string>
<string name="track_info_album">Album</string>
<string name="track_info_artist">Artista</string>
<string name="track_info_bitrate">Bitrate</string>
<string name="track_info_content_type">Tipo di contenuto</string>
<string name="track_info_dialog_positive_button">OK</string>
<string name="track_info_dialog_title">Info traccia</string>
<string name="track_info_disc_number">Numero del disco</string>
<string name="track_info_duration">Durata</string>
<string name="track_info_genre">Genere</string>
<string name="track_info_path">Percorso</string>
<string name="track_info_size">Dimensione</string>
<string name="track_info_suffix">Suffisso</string>
<string name="track_info_summary_downloaded_file">Il file è stato scaricato utilizzando le API Subsonic. Il codec e il bitrate del file rimangono invariati rispetto al file sorgente.</string>
<string name="track_info_summary_full_transcode">L\'applicazione richiederà al server di transcodedare il file e modificare il suo bitrate. Il codec richiesto dall\'utente è %1$s, con un bitrate di %2$s. Eventuali modifiche al codec e al bitrate del file nel formato scelto saranno gestite dal server, che potrebbe o meno supportare l\'operazione.</string>
<string name="track_info_summary_original_file">L\'applicazione leggerà solo il file originale fornito dal server. L\'app richiederà esplicitamente al server il file non transcodedato con il bitrate della sorgente originale.</string>
<string name="track_info_summary_server_prioritized">La qualità del file da riprodurre è lasciata alla decisione del server. L\'app non imporrà la scelta di codec e bitrate per eventuali transcoding.</string>
<string name="track_info_summary_transcoding_bitrate">L\'applicazione richiederà al server di modificare il bitrate del file. L\'utente ha richiesto un bitrate di %1$s, mentre il codec del file sorgente rimarrà lo stesso. Eventuali modifiche al bitrate del file nel formato scelto saranno effettuate dal server, che potrebbe o meno supportare l\'operazione.</string>
<string name="track_info_summary_transcoding_codec">L\'applicazione richiederà al server di transcodedare il file. Il codec richiesto dall\'utente è %1$s, mentre il bitrate sarà lo stesso del file sorgente. L\'eventuale transcoding del file nel formato scelto dipende dal server, in quanto potrebbe o meno supportare l\'operazione.</string>
<string name="track_info_title">Titolo</string>
<string name="track_info_track_number">Numero traccia</string>
<string name="track_info_transcoded_content_type">Tipo di contenuto transcodedato</string>
<string name="track_info_transcoded_suffix">Suffisso transcodedato</string>
<string name="track_info_year">Anno</string>
<string name="undraw_page">unDraw</string>
<string name="undraw_thanks">Un ringraziamento speciale va a unDraw, senza le cui illustrazioni non avremmo potuto rendere questa applicazione più bella.</string>
<string name="undraw_url">https://undraw.co/</string>
</resources>

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimary">@color/md_theme_dark_primary</item>
<item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item>
<item name="colorPrimaryContainer">@color/md_theme_dark_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/md_theme_dark_onPrimaryContainer</item>
<item name="colorSecondary">@color/md_theme_dark_secondary</item>
<item name="colorOnSecondary">@color/md_theme_dark_onSecondary</item>
<item name="colorSecondaryContainer">@color/md_theme_dark_secondaryContainer</item>
<item name="colorOnSecondaryContainer">@color/md_theme_dark_onSecondaryContainer
</item>
<item name="colorTertiary">@color/md_theme_dark_tertiary</item>
<item name="colorOnTertiary">@color/md_theme_dark_onTertiary</item>
<item name="colorTertiaryContainer">@color/md_theme_dark_tertiaryContainer</item>
<item name="colorOnTertiaryContainer">@color/md_theme_dark_onTertiaryContainer
</item>
<item name="colorError">@color/md_theme_dark_error</item>
<item name="colorErrorContainer">@color/md_theme_dark_errorContainer</item>
<item name="colorOnError">@color/md_theme_dark_onError</item>
<item name="colorOnErrorContainer">@color/md_theme_dark_onErrorContainer</item>
<item name="android:colorBackground">@color/md_theme_dark_background</item>
<item name="colorOnBackground">@color/md_theme_dark_onBackground</item>
<item name="colorSurface">@color/md_theme_dark_surface</item>
<item name="colorOnSurface">@color/md_theme_dark_onSurface</item>
<item name="colorSurfaceVariant">@color/md_theme_dark_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/md_theme_dark_onSurfaceVariant</item>
<item name="colorOutline">@color/md_theme_dark_outline</item>
<item name="colorOnSurfaceInverse">@color/md_theme_dark_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_dark_primaryInverse</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:statusBarColor">?attr/colorSurface</item>
<item name="android:navigationBarColor">?attr/colorSurface</item>
<item name="android:scrollbars">none</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>

View File

@@ -32,21 +32,6 @@
<item>300</item>
</string-array>
<string-array name="streaming_cache_size_titles">
<item>Отключено</item>
<item>128 MiB</item>
<item>256 MiB</item>
<item>512 MiB</item>
<item>1024 MiB</item>
</string-array>
<string-array name="streaming_cache_size_values">
<item>0</item>
<item>128</item>
<item>256</item>
<item>512</item>
<item>1024</item>
</string-array>
<string-array name="max_bitrate_wifi_list_titles">
<item>Оригинал</item>
<item>32 kbps</item>

View File

@@ -1,398 +1,390 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="activity_battery_optimizations_conclusion">Если у вас возникли проблемы, посетите https://dontkillmyapp.com. Он содержит подробные инструкции о том, как отключить любые функции энергосбережения, которые могут повлиять на производительность приложения.</string>
<string name="activity_battery_optimizations_summary">Пожалуйста, отключите оптимизацию батареи для воспроизведения мультимедиа при выключенном экране.</string>
<string name="activity_battery_optimizations_title">Оптимизация батареи</string>
<string name="activity_info_offline_mode">Офлайн-режим</string>
<string name="album_bottom_sheet_add_to_queue">Добавить в очередь</string>
<string name="album_bottom_sheet_download_all">Скачать все</string>
<string name="album_bottom_sheet_go_to_artist">Перейти к исполнителю</string>
<string name="album_bottom_sheet_instant_mix">Мгновенный микс</string>
<string name="album_bottom_sheet_play_next">Играть дальше</string>
<string name="album_bottom_sheet_remove_all">Убрать все</string>
<string name="album_bottom_sheet_share">Поделиться</string>
<string name="album_bottom_sheet_shuffle">Перемешать</string>
<string name="album_catalogue_title">Альбомы</string>
<string name="album_catalogue_title_expanded">Просмотр альбомов</string>
<string name="album_error_retrieving_artist">Не удалось получить исполнителя.</string>
<string name="album_list_page_downloaded">Скачанные альбомы</string>
<string name="album_list_page_most_played">Самые проигрываемые альбомы</string>
<string name="album_list_page_new_releases">Новые релизы</string>
<string name="album_list_page_recently_added">Недавно добавленные альбомы</string>
<string name="album_list_page_recently_played">Недавно воспроизведенные альбомы</string>
<string name="album_list_page_starred">Помеченные альбомы</string>
<string name="album_list_page_title">Альбомы</string>
<string name="album_page_extra_info_button">Больше подобного</string>
<string name="album_page_play_button">Играть</string>
<string name="album_page_release_date_label">Выпущен %1$s</string>
<string name="album_page_release_dates_label">Выпущен %1$s, оригинал %2$s</string>
<string name="album_page_shuffle_button">Смешать</string>
<string name="album_page_tracks_count_and_duration">%1$d треков • %2$d минут</string>
<string name="app_name">Tempo</string>
<string name="artist_adapter_radio_station_starting">Поиск…</string>
<string name="artist_bottom_sheet_instant_mix">Мгновенный микс</string>
<string name="artist_bottom_sheet_shuffle">Смешать</string>
<string name="artist_catalogue_title">Артисты</string>
<string name="artist_catalogue_title_expanded">Просмотр исполнителя</string>
<string name="artist_error_retrieving_radio">Ошибка при получении радио исполнителя.</string>
<string name="artist_error_retrieving_tracks">Ошибка при получении треков исполнителя.</string>
<string name="artist_list_page_downloaded">Скачанные исполнители</string>
<string name="artist_list_page_starred">Рейтинговые исполнители</string>
<string name="artist_list_page_title">Исполнители</string>
<string name="artist_page_radio_button">Радио</string>
<string name="artist_page_shuffle_button">Смешать</string>
<string name="artist_page_switch_layout_button">Переключить раскладку</string>
<string name="artist_page_title_album_more_like_this_button">Больше подобного</string>
<string name="artist_page_title_album_section">Альбомы</string>
<string name="artist_page_title_biography_more_button">Больше</string>
<string name="artist_page_title_biography_section">Биография</string>
<string name="artist_page_title_most_streamed_song_section">Самые прослушиваемые треки</string>
<string name="artist_page_title_most_streamed_song_see_all_button">Посмотреть все</string>
<string name="battery_optimization_negative_button">Игнорировать</string>
<string name="battery_optimization_neutral_button">Больше не спрашивать</string>
<string name="battery_optimization_positive_button">Отключить</string>
<string name="connection_alert_dialog_negative_button">Отмена</string>
<string name="connection_alert_dialog_neutral_button">Включить сохранение данных</string>
<string name="connection_alert_dialog_positive_button">OK</string>
<string name="connection_alert_dialog_summary">Доступ к серверу Subsonic по соединениям, отличным от Wi-Fi, ограничен. Чтобы это диалоговое окно предупреждения не появлялось снова, отключите проверку соединения в настройках приложения.</string>
<string name="connection_alert_dialog_title">Wi-Fi не подключен</string>
<string name="content_description_shuffle_button">Перемешать</string>
<string name="delete_download_storage_dialog_negative_button">Отмена</string>
<string name="delete_download_storage_dialog_positive_button">Продолжить</string>
<string name="delete_download_storage_dialog_summary">Имейте в виду, что продолжение этого действия приведет к безвозвратному удалению всех сохраненных элементов, загруженных со всех серверов.</string>
<string name="delete_download_storage_dialog_title">Удалить сохраненные элементы</string>
<string name="description_empty_title">Нет описания</string>
<string name="disc_titlefull">Диск %1$s - %2$s</string>
<string name="disc_titleless">Диск %1$s</string>
<string name="download_directory_dialog_negative_button">Отмена</string>
<string name="download_directory_dialog_positive_button">Загрузить</string>
<string name="download_directory_dialog_summary">Все треки из этой папки будут загружены. Треки, находящиеся в подпапках, не будут загружены.</string>
<string name="download_directory_dialog_title">Скачать треки</string>
<string name="download_info_empty_subtitle">Скачав песню, вы найдете ее здесь.</string>
<string name="download_info_empty_title">Загрузок пока нет!</string>
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s товаров</string>
<string name="download_item_single_subtitle_formatter">%1$s предметов</string>
<string name="download_shuffle_all_subtitle">Перемешать все</string>
<string name="download_storage_dialog_sub_summary">Чтобы изменения вступили в силу, перезапустите приложение.</string>
<string name="download_storage_dialog_summary">Изменение места назначения загружаемых файлов из одного хранилища в другое приведет к немедленному удалению всех ранее загруженных файлов в другом хранилище.</string>
<string name="download_storage_dialog_title">Выберите вариант хранения</string>
<string name="download_storage_external_dialog_positive_button">Внешний</string>
<string name="download_storage_internal_dialog_negative_button">Внутренний</string>
<string name="download_title_section">Загрузки</string>
<string name="downloaded_bottom_sheet_add_to_queue">Добавить в очередь</string>
<string name="downloaded_bottom_sheet_play_next">Играть дальше</string>
<string name="downloaded_bottom_sheet_remove">Удалить</string>
<string name="downloaded_bottom_sheet_remove_all">Убрать все</string>
<string name="downloaded_bottom_sheet_shuffle">Смешать</string>
<string name="activity_battery_optimizations_conclusion">"Если у вас возникли проблемы, посетите https://dontkillmyapp.com. Он содержит подробные инструкции о том, как отключить любые функции энергосбережения, которые могут повлиять на производительность приложения"</string>
<string name="activity_battery_optimizations_summary">"Пожалуйста, отключите оптимизацию батареи для воспроизведения мультимедиа при выключенном экране."</string>
<string name="activity_battery_optimizations_title">"Оптимизация батареи"</string>
<string name="activity_info_offline_mode">"Офлайн-режим"</string>
<string name="album_bottom_sheet_add_to_queue">"Добавить в очередь"</string>
<string name="album_bottom_sheet_download_all">"Скачать все"</string>
<string name="album_bottom_sheet_go_to_artist">"Перейти к исполнителю"</string>
<string name="album_bottom_sheet_instant_mix">"Мгновенный микс"</string>
<string name="album_bottom_sheet_play_next">"Играть дальше"</string>
<string name="album_bottom_sheet_remove_all">"Убрать все"</string>
<string name="album_bottom_sheet_share">"Поделиться"</string>
<string name="album_bottom_sheet_shuffle">"Перемешать"</string>
<string name="album_catalogue_title">"Альбомы"</string>
<string name="album_catalogue_title_expanded">"Просмотр альбомов"</string>
<string name="album_error_retrieving_artist">"Не удалось получить исполнителя."</string>
<string name="album_list_page_downloaded">"Скачанные альбомы"</string>
<string name="album_list_page_most_played">"Самые проигрываемые альбомы"</string>
<string name="album_list_page_new_releases">"Новые релизы"</string>
<string name="album_list_page_recently_added">"Недавно добавленные альбомы"</string>
<string name="album_list_page_recently_played">"Недавно воспроизведенные альбомы"</string>
<string name="album_list_page_starred">"Помеченные альбомы"</string>
<string name="album_list_page_title">"Альбомы"</string>
<string name="album_page_extra_info_button">"Больше подобного"</string>
<string name="album_page_play_button">"Играть"</string>
<string name="album_page_shuffle_button">"Смешать"</string>
<string name="album_page_tracks_count_and_duration">"%1$d треков • %2$d минут"</string>
<string name="app_name">"Tempo"</string>
<string name="artist_adapter_radio_station_starting">"Поиск…"</string>
<string name="artist_bottom_sheet_instant_mix">"Мгновенный микс"</string>
<string name="artist_bottom_sheet_shuffle">"Смешать"</string>
<string name="artist_catalogue_title">"Артисты"</string>
<string name="artist_catalogue_title_expanded">"Посмотреть исполнителя"</string>
<string name="artist_error_retrieving_radio">"Ошибка при получении радио исполнителя."</string>
<string name="artist_error_retrieving_tracks">"Ошибка при получении треков исполнителя."</string>
<string name="artist_list_page_downloaded">"Скачанные исполнители"</string>
<string name="artist_list_page_starred">"Рейтинговые исполнители"</string>
<string name="artist_list_page_title">сполнители"</string>
<string name="artist_page_radio_button">"Радио"</string>
<string name="artist_page_shuffle_button">"Смешать"</string>
<string name="artist_page_switch_layout_button">"Переключить раскладку"</string>
<string name="artist_page_title_album_more_like_this_button">"Больше подобного"</string>
<string name="artist_page_title_album_section">"Альбомы"</string>
<string name="artist_page_title_biography_more_button">"Больше"</string>
<string name="artist_page_title_biography_section">"Биография"</string>
<string name="artist_page_title_most_streamed_song_section">"Самые прослушиваемые треки"</string>
<string name="artist_page_title_most_streamed_song_see_all_button">"Посмотреть все"</string>
<string name="battery_optimization_negative_button">"Игнорировать"</string>
<string name="battery_optimization_neutral_button">"Больше не спрашивать"</string>
<string name="battery_optimization_positive_button">"Отключить"</string>
<string name="connection_alert_dialog_negative_button">"Отмена"</string>
<string name="connection_alert_dialog_neutral_button">"Включить сохранение данных"</string>
<string name="connection_alert_dialog_positive_button">"OK"</string>
<string name="connection_alert_dialog_summary">"Доступ к серверу Subsonic по соединениям, отличным от Wi-Fi, ограничен. Чтобы это диалоговое окно предупреждения не появлялось снова, отключите проверку соединения в настройках приложения."</string>
<string name="connection_alert_dialog_title">"Wi-Fi не подключен"</string>
<string name="delete_download_storage_dialog_negative_button">"Отмена"</string>
<string name="delete_download_storage_dialog_positive_button">"Продолжить"</string>
<string name="delete_download_storage_dialog_summary">"Имейте в виду, что продолжение этого действия приведет к безвозвратному удалению всех сохраненных элементов, загруженных со всех серверов."</string>
<string name="delete_download_storage_dialog_title">"Удалить сохраненные элементы"</string>
<string name="description_empty_title">"Нет описания"</string>
<string name="download_directory_dialog_negative_button">"Отмена"</string>
<string name="download_directory_dialog_positive_button">"Загрузить"</string>
<string name="download_directory_dialog_summary">"Все треки из этой папки будут загружены. Треки, находящиеся в подпапках, не будут загружены."</string>
<string name="download_directory_dialog_title">"Скачать треки"</string>
<string name="download_info_empty_subtitle">"Скачав песню, вы найдете ее здесь."</string>
<string name="download_info_empty_title">"Загрузок пока нет!"</string>
<string name="download_item_multiple_subtitle_formatter">"%1$s • %2$s товаров"</string>
<string name="download_item_single_subtitle_formatter">"%1$s предметов"</string>
<string name="download_shuffle_all_subtitle">"Перемешать все"</string>
<string name="download_storage_dialog_sub_summary">"Чтобы изменения вступили в силу, перезапустите приложение."</string>
<string name="download_storage_dialog_summary">"Изменение места назначения загружаемых файлов из одного хранилища в другое приведет к немедленному удалению всех ранее загруженных файлов в другом хранилище."</string>
<string name="download_storage_dialog_title">"Выберите вариант хранения"</string>
<string name="download_storage_external_dialog_positive_button">"Внешний"</string>
<string name="download_storage_internal_dialog_negative_button">"Внутренний"</string>
<string name="download_title_section">"Загрузки"</string>
<string name="downloaded_bottom_sheet_add_to_queue">"Добавить в очередь"</string>
<string name="downloaded_bottom_sheet_play_next">"Играть дальше"</string>
<string name="downloaded_bottom_sheet_remove">"Удалить"</string>
<string name="downloaded_bottom_sheet_remove_all">"Убрать все"</string>
<string name="downloaded_bottom_sheet_shuffle">"Смешать"</string>
<string name="empty_string"></string>
<string name="error_required">Необходимый</string>
<string name="error_server_prefix">Требуется префикс http или https</string>
<string name="exo_download_notification_channel_name">Загрузки</string>
<string name="filter_info_selection">Выберите два или более фильтров</string>
<string name="filter_title">Фильтр</string>
<string name="filter_title_expanded">Фильтровать жанры</string>
<string name="genre_catalogue_title">Каталог жанров</string>
<string name="genre_catalogue_title_expanded">Просмотр жанров</string>
<string name="github_update_dialog_negative_button">Напомнить позже</string>
<string name="github_update_dialog_neutral_button">Поддержать меня</string>
<string name="github_update_dialog_positive_button">Скачать сейчас</string>
<string name="github_update_dialog_summary">На Github доступна новая версия приложения.</string>
<string name="github_update_dialog_title">Доступно обновление</string>
<string name="home_rearrangement_dialog_negative_button">Отмена</string>
<string name="home_rearrangement_dialog_neutral_button">Перезагрузить</string>
<string name="home_rearrangement_dialog_positive_button">Сохранять</string>
<string name="home_rearrangement_dialog_title">Настроить главную</string>
<string name="home_rearrangement_dialog_subtitle">Обратите внимание, чтобы внесенные изменения вступили в силу, необходимо перезапустить приложение.</string>
<string name="home_subtitle_best_of">Лучшие треки любимых исполнителей</string>
<string name="home_subtitle_made_for_you">Запустите микс с понравившимся вам треком</string>
<string name="home_subtitle_new_internet_radio_station">Добавить новое радио</string>
<string name="home_subtitle_new_podcast_channel">Добавить новый канал подкаста</string>
<string name="home_sync_starred_cancel">Отмена</string>
<string name="home_sync_starred_download">Скачать</string>
<string name="home_sync_starred_subtitle">Загрузка этих треков может потребовать значительного использования данных</string>
<string name="home_sync_starred_title">Похоже, есть несколько отмеченных треков для синхронизации.</string>
<string name="home_title_best_of">Лучшее из</string>
<string name="home_title_discovery">Открытие</string>
<string name="home_title_discovery_shuffle_all_button">Перемешать все</string>
<string name="home_title_flashback">Воспоминание</string>
<string name="home_title_internet_radio_station">Интернет-радиостанции</string>
<string name="home_title_last_played">Последнее воспроизведение</string>
<string name="home_title_last_played_see_all_button">Увидеть все</string>
<string name="home_title_last_week">На прошлой неделе</string>
<string name="home_title_last_month">Прошлый месяц</string>
<string name="home_title_last_year">Прошлый год</string>
<string name="home_title_made_for_you">Сделано для тебя</string>
<string name="home_title_most_played">Самое популярное</string>
<string name="home_title_most_played_see_all_button">Увидеть все</string>
<string name="home_title_new_releases">Новые релизы</string>
<string name="home_title_newest_podcasts">Новейшие подкасты</string>
<string name="home_title_podcast_channels">Каналы</string>
<string name="home_title_podcast_channels_see_all_button">Увидеть все</string>
<string name="home_title_radio_station">Радиостанции</string>
<string name="home_title_recently_added">Недавно добавленный</string>
<string name="home_title_recently_added_see_all_button">Увидеть все</string>
<string name="home_title_shares">Общий доступ</string>
<string name="home_title_starred_albums">★ Отмеченные альбомы</string>
<string name="home_title_starred_albums_see_all_button">Увидеть все</string>
<string name="home_title_starred_artists">★ Рейтинговые артисты</string>
<string name="home_title_starred_artists_see_all_button">Увидеть все</string>
<string name="home_title_starred_tracks">★ Отмеченные треки</string>
<string name="home_title_starred_tracks_see_all_button">Увидеть все</string>
<string name="home_title_top_songs">Ваши лучшие треки</string>
<string name="home_option_reorganize">Реорганизовать</string>
<string name="library_title_album">Альбомы</string>
<string name="library_title_album_see_all_button">Увидеть все</string>
<string name="library_title_artist">Исполнители</string>
<string name="library_title_artist_see_all_button">Увидеть все</string>
<string name="library_title_genre">Жанры</string>
<string name="library_title_genre_see_all_button">Увидеть все</string>
<string name="library_title_music_folder">Музыкальные папки</string>
<string name="library_title_playlist">Плейлисты</string>
<string name="library_title_playlist_see_all_button">Увидеть все</string>
<string name="login_empty">Сервер не добавлен</string>
<string name="login_title">Subsonic серверы</string>
<string name="login_title_expanded">Subsonic серверы</string>
<string name="media_route_menu_title">Cast</string>
<string name="menu_add_button">Добавить</string>
<string name="menu_download_all_button">Скачать все</string>
<string name="menu_download_label">Скачать</string>
<string name="menu_filter_all">Все</string>
<string name="menu_filter_download">Загружено</string>
<string name="menu_group_by_album">Альбом</string>
<string name="menu_group_by_artist">Исполнитель</string>
<string name="menu_group_by_genre">Жанр</string>
<string name="menu_group_by_track">Трек</string>
<string name="menu_group_by_year">Год</string>
<string name="menu_home_label">Главная</string>
<string name="menu_library_label">Библиотека</string>
<string name="menu_search_button">Поиск</string>
<string name="menu_settings_button">Настройки</string>
<string name="menu_sort_artist">Исполнитель</string>
<string name="menu_sort_name">Имя</string>
<string name="menu_sort_random">Случайный</string>
<string name="menu_sort_recently_added">Недавно добавленный</string>
<string name="menu_sort_recently_played">Недавно воспроизведенный</string>
<string name="menu_sort_most_played">Самое популярное</string>
<string name="menu_sort_most_recently_starred">Недавно отмеченный</string>
<string name="menu_sort_least_recently_starred">Давно отмеченный</string>
<string name="menu_sort_year">Год</string>
<string name="menu_pin_button">Добавить на главный экран</string>
<string name="menu_unpin_button">Убрать с главного экрана</string>
<string name="player_playback_speed">%1$.2fx</string>
<string name="player_queue_clean_all_button">Очистить очередь воспроизведения</string>
<string name="player_server_priority">Приоритет сервера</string>
<string name="playlist_catalogue_title">Каталог плейлистов</string>
<string name="playlist_catalogue_title_expanded">Просмотр плейлистов</string>
<string name="playlist_chooser_dialog_empty">Плейлисты не созданы</string>
<string name="playlist_chooser_dialog_negative_button">Отмена</string>
<string name="playlist_chooser_dialog_neutral_button">Создать</string>
<string name="playlist_chooser_dialog_title">Добавить в плейлист</string>
<string name="playlist_counted_tracks">%1$d треков • %2$s</string>
<string name="playlist_duration">Продолжительность • %1$s</string>
<string name="playlist_editor_dialog_action_delete_toast">Долгое нажатие для удаления</string>
<string name="playlist_editor_dialog_hint_name">Название плейлиста</string>
<string name="playlist_editor_dialog_negative_button">Отмена</string>
<string name="playlist_editor_dialog_neutral_button">Удалить</string>
<string name="playlist_editor_dialog_positive_button">Сохранять</string>
<string name="playlist_editor_dialog_title">Редактировать плейлист</string>
<string name="playlist_page_play_button">Воспроизвести</string>
<string name="playlist_page_shuffle_button">Смешать</string>
<string name="playlist_song_count">Плейлист • %1$d треки</string>
<string name="podcast_bottom_sheet_add_to_queue">Добавить в очередь</string>
<string name="podcast_bottom_sheet_delete">Удалить</string>
<string name="podcast_bottom_sheet_download">Скачать</string>
<string name="podcast_bottom_sheet_go_to_channel">Перейти на канал</string>
<string name="podcast_bottom_sheet_play_next">Играть дальше</string>
<string name="podcast_bottom_sheet_remove">Удалить</string>
<string name="podcast_channel_catalogue_title">Каналы</string>
<string name="podcast_channel_catalogue_title_expanded">Просмотр каналов</string>
<string name="podcast_channel_editor_dialog_hint_rss_url">RSS-адрес</string>
<string name="podcast_channel_editor_dialog_title">Подкаст-канал</string>
<string name="podcast_channel_page_title_description_section">Описание</string>
<string name="podcast_channel_page_title_episode_section">Эпизоды</string>
<string name="podcast_channel_page_title_no_episode_available">Нет доступных серий</string>
<string name="podcast_episode_download_request_snackbar">Ваш запрос отправлен на сервер</string>
<string name="podcast_info_empty_button">Нажмите, чтобы скрыть раздел. Изменения будут видны при перезапуске</string>
<string name="podcast_info_empty_subtitle">Добавив канал, вы найдете его здесь</string>
<string name="podcast_info_empty_title">Подкасты не найдены!</string>
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
<string name="radio_editor_dialog_hint_homepage_url">URL-адрес домашней страницы радио</string>
<string name="radio_editor_dialog_hint_name">Название радио</string>
<string name="radio_editor_dialog_hint_stream_url">URL-адрес радиопотока</string>
<string name="radio_editor_dialog_negative_button">Отмена</string>
<string name="radio_editor_dialog_neutral_button">Удалить</string>
<string name="radio_editor_dialog_positive_button">Сохранять</string>
<string name="radio_editor_dialog_title">Интернет-радиостанция</string>
<string name="radio_station_info_empty_button">Нажмите, чтобы скрыть раздел. Изменения будут видны при перезапуске</string>
<string name="radio_station_info_empty_subtitle">Добавив радиостанцию, вы найдете ее здесь</string>
<string name="radio_station_info_empty_title">Станции не найдены!</string>
<string name="rating_dialog_negative_button">Отмена</string>
<string name="rating_dialog_positive_button">Сохранять</string>
<string name="rating_dialog_title">Рейтинг</string>
<string name="search_hint">Поиск по названию, исполнителям или альбомам</string>
<string name="search_info_minimum_characters">Введите не менее трех символов</string>
<string name="search_title_album">Альбомы</string>
<string name="search_title_artist">Исполнители</string>
<string name="search_title_song">Треки</string>
<string name="server_signup_dialog_action_low_security">Низкая безопасность</string>
<string name="server_signup_dialog_action_delete_toast">Долгое нажатие для удаления</string>
<string name="server_signup_dialog_hint_local_address">Локальный URL</string>
<string name="server_signup_dialog_hint_name">Имя сервера</string>
<string name="server_signup_dialog_hint_password">Пароль</string>
<string name="server_signup_dialog_hint_url">URL-адрес сервера</string>
<string name="server_signup_dialog_hint_username">Имя пользователя</string>
<string name="server_signup_dialog_negative_button">Отмена</string>
<string name="server_signup_dialog_neutral_button">Удалить</string>
<string name="server_signup_dialog_positive_button">Сохранить</string>
<string name="server_signup_dialog_title">Добавить сервер</string>
<string name="server_unreachable_dialog_negative_button">Отмена</string>
<string name="server_unreachable_dialog_neutral_button">Перейти к входу</string>
<string name="server_unreachable_dialog_positive_button">Продолжить в любом случае</string>
<string name="server_unreachable_dialog_summary">Запрошенный сервер недоступен. Если вы решите продолжить, это диалоговое окно не появится в течение следующего часа</string>
<string name="server_unreachable_dialog_title">Сервер недоступен</string>
<string name="settings_about_summary">Tempo — это легкий музыкальный клиент с открытым исходным кодом для Subsonic, разработанный и созданный специально для Android.</string>
<string name="settings_about_title">О нас</string>
<string name="settings_always_on_display">Всегда на дисплее</string>
<string name="settings_audio_transcode_download_format">Формат перекодирования</string>
<string name="settings_audio_transcode_download_priority_summary">Если этот параметр включен, Tempo не будет принудительно загружать трек с настройками перекодирования, указанными ниже.</string>
<string name="settings_audio_transcode_download_priority_title">Установите приоритет настроек сервера, используемых для потоковой передачи при загрузке</string>
<string name="settings_audio_transcode_download_summary">Если этот параметр включен, Tempo будет загружать перекодированные треки.</string>
<string name="settings_audio_transcode_download_title">Скачать перекодированные треки</string>
<string name="settings_audio_transcode_estimate_content_length_summary">Если этот параметр включен, на сервере будет запрошена предполагаемая продолжительность трека.</string>
<string name="settings_audio_transcode_estimate_content_length_title">Оцените длину содержимого</string>
<string name="settings_audio_transcode_format_download">Формат перекодирования для загрузки</string>
<string name="settings_audio_transcode_format_mobile">Формат перекодирования в мобильном телефоне</string>
<string name="settings_audio_transcode_format_wifi">Перекодировать формат в Wi-Fi</string>
<string name="settings_audio_transcode_priority_summary">Если этот параметр включен, Tempo не будет принудительно транслировать трек с настройками перекодирования, указанными ниже.</string>
<string name="settings_audio_transcode_priority_title">Приоритизация настроек перекодирования сервера</string>
<string name="settings_audio_transcode_priority_toast">Приоритет при перекодировании трека отдается серверу</string>
<string name="settings_buffering_strategy">Стратегия буферизации</string>
<string name="settings_buffering_strategy_summary">Чтобы изменения вступили в силу, необходимо вручную перезапустить приложение.</string>
<string name="settings_covers_cache">Размер кэша обложек</string>
<string name="settings_data_saving_mode_summary">Чтобы сократить потребление данных, избегайте загрузки обложек.</string>
<string name="settings_data_saving_mode_title">Ограничить использование мобильных данных</string>
<string name="settings_delete_download_storage_summary">Продолжение приведет к необратимому удалению всех сохраненных элементов.</string>
<string name="settings_delete_download_storage_title">Удалить сохраненные элементы</string>
<string name="settings_download_storage_title">Загрузить хранилище</string>
<string name="settings_equalizer_summary">Отрегулируйте настройки звука</string>
<string name="settings_equalizer_title">Эквалайзер</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string>
<string name="settings_github_summary">Следите за развитием</string>
<string name="settings_github_title">Github</string>
<string name="settings_image_size">Установить разрешение изображения</string>
<string name="settings_language">Язык</string>
<string name="settings_logout_title">Выйти</string>
<string name="settings_max_bitrate_download">Битрейт для скачиваний</string>
<string name="settings_max_bitrate_mobile">Битрейт в мобильной сети 4G/5G</string>
<string name="settings_max_bitrate_wifi">Битрейт через соединение Wi-Fi</string>
<string name="settings_media_cache">Размер кэша медиафайлов</string>
<string name="settings_music_directory">Показать музыкальные каталоги</string>
<string name="settings_music_directory_summary">Если включено, то показывать раздел музыкального каталога. Обратите внимание: для правильной работы навигации по папкам сервер должен поддерживать эту функцию.</string>
<string name="settings_podcast">Показать подкаст</string>
<string name="settings_podcast_summary">Если включено, показывать раздел подкаста. Перезапустите приложение, чтобы оно вступило в силу.</string>
<string name="settings_audio_quality">Показать качество звука (битрейт)</string>
<string name="settings_audio_quality_summary">Битрейт и аудиоформат будут показаны для каждой аудиодорожки.</string>
<string name="settings_item_rating">Показать рейтинг</string>
<string name="settings_item_rating_summary">Если эта функция включена, будет отображаться рейтинг элемента и то, отмечен ли он как избранный.</string>
<string name="settings_queue_syncing_countdown">Таймер синхронизации</string>
<string name="settings_queue_syncing_summary">Если этот параметр включен, пользователь будет иметь возможность сохранять свою очередь воспроизведения и загружать состояние при открытии приложения.</string>
<string name="settings_queue_syncing_title">Синхронизировать очередь воспроизведения для этого пользователя</string>
<string name="settings_radio">Показать радио</string>
<string name="settings_radio_summary">Если включено, показывать раздел радио. Перезапустите приложение, чтобы оно вступило в силу.</string>
<string name="settings_replay_gain">Установите режим усиления воспроизведения</string>
<string name="settings_rounded_corner">Закругленные углы</string>
<string name="settings_rounded_corner_size">Размер углов</string>
<string name="settings_rounded_corner_size_summary">Устанавливает величину угла кривизны.</string>
<string name="settings_rounded_corner_summary">Если этот параметр включен, задает угол кривизны для всех отображаемых обложек. Изменения вступят в силу при перезапуске.</string>
<string name="settings_scan_title">Сканировать библиотеку</string>
<string name="settings_scrobble_title">Включить скробблинг музыки Last.FM и т.д.</string>
<string name="settings_share_title">Включить обмен музыкой</string>
<string name="settings_sub_summary_scrobble">Важно отметить, что скробблинг также зависит от того, настроен ли сервер для получения этих данных.</string>
<string name="settings_summary_skip_min_star_rating">При прослушивании радио исполнителя, мгновенном миксе или перемешивании всех, треки ниже определенного пользовательского рейтинга будут игнорироваться.</string>
<string name="settings_summary_replay_gain">Усиление воспроизведения — это функция, которая позволяет регулировать уровень громкости звуковых дорожек для обеспечения единообразного качества прослушивания. Этот параметр действует только в том случае, если трек содержит необходимые метаданные.</string>
<string name="settings_summary_scrobble">Скробблинг — это функция, которая позволяет вашему устройству отправлять информацию о песнях, которые вы слушаете, на музыкальный сервер. Эта информация помогает создавать персональные рекомендации на основе ваших музыкальных предпочтений.</string>
<string name="settings_summary_share">Позволяет пользователю делиться музыкой по ссылке. Функциональность должна поддерживаться и включаться на стороне сервера и ограничивается отдельными треками, альбомами и плейлистами.</string>
<string name="settings_summary_syncing">Возвращает состояние очереди воспроизведения для этого пользователя. Сюда входят треки в очереди воспроизведения, воспроизводимый в данный момент трек и позиция внутри этого трека. Сервер должен поддерживать эту функцию.</string>
<string name="settings_summary_transcoding">Приоритет отдается режиму перекодирования. Если установлено «Прямое воспроизведение», битрейт файла не изменится.</string>
<string name="settings_summary_transcoding_download">Загрузите перекодированные медиафайлы. Если этот параметр включен, будет использоваться не конечная точка загрузки, а следующие настройки. Если для параметра «Формат перекодирования для загрузки» установлено значение «Прямая загрузка», битрейт файла не изменится.</string>
<string name="settings_summary_transcoding_estimate_content_length">Когда файл перекодируется на лету, клиент обычно не показывает длину трека. Можно запросить у серверов, поддерживающих данную функцию, оценку длительности воспроизводимого трека, но время ответа может занять больше времени.</string>
<string name="settings_sync_starred_tracks_for_offline_use_summary">Если этот параметр включен, помеченные треки будут загружены для использования в автономном режиме.</string>
<string name="settings_sync_starred_tracks_for_offline_use_title">Синхронизируйте помеченные треки для использования в автономном режиме.</string>
<string name="settings_theme">Тема</string>
<string name="settings_title_data">Данные</string>
<string name="settings_title_general">Общий</string>
<string name="settings_title_rating">Рейтинг</string>
<string name="settings_title_replay_gain">Усиление воспроизведения</string>
<string name="settings_title_scrobble">Скроббл</string>
<string name="settings_title_skip_min_star_rating">Игнорировать треки по рейтингу</string>
<string name="settings_title_skip_min_star_rating_dialog">Треки с рейтингом:</string>
<string name="settings_title_share">Поделиться</string>
<string name="settings_title_syncing">Синхронизации</string>
<string name="settings_title_transcoding">Транскодирование</string>
<string name="settings_title_transcoding_download">Транскодирование Скачать</string>
<string name="settings_title_ui">UI (Пользовательский интерфейс)</string>
<string name="settings_transcoded_download">Перекодированная загрузка</string>
<string name="settings_version_title">Версия</string>
<string name="settings_wifi_only_summary">Запросить подтверждение пользователя перед потоковой передачей по мобильной сети.</string>
<string name="settings_wifi_only_title">Оповещение о потоковой передаче только через Wi-Fi</string>
<string name="share_bottom_sheet_copy_link">Копировать ссылку</string>
<string name="share_bottom_sheet_delete">Удалить общий доступ</string>
<string name="share_bottom_sheet_update">Обновить общий доступ</string>
<string name="share_subtitle_item">Срок действия: %1$s</string>
<string name="share_unsupported_error">Общий доступ не поддерживается или не включен</string>
<string name="share_update_dialog_hint_description">Описание</string>
<string name="share_update_dialog_hint_expiration_date">Дата окончания срока</string>
<string name="share_update_dialog_negative_button">Отмена</string>
<string name="share_update_dialog_positive_button">Сохранять</string>
<string name="share_update_dialog_title">Поделиться</string>
<string name="song_bottom_sheet_add_to_playlist">Добавить в плейлист</string>
<string name="song_bottom_sheet_add_to_queue">Добавить в очередь</string>
<string name="song_bottom_sheet_download">Скачать</string>
<string name="song_bottom_sheet_error_retrieving_album">Ошибка при получении альбома</string>
<string name="song_bottom_sheet_error_retrieving_artist">Не удалось получить исполнителя</string>
<string name="song_bottom_sheet_go_to_album">Перейти в альбом</string>
<string name="song_bottom_sheet_go_to_artist">Перейти к исполнителю</string>
<string name="song_bottom_sheet_instant_mix">Мгновенный микс</string>
<string name="song_bottom_sheet_play_next">Играть дальше</string>
<string name="song_bottom_sheet_rate">Оценить</string>
<string name="song_bottom_sheet_remove">Удалить</string>
<string name="song_bottom_sheet_share">Поделиться</string>
<string name="song_list_page_downloaded">Загружено</string>
<string name="song_list_page_most_played">Самые популярные треки</string>
<string name="song_list_page_recently_added">Недавно добавленные треки</string>
<string name="song_list_page_recently_played">Недавно воспроизведенные треки</string>
<string name="song_list_page_starred">Помеченные треки</string>
<string name="song_list_page_top">%1$s\'s Лучшие треки</string>
<string name="song_list_page_year">Год %1$d</string>
<string name="song_subtitle_formatter">%1$s • %2$s %3$s</string>
<string name="starred_sync_dialog_negative_button">Отмена</string>
<string name="starred_sync_dialog_neutral_button">Продолжить</string>
<string name="starred_sync_dialog_positive_button">Продолжить и скачать</string>
<string name="starred_sync_dialog_summary">Для скачивания рейтинговых треков может потребоваться большой объем данных.</string>
<string name="starred_sync_dialog_title">Синхронизировать отмеченные треки</string>
<string name="track_info_album">Альбом</string>
<string name="track_info_artist">Исполнитель</string>
<string name="track_info_bitrate">Битрейт</string>
<string name="track_info_content_type">Тип содержимого</string>
<string name="track_info_dialog_positive_button">OK</string>
<string name="track_info_dialog_title">Информация о треке</string>
<string name="track_info_disc_number">Номер диска</string>
<string name="track_info_duration">Продолжительность</string>
<string name="track_info_genre">Жанр</string>
<string name="track_info_path">Путь</string>
<string name="track_info_size">Размер</string>
<string name="track_info_suffix">Суффикс</string>
<string name="track_info_summary_downloaded_file">Файл был загружен с использованием API Subsonic. Кодек и битрейт файла остаются неизменными по сравнению с исходным файлом.</string>
<string name="track_info_summary_full_transcode">Приложение запросит сервер перекодировать файл и изменить его битрейт. Запрошенный пользователем кодек: %1$s, с битрейтом %2$s. Любые потенциальные изменения кодека и битрейта файла в выбранном формате будут обрабатываться сервером, который может поддерживать или не поддерживать эту операцию.</string>
<string name="track_info_summary_original_file">Приложение будет читать только исходный файл, предоставленный сервером. Приложение явно запросит у сервера неперекодированный файл с битрейтом исходного источника.</string>
<string name="track_info_summary_server_prioritized">Качество воспроизводимого файла остается на усмотрение сервера. Приложение не будет принудительно выбирать кодек и битрейт для любого потенциального перекодирования.</string>
<string name="track_info_summary_transcoding_bitrate">Приложение запросит сервер изменить битрейт файла. Пользователь запросил битрейт %1$s, при этом кодек исходного файла останется прежним. Любые изменения битрейта файла в выбранном формате будут выполняться сервером, который может поддерживать или не поддерживать эту операцию.</string>
<string name="track_info_summary_transcoding_codec">Приложение запросит сервер перекодировать файл. Запрошенный пользователем кодек — %1$s, а битрейт будет такой же, как у исходного файла. Потенциальное перекодирование файла в выбранный формат зависит от сервера, поскольку он может поддерживать или не поддерживать эту операцию.</string>
<string name="track_info_title">Заголовок</string>
<string name="track_info_track_number">Номер трека</string>
<string name="track_info_transcoded_content_type">Тип транскодированного контента</string>
<string name="track_info_transcoded_suffix">Транскодированный суффикс</string>
<string name="track_info_year">Год</string>
<string name="undraw_page">Развернуть</string>
<string name="undraw_thanks">Особая благодарность — команде unDraw, без иллюстраций которой мы не смогли бы сделать это приложение красивее.</string>
<string name="undraw_url">https://undraw.co/</string>
<string name="error_required">"Необходимый"</string>
<string name="error_server_prefix">"Требуется префикс http или https"</string>
<string name="exo_download_notification_channel_name">"Загрузки"</string>
<string name="filter_info_selection">"Выберите два или более фильтров"</string>
<string name="filter_title">"Фильтр"</string>
<string name="filter_title_expanded">"Фильтровать жанры"</string>
<string name="genre_catalogue_title">"Каталог жанров"</string>
<string name="genre_catalogue_title_expanded">"Просмотр жанров"</string>
<string name="home_rearrangement_dialog_negative_button">"Отмена"</string>
<string name="home_rearrangement_dialog_neutral_button">"Перезагрузить"</string>
<string name="home_rearrangement_dialog_positive_button">"Сохранять"</string>
<string name="home_rearrangement_dialog_title">"Настроить главную"</string>
<string name="home_rearrangement_dialog_subtitle">"Обратите внимание, чтобы внесенные изменения вступили в силу, необходимо перезапустить приложение."</string>
<string name="home_subtitle_best_of">"Лучшие треки любимых исполнителей"</string>
<string name="home_subtitle_made_for_you">"Запустите микс с понравившимся вам треком"</string>
<string name="home_subtitle_new_internet_radio_station">"Добавить новое радио"</string>
<string name="home_subtitle_new_podcast_channel">"Добавить новый канал подкаста"</string>
<string name="home_sync_starred_cancel">"Отмена"</string>
<string name="home_sync_starred_download">"Скачать"</string>
<string name="home_sync_starred_subtitle">"Загрузка этих треков может потребовать значительного использования данных"</string>
<string name="home_sync_starred_title">"Похоже, есть несколько отмеченных треков для синхронизации."</string>
<string name="home_title_best_of">"Лучшее из"</string>
<string name="home_title_discovery">"Открытие"</string>
<string name="home_title_discovery_shuffle_all_button">"Перемешать все"</string>
<string name="home_title_flashback">"Воспоминание"</string>
<string name="home_title_internet_radio_station">"Интернет-радиостанции"</string>
<string name="home_title_last_played">"Последнее воспроизведение"</string>
<string name="home_title_last_played_see_all_button">"Увидеть все"</string>
<string name="home_title_last_week">"На прошлой неделе"</string>
<string name="home_title_made_for_you">"Сделано для тебя"</string>
<string name="home_title_most_played">"Самое популярное"</string>
<string name="home_title_most_played_see_all_button">"Увидеть все"</string>
<string name="home_title_new_releases">"Новые релизы"</string>
<string name="home_title_newest_podcasts">"Новейшие подкасты"</string>
<string name="home_title_podcast_channels">"Каналы"</string>
<string name="home_title_podcast_channels_see_all_button">"Увидеть все"</string>
<string name="home_title_radio_station">"Радиостанции"</string>
<string name="home_title_recently_added">"Недавно добавленный"</string>
<string name="home_title_recently_added_see_all_button">"Увидеть все"</string>
<string name="home_title_shares">"Общий доступ"</string>
<string name="home_title_starred_albums">"★ Отмеченные альбомы"</string>
<string name="home_title_starred_albums_see_all_button">"Увидеть все"</string>
<string name="home_title_starred_artists">"★ Рейтинговые артисты"</string>
<string name="home_title_starred_artists_see_all_button">"Увидеть все"</string>
<string name="home_title_starred_tracks">"★ Отмеченные треки"</string>
<string name="home_title_starred_tracks_see_all_button">"Увидеть все"</string>
<string name="home_title_top_songs">"Ваши лучшие треки"</string>
<string name="home_option_reorganize">"Реорганизовать"</string>
<string name="library_title_album">"Альбомы"</string>
<string name="library_title_album_see_all_button">"Увидеть все"</string>
<string name="library_title_artist">"Исполнители"</string>
<string name="library_title_artist_see_all_button">"Увидеть все"</string>
<string name="library_title_genre">"Жанры"</string>
<string name="library_title_genre_see_all_button">"Увидеть все"</string>
<string name="library_title_music_folder">"Музыкальные папки"</string>
<string name="library_title_playlist">"Плейлисты"</string>
<string name="library_title_playlist_see_all_button">"Увидеть все"</string>
<string name="login_empty">"Сервер не добавлен"</string>
<string name="login_title">"Subsonic серверы"</string>
<string name="login_title_expanded">"Subsonic серверы"</string>
<!-- Fuzzy -->
<string name="media_route_menu_title">"Cast"</string>
<string name="menu_add_button">"Добавить"</string>
<string name="menu_download_all_button">"Скачать все"</string>
<string name="menu_download_label">"Скачать"</string>
<string name="menu_filter_all">"Все"</string>
<string name="menu_filter_download">"Загружено"</string>
<string name="menu_group_by_album">"Альбом"</string>
<string name="menu_group_by_artist">"Исполнитель"</string>
<string name="menu_group_by_genre">"Жанр"</string>
<string name="menu_group_by_track">"Трек"</string>
<string name="menu_group_by_year">"Год"</string>
<!-- Fuzzy -->
<string name="menu_home_label">"Главная"</string>
<string name="menu_library_label">"Библиотека"</string>
<string name="menu_search_button">"Поиск"</string>
<string name="menu_settings_button">"Настройки"</string>
<string name="menu_sort_artist">"Исполнитель"</string>
<string name="menu_sort_name">"Имя"</string>
<string name="menu_sort_random">"Случайный"</string>
<string name="menu_sort_recently_added">"Недавно добавленный"</string>
<string name="menu_sort_year">"Год"</string>
<string name="player_playback_speed">"%1$.2fx"</string>
<string name="player_queue_clean_all_button">"Очистить очередь воспроизведения"</string>
<string name="player_server_priority">"Приоритет сервера"</string>
<string name="playlist_catalogue_title">"Каталог плейлистов"</string>
<string name="playlist_catalogue_title_expanded">"Просмотр плейлистов"</string>
<string name="playlist_chooser_dialog_empty">"Плейлисты не созданы"</string>
<string name="playlist_chooser_dialog_negative_button">"Отмена"</string>
<string name="playlist_chooser_dialog_neutral_button">"Создать"</string>
<string name="playlist_chooser_dialog_title">"Добавить в плейлист"</string>
<string name="playlist_counted_tracks">"%1$d треков • %2$s"</string>
<string name="playlist_duration">"Продолжительность • %1$s"</string>
<string name="playlist_editor_dialog_hint_name">"Название плейлиста"</string>
<string name="playlist_editor_dialog_negative_button">"Отмена"</string>
<string name="playlist_editor_dialog_neutral_button">"Удалить"</string>
<string name="playlist_editor_dialog_positive_button">"Сохранять"</string>
<string name="playlist_editor_dialog_title">"Редактировать плейлист"</string>
<string name="playlist_page_play_button">"Воспроизвести"</string>
<string name="playlist_page_shuffle_button">"Смешать"</string>
<string name="playlist_song_count">"Плейлист • %1$d треки"</string>
<string name="podcast_bottom_sheet_add_to_queue">"Добавить в очередь"</string>
<string name="podcast_bottom_sheet_delete">"Удалить"</string>
<string name="podcast_bottom_sheet_download">"Скачать"</string>
<string name="podcast_bottom_sheet_go_to_channel">"Перейти на канал"</string>
<string name="podcast_bottom_sheet_play_next">"Играть дальше"</string>
<string name="podcast_bottom_sheet_remove">"Удалить"</string>
<string name="podcast_channel_catalogue_title">"Каналы"</string>
<string name="podcast_channel_catalogue_title_expanded">"Просмотр каналов"</string>
<string name="podcast_channel_editor_dialog_hint_rss_url">"RSS-адрес"</string>
<string name="podcast_channel_editor_dialog_title">"Подкаст-канал"</string>
<string name="podcast_channel_page_title_description_section">"Описание"</string>
<string name="podcast_channel_page_title_episode_section">"Эпизоды"</string>
<string name="podcast_channel_page_title_no_episode_available">"Нет доступных серий"</string>
<string name="podcast_episode_download_request_snackbar">"Ваш запрос отправлен на сервер"</string>
<string name="podcast_info_empty_button">"Нажмите, чтобы скрыть раздел. Изменения будут видны при перезапуске"</string>
<string name="podcast_info_empty_subtitle">"Добавив канал, вы найдете его здесь"</string>
<string name="podcast_info_empty_title">"Подкасты не найдены!"</string>
<!-- Fuzzy -->
<string name="podcast_release_date_duration_formatter">"%1$s • %2$s"</string>
<string name="radio_editor_dialog_hint_homepage_url">"URL-адрес домашней страницы радио"</string>
<string name="radio_editor_dialog_hint_name">"Название радио"</string>
<string name="radio_editor_dialog_hint_stream_url">"URL-адрес радиопотока"</string>
<string name="radio_editor_dialog_negative_button">"Отмена"</string>
<string name="radio_editor_dialog_neutral_button">"Удалить"</string>
<string name="radio_editor_dialog_positive_button">"Сохранять"</string>
<string name="radio_editor_dialog_title">"Интернет-радиостанция"</string>
<string name="radio_station_info_empty_button">"Нажмите, чтобы скрыть раздел. Изменения будут видны при перезапуске"</string>
<string name="radio_station_info_empty_subtitle">"Добавив радиостанцию, вы найдете ее здесь"</string>
<string name="radio_station_info_empty_title">"Станции не найдены!"</string>
<string name="rating_dialog_negative_button">"Отмена"</string>
<string name="rating_dialog_positive_button">"Сохранять"</string>
<string name="rating_dialog_title">"Рейтинг"</string>
<string name="search_hint">"Поиск по названию, исполнителям или альбомам"</string>
<string name="search_info_minimum_characters">"Введите не менее трех символов"</string>
<string name="search_title_album">"Альбомы"</string>
<string name="search_title_artist">"Исполнители"</string>
<string name="search_title_song">"Треки"</string>
<string name="server_signup_dialog_action_low_security">"Низкая безопасность"</string>
<string name="server_signup_dialog_hint_name">"Имя сервера"</string>
<string name="server_signup_dialog_hint_password">"Пароль"</string>
<string name="server_signup_dialog_hint_url">"URL-адрес сервера"</string>
<string name="server_signup_dialog_hint_username">"Имя пользователя"</string>
<string name="server_signup_dialog_negative_button">"Отмена"</string>
<string name="server_signup_dialog_neutral_button">"Удалить"</string>
<string name="server_signup_dialog_positive_button">"Сохранить"</string>
<string name="server_signup_dialog_title">"Добавить сервер"</string>
<string name="server_unreachable_dialog_negative_button">"Отмена"</string>
<string name="server_unreachable_dialog_neutral_button">"Перейти к входу"</string>
<string name="server_unreachable_dialog_positive_button">"Продолжить в любом случае"</string>
<string name="server_unreachable_dialog_summary">"Запрошенный сервер недоступен. Если вы решите продолжить, это диалоговое окно не появится в течение следующего часа"</string>
<string name="server_unreachable_dialog_title">"Сервер недоступен"</string>
<string name="settings_about_summary">"Tempo — это легкий музыкальный клиент с открытым исходным кодом для Subsonic, разработанный и созданный специально для Android."</string>
<string name="settings_about_title">"О нас"</string>
<string name="settings_always_on_display">"Всегда на дисплее"</string>
<string name="settings_audio_transcode_download_format">"Формат перекодирования"</string>
<string name="settings_audio_transcode_download_priority_summary">"Если этот параметр включен, Tempo не будет принудительно загружать трек с настройками перекодирования, указанными ниже."</string>
<string name="settings_audio_transcode_download_priority_title">"Установите приоритет настроек сервера, используемых для потоковой передачи при загрузке"</string>
<string name="settings_audio_transcode_download_summary">"Если этот параметр включен, Tempo будет загружать перекодированные треки."</string>
<string name="settings_audio_transcode_download_title">"Скачать перекодированные треки"</string>
<string name="settings_audio_transcode_estimate_content_length_summary">"Если этот параметр включен, на сервере будет запрошена предполагаемая продолжительность трека."</string>
<string name="settings_audio_transcode_estimate_content_length_title">"Оцените длину содержимого"</string>
<string name="settings_audio_transcode_format_download">"Формат перекодирования для загрузки"</string>
<string name="settings_audio_transcode_format_mobile">"Формат перекодирования в мобильном телефоне"</string>
<string name="settings_audio_transcode_format_wifi">"Перекодировать формат в Wi-Fi"</string>
<string name="settings_audio_transcode_priority_summary">"Если этот параметр включен, Tempo не будет принудительно транслировать трек с настройками перекодирования, указанными ниже."</string>
<string name="settings_audio_transcode_priority_title">"Приоритизация настроек перекодирования сервера"</string>
<string name="settings_audio_transcode_priority_toast">"Приоритет при перекодировании трека отдается серверу"</string>
<string name="settings_buffering_strategy">"Стратегия буферизации"</string>
<string name="settings_buffering_strategy_summary">"Чтобы изменения вступили в силу, необходимо вручную перезапустить приложение."</string>
<string name="settings_covers_cache">"Размер кэша обложек"</string>
<string name="settings_data_saving_mode_summary">"Чтобы сократить потребление данных, избегайте загрузки обложек."</string>
<string name="settings_data_saving_mode_title">"Ограничить использование мобильных данных"</string>
<string name="settings_delete_download_storage_summary">"Продолжение приведет к необратимому удалению всех сохраненных элементов."</string>
<string name="settings_delete_download_storage_title">"Удалить сохраненные элементы"</string>
<string name="settings_download_storage_title">"Загрузить хранилище"</string>
<string name="settings_equalizer_summary">"Отрегулируйте настройки звука"</string>
<string name="settings_equalizer_title">"Эквалайзер"</string>
<string name="settings_github_link">"https://github.com/CappielloAntonio/tempo"</string>
<string name="settings_github_summary">"Следите за развитием"</string>
<string name="settings_github_title">"Github"</string>
<string name="settings_image_size">"Установить разрешение изображения"</string>
<string name="settings_language">"Язык"</string>
<string name="settings_logout_title">"Выйти"</string>
<string name="settings_max_bitrate_download">"Битрейт для скачиваний"</string>
<!-- Fuzzy -->
<string name="settings_max_bitrate_mobile">"Битрейт в мобильной сети 4G/5G"</string>
<string name="settings_max_bitrate_wifi">"Битрейт через соединение Wi-Fi"</string>
<string name="settings_media_cache">"Размер кэша медиафайлов"</string>
<string name="settings_music_directory">"Показать музыкальные каталоги"</string>
<string name="settings_music_directory_summary">"Если включено, то показывать раздел музыкального каталога. Обратите внимание: для правильной работы навигации по папкам сервер должен поддерживать эту функцию."</string>
<string name="settings_podcast">"Показать подкаст"</string>
<string name="settings_podcast_summary">"Если включено, показывать раздел подкаста. Перезапустите приложение, чтобы оно вступило в силу."</string>
<string name="settings_audio_quality">"Показать качество звука (битрейт)"</string>
<string name="settings_audio_quality_summary">"Битрейт и аудиоформат будут показаны для каждой аудиодорожки."</string>
<string name="settings_item_rating">"Показать рейтинг"</string>
<string name="settings_item_rating_summary">"Если эта функция включена, будет отображаться рейтинг элемента и то, отмечен ли он как избранный."</string>
<string name="settings_queue_syncing_countdown">"Таймер синхронизации"</string>
<string name="settings_queue_syncing_summary">"Если этот параметр включен, пользователь будет иметь возможность сохранять свою очередь воспроизведения и загружать состояние при открытии приложения."</string>
<string name="settings_queue_syncing_title">"Синхронизировать очередь воспроизведения для этого пользователя"</string>
<string name="settings_radio">оказать радио"</string>
<string name="settings_radio_summary">"Если включено, показывать раздел радио. Перезапустите приложение, чтобы оно вступило в силу."</string>
<string name="settings_replay_gain">"Установите режим усиления воспроизведения"</string>
<string name="settings_rounded_corner">"Закругленные углы"</string>
<string name="settings_rounded_corner_size">"Размер углов"</string>
<string name="settings_rounded_corner_size_summary">"Устанавливает величину угла кривизны."</string>
<string name="settings_rounded_corner_summary">"Если этот параметр включен, задает угол кривизны для всех отображаемых обложек. Изменения вступят в силу при перезапуске."</string>
<string name="settings_scan_title">"Сканировать библиотеку"</string>
<string name="settings_scrobble_title">"Включить скробблинг музыки Last.FM и т.д."</string>
<string name="settings_share_title">"Включить обмен музыкой"</string>
<string name="settings_sub_summary_scrobble">"Важно отметить, что скробблинг также зависит от того, настроен ли сервер для получения этих данных."</string>
<string name="settings_summary_skip_min_star_rating">"При прослушивании радио исполнителя, мгновенном миксе или перемешивании всех, треки ниже определенного пользовательского рейтинга будут игнорироваться."</string>
<string name="settings_summary_replay_gain">"Усиление воспроизведения — это функция, которая позволяет регулировать уровень громкости звуковых дорожек для обеспечения единообразного качества прослушивания. Этот параметр действует только в том случае, если трек содержит необходимые метаданные."</string>
<string name="settings_summary_scrobble">"Скробблинг — это функция, которая позволяет вашему устройству отправлять информацию о песнях, которые вы слушаете, на музыкальный сервер. Эта информация помогает создавать персональные рекомендации на основе ваших музыкальных предпочтений."</string>
<string name="settings_summary_share">"Позволяет пользователю делиться музыкой по ссылке. Функциональность должна поддерживаться и включаться на стороне сервера и ограничивается отдельными треками, альбомами и плейлистами."</string>
<string name="settings_summary_syncing">"Возвращает состояние очереди воспроизведения для этого пользователя. Сюда входят треки в очереди воспроизведения, воспроизводимый в данный момент трек и позиция внутри этого трека. Сервер должен поддерживать эту функцию."</string>
<string name="settings_summary_transcoding">"Приоритет отдается режиму перекодирования. Если установлено «Прямое воспроизведение», битрейт файла не изменится."</string>
<string name="settings_summary_transcoding_download">"Загрузите перекодированные медиафайлы. Если этот параметр включен, будет использоваться не конечная точка загрузки, а следующие настройки. Если для параметра «Формат перекодирования для загрузки» установлено значение «Прямая загрузка», битрейт файла не изменится."</string>
<string name="settings_summary_transcoding_estimate_content_length">"Когда файл перекодируется на лету, клиент обычно не показывает длину трека. Можно запросить у серверов, поддерживающих данную функцию, оценку длительности воспроизводимого трека, но время ответа может занять больше времени."</string>
<string name="settings_sync_starred_tracks_for_offline_use_summary">"Если этот параметр включен, помеченные треки будут загружены для использования в автономном режиме."</string>
<string name="settings_sync_starred_tracks_for_offline_use_title">"Синхронизируйте помеченные треки для использования в автономном режиме."</string>
<string name="settings_theme">"Тема"</string>
<string name="settings_title_data">"Данные"</string>
<string name="settings_title_general">"Общий"</string>
<string name="settings_title_rating">"Рейтинг"</string>
<string name="settings_title_replay_gain">"Усиление воспроизведения"</string>
<string name="settings_title_scrobble">"Скроббл"</string>
<string name="settings_title_skip_min_star_rating">"Игнорировать треки по рейтингу"</string>
<string name="settings_title_skip_min_star_rating_dialog">"Треки с рейтингом:"</string>
<string name="settings_title_share">"Поделиться"</string>
<string name="settings_title_syncing">"Синхронизации"</string>
<string name="settings_title_transcoding">"Транскодирование"</string>
<string name="settings_title_transcoding_download">"Транскодирование Скачать"</string>
<string name="settings_title_ui">"UI (Пользовательский интерфейс)"</string>
<string name="settings_transcoded_download">"Перекодированная загрузка"</string>
<string name="settings_version_title">"Версия"</string>
<string name="settings_wifi_only_summary">"Запросить подтверждение пользователя перед потоковой передачей по мобильной сети."</string>
<string name="settings_wifi_only_title">"Оповещение о потоковой передаче только через Wi-Fi"</string>
<string name="share_bottom_sheet_copy_link">"Копировать ссылку"</string>
<string name="share_bottom_sheet_delete">"Удалить общий доступ"</string>
<string name="share_bottom_sheet_update">"Обновить общий доступ"</string>
<string name="share_subtitle_item">"Срок действия: %1$s"</string>
<string name="share_unsupported_error">"Общий доступ не поддерживается или не включен"</string>
<string name="share_update_dialog_hint_description">"Описание"</string>
<string name="share_update_dialog_hint_expiration_date">"Дата окончания срока"</string>
<string name="share_update_dialog_negative_button">"Отмена"</string>
<!-- Fuzzy -->
<string name="share_update_dialog_positive_button">"Сохранять"</string>
<string name="share_update_dialog_title">"Поделиться"</string>
<string name="song_bottom_sheet_add_to_playlist">"Добавить в плейлист"</string>
<string name="song_bottom_sheet_add_to_queue">"Добавить в очередь"</string>
<string name="song_bottom_sheet_download">"Скачать"</string>
<string name="song_bottom_sheet_error_retrieving_album">"Ошибка при получении альбома"</string>
<string name="song_bottom_sheet_error_retrieving_artist">"Не удалось получить исполнителя"</string>
<string name="song_bottom_sheet_go_to_album">"Перейти в альбом"</string>
<string name="song_bottom_sheet_go_to_artist">"Перейти к исполнителю"</string>
<string name="song_bottom_sheet_instant_mix">"Мгновенный микс"</string>
<string name="song_bottom_sheet_play_next">"Играть дальше"</string>
<!-- Fuzzy -->
<string name="song_bottom_sheet_rate">"Оценить"</string>
<string name="song_bottom_sheet_remove">"Удалить"</string>
<string name="song_bottom_sheet_share">"Поделиться"</string>
<string name="song_list_page_downloaded">"Загружено"</string>
<string name="song_list_page_most_played">"Самые популярные треки"</string>
<string name="song_list_page_recently_added">"Недавно добавленные треки"</string>
<string name="song_list_page_recently_played">"Недавно воспроизведенные треки"</string>
<string name="song_list_page_starred">"Помеченные треки"</string>
<string name="song_list_page_top">"%1$s's Лучшие треки"</string>
<string name="song_list_page_year">"Год %1$d"</string>
<string name="song_subtitle_formatter">"%1$s • %2$s %3$s"</string>
<string name="starred_sync_dialog_negative_button">"Отмена"</string>
<string name="starred_sync_dialog_neutral_button">"Продолжить"</string>
<string name="starred_sync_dialog_positive_button">"Продолжить и скачать"</string>
<string name="starred_sync_dialog_summary">"Для скачивания рейтинговых треков может потребоваться большой объем данных."</string>
<string name="starred_sync_dialog_title">"Синхронизировать отмеченные треки"</string>
<string name="track_info_album">"Альбом"</string>
<string name="track_info_artist">"Исполнитель"</string>
<string name="track_info_bitrate">"Битрейт"</string>
<string name="track_info_content_type">"Тип содержимого"</string>
<string name="track_info_dialog_positive_button">"OK"</string>
<string name="track_info_dialog_title">"Информация о треке"</string>
<string name="track_info_disc_number">"Номер диска"</string>
<string name="track_info_duration">"Продолжительность"</string>
<string name="track_info_genre">"Жанр"</string>
<string name="track_info_path">"Путь"</string>
<string name="track_info_size">"Размер"</string>
<string name="track_info_suffix">"Суффикс"</string>
<string name="track_info_summary_downloaded_file">"Файл был загружен с использованием API Subsonic. Кодек и битрейт файла остаются неизменными по сравнению с исходным файлом."</string>
<string name="track_info_summary_full_transcode">"Приложение запросит сервер перекодировать файл и изменить его битрейт. Запрошенный пользователем кодек: %1$s, с битрейтом %2$s. Любые потенциальные изменения кодека и битрейта файла в выбранном формате будут обрабатываться сервером, который может поддерживать или не поддерживать эту операцию."</string>
<string name="track_info_summary_original_file">"Приложение будет читать только исходный файл, предоставленный сервером. Приложение явно запросит у сервера неперекодированный файл с битрейтом исходного источника."</string>
<string name="track_info_summary_server_prioritized">"Качество воспроизводимого файла остается на усмотрение сервера. Приложение не будет принудительно выбирать кодек и битрейт для любого потенциального перекодирования."</string>
<string name="track_info_summary_transcoding_bitrate">"Приложение запросит сервер изменить битрейт файла. Пользователь запросил битрейт %1$s, при этом кодек исходного файла останется прежним. Любые изменения битрейта файла в выбранном формате будут выполняться сервером, который может поддерживать или не поддерживать эту операцию."</string>
<string name="track_info_summary_transcoding_codec">"Приложение запросит сервер перекодировать файл. Запрошенный пользователем кодек %1$s, а битрейт будет такой же, как у исходного файла. Потенциальное перекодирование файла в выбранный формат зависит от сервера, поскольку он может поддерживать или не поддерживать эту операцию."</string>
<string name="track_info_title">"Заголовок"</string>
<string name="track_info_track_number">"Номер трека"</string>
<string name="track_info_transcoded_content_type">"Тип транскодированного контента"</string>
<string name="track_info_transcoded_suffix">"Транскодированный суффикс"</string>
<string name="track_info_year">"Год"</string>
<string name="undraw_page">"Развернуть"</string>
<string name="undraw_thanks">"Особая благодарность — команде unDraw, без иллюстраций которой мы не смогли бы сделать это приложение красивее."</string>
<string name="undraw_url">"https://undraw.co/"</string>
</resources>

View File

@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/md_theme_light_primary</item>
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>
<item name="colorPrimaryContainer">@color/md_theme_light_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/md_theme_light_onPrimaryContainer</item>
<item name="colorSecondary">@color/md_theme_light_secondary</item>
<item name="colorOnSecondary">@color/md_theme_light_onSecondary</item>
<item name="colorSecondaryContainer">@color/md_theme_light_secondaryContainer</item>
<item name="colorOnSecondaryContainer">@color/md_theme_light_onSecondaryContainer</item>
<item name="colorTertiary">@color/md_theme_light_tertiary</item>
<item name="colorOnTertiary">@color/md_theme_light_onTertiary</item>
<item name="colorTertiaryContainer">@color/md_theme_light_tertiaryContainer</item>
<item name="colorOnTertiaryContainer">@color/md_theme_light_onTertiaryContainer</item>
<item name="colorError">@color/md_theme_light_error</item>
<item name="colorErrorContainer">@color/md_theme_light_errorContainer</item>
<item name="colorOnError">@color/md_theme_light_onError</item>
<item name="colorOnErrorContainer">@color/md_theme_light_onErrorContainer</item>
<item name="android:colorBackground">@color/md_theme_light_background</item>
<item name="colorOnBackground">@color/md_theme_light_onBackground</item>
<item name="colorSurface">@color/md_theme_light_surface</item>
<item name="colorOnSurface">@color/md_theme_light_onSurface</item>
<item name="colorSurfaceVariant">@color/md_theme_light_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/md_theme_light_onSurfaceVariant</item>
<item name="colorOutline">@color/md_theme_light_outline</item>
<item name="colorOnSurfaceInverse">@color/md_theme_light_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/md_theme_light_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_light_primaryInverse</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:statusBarColor">?attr/colorSurface</item>
<item name="android:navigationBarColor">?attr/colorSurface</item>
<item name="android:scrollbars">none</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>

View File

@@ -24,7 +24,6 @@
<string name="album_page_extra_info_button">更多相似</string>
<string name="album_page_play_button">播放</string>
<string name="album_page_shuffle_button">随机播放</string>
<string name="album_page_tracks_count_and_duration">%1$d 首歌曲 • %2$d 分钟</string>
<string name="app_name">Tempo</string>
<string name="artist_adapter_radio_station_starting">正在搜索...</string>
<string name="artist_bottom_sheet_instant_mix">即时混合</string>
@@ -64,8 +63,8 @@
<string name="download_directory_dialog_title">下载曲目</string>
<string name="download_info_empty_subtitle">下载歌曲后,您可以在这里找到它。</string>
<string name="download_info_empty_title">还没有下载!</string>
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s 个项目</string>
<string name="download_item_single_subtitle_formatter">%1$s 个项目</string>
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s items</string>
<string name="download_item_single_subtitle_formatter">%1$s items</string>
<string name="download_storage_dialog_sub_summary">要使更改生效,请重新启动应用程序。</string>
<string name="download_storage_dialog_summary">更改已下载文件的目录将会立即删除以前已下载的所有文件。</string>
<string name="download_storage_dialog_title">选择存储选项</string>
@@ -107,7 +106,6 @@
<string name="home_title_most_played_see_all_button">查看全部</string>
<string name="home_title_new_releases">新发行</string>
<string name="home_title_newest_podcasts">最新播客</string>
<string name="home_title_pinned_playlists">播放列表</string>
<string name="home_title_podcast_channels">频道</string>
<string name="home_title_podcast_channels_see_all_button">查看全部</string>
<string name="home_title_radio_station">广播电台</string>
@@ -147,14 +145,12 @@
<string name="menu_group_by_track">曲目</string>
<string name="menu_group_by_year">年份</string>
<string name="menu_home_label">首页</string>
<string name="menu_last_year_name">去年</string>
<string name="menu_library_label">曲库</string>
<string name="menu_search_button">搜索</string>
<string name="menu_settings_button">设置</string>
<string name="menu_sort_artist">艺术家</string>
<string name="menu_sort_name">姓名</string>
<string name="menu_sort_random">随机</string>
<string name="menu_sort_recently_added">最近添加</string>
<string name="menu_sort_year">年份</string>
<string name="player_playback_speed">%1$.2fx</string>
<string name="player_queue_clean_all_button">清空队列</string>
@@ -165,7 +161,7 @@
<string name="playlist_chooser_dialog_negative_button">取消</string>
<string name="playlist_chooser_dialog_neutral_button">新建</string>
<string name="playlist_chooser_dialog_title">添加到播放列表</string>
<string name="playlist_counted_tracks">%1$d 首曲目 • %2$s</string>
<string name="playlist_counted_tracks">%1$d tracks • %2$s</string>
<string name="playlist_duration">持续时间 • %1$s</string>
<string name="playlist_editor_dialog_hint_name">播放列表名称</string>
<string name="playlist_editor_dialog_negative_button">取消</string>
@@ -174,7 +170,7 @@
<string name="playlist_editor_dialog_title">编辑播放列表</string>
<string name="playlist_page_play_button">播放</string>
<string name="playlist_page_shuffle_button">随机播放</string>
<string name="playlist_song_count">播放列表 • %1$d 首歌曲</string>
<string name="playlist_song_count">播放列表 • %1$d songs</string>
<string name="podcast_bottom_sheet_add_to_queue">添加到队列</string>
<string name="podcast_bottom_sheet_delete">删除</string>
<string name="podcast_bottom_sheet_download">下载</string>
@@ -227,7 +223,6 @@
<string name="server_unreachable_dialog_title">服务器无法访问</string>
<string name="settings_about_summary">Tempo 是 Subsonic 的开源轻量级音乐客户端,专为 Android 设计和构建。</string>
<string name="settings_about_title">关于</string>
<string name="settings_always_on_display">保持屏幕常亮</string>
<string name="settings_audio_transcode_download_format">转码格式</string>
<string name="settings_audio_transcode_download_priority_summary">如果启用Tempo 将不会强制使用下面的转码设置下载曲目。</string>
<string name="settings_audio_transcode_download_priority_title">优先考虑服务器上用于流式传输的设置</string>
@@ -243,8 +238,6 @@
<string name="settings_audio_transcode_priority_toast">曲目转码设置优先级设置为服务器</string>
<string name="settings_buffering_strategy">缓存策略</string>
<string name="settings_buffering_strategy_summary">为了使更改生效,您必须手动重新启动应用程序。</string>
<string name="settings_continuous_play_summary">允许在播放列表结束后,播放相似的曲目。</string>
<string name="settings_continuous_play_title">连续播放</string>
<string name="settings_covers_cache">图片缓存大小</string>
<string name="settings_data_saving_mode_summary">为了减少数据消耗,请避免下载封面。</string>
<string name="settings_data_saving_mode_title">限制移动数据使用</string>
@@ -267,10 +260,6 @@
<string name="settings_music_directory_summary">如果启用,则显示音乐目录部分。 请注意,要使文件夹导航正常工作,服务器必须支持此功能。</string>
<string name="settings_podcast">显示播客</string>
<string name="settings_podcast_summary">如果启用,则显示播客部分。</string>
<string name="settings_audio_quality">显示音频质量</string>
<string name="settings_audio_quality_summary">显示曲目的比特率和音频格式。</string>
<string name="settings_item_rating">显示评分</string>
<string name="settings_item_rating_summary">如果启用,则显示项目的评分和收藏状态。</string>
<string name="settings_queue_syncing_countdown">同步定时器</string>
<string name="settings_queue_syncing_summary">如果启用,将允许当前用户保存其播放队列,并能够在打开应用程序时加载保存状态。</string>
<string name="settings_queue_syncing_title">同步当前用户的播放队列</string>
@@ -284,14 +273,11 @@
<string name="settings_scan_title">扫描曲库</string>
<string name="settings_scrobble_title">启用音乐记录</string>
<string name="settings_share_title">启用音乐共享</string>
<string name="settings_streaming_cache_size">播放缓存大小</string>
<string name="settings_sub_summary_scrobble">请注意,音乐记录同时也依赖于服务器是否能够接收这些数据。</string>
<string name="settings_summary_skip_min_star_rating">收听电台,即时混合和随机播放时,低于特定评分的曲目将会被忽略。</string>
<string name="settings_summary_replay_gain">播放增益Replay gain允许您通过调整音轨的音量以获得始终如一的聆听体验。 仅当曲目标签包含必要的元数据时,此设置才有效。</string>
<string name="settings_summary_scrobble">音乐记录Scrobbling允许您的设备将您收听的歌曲的相关信息发送到音乐服务器。 这些信息有助于基于您的音乐偏好生成个性化推荐。</string>
<string name="settings_summary_share">允许用户通过链接共享音乐。 该功能需要服务器端支持并启用,并且仅限于单个曲目、专辑和队列。</string>
<string name="settings_summary_syncing">返回当前用户的播放队列状态。 这包括播放队列中的曲目、正在播放的曲目以及曲目播放进度。需要服务器支持此功能。</string>
<string name="settings_summary_streaming_cache_size">%1$s \n已使用: %2$s MiB</string>
<string name="settings_summary_transcoding">转码模式优先级设置。 如果设置为“播放原始”,文件的比特率将不会更改。</string>
<string name="settings_summary_transcoding_download">下载转码后的媒体。 如果启用,将不会下载原始数据,而是使用以下设置。\n如果“用于下载的转码格式”设置为“下载原始”则文件的比特率不会更改。</string>
<string name="settings_summary_transcoding_estimate_content_length">当文件即时转码时,客户端通常不会显示曲目长度。 可以向支持该功能的服务器发送请求,估计正在播放的曲目的持续时间,但可能响应变慢。</string>
@@ -300,10 +286,8 @@
<string name="settings_theme">主题</string>
<string name="settings_title_data">数据</string>
<string name="settings_title_general">通用</string>
<string name="settings_title_rating">评分</string>
<string name="settings_title_replay_gain">播放增益</string>
<string name="settings_title_scrobble">音乐记录</string>
<string name="settings_title_skip_min_star_rating">根据评分忽略歌曲</string>
<string name="settings_title_share">分享</string>
<string name="settings_title_syncing">同步</string>
<string name="settings_title_transcoding">转码</string>
@@ -372,28 +356,7 @@
<string name="track_info_transcoded_content_type">转码内容类型</string>
<string name="track_info_transcoded_suffix">转码后缀</string>
<string name="track_info_year">年份</string>
<string name="streaming_cache_storage_external_dialog_positive_button">外部</string>
<string name="streaming_cache_storage_internal_dialog_negative_button">内部</string>
<string name="undraw_page">unDraw</string>
<string name="undraw_thanks">特别感谢 unDraw没有它提供的插图我们的应用不可能会如此精美。</string>
<string name="undraw_url">https://undraw.co/</string>
<string name="album_page_release_date_label">发布于 %1$s</string>
<string name="disc_titlefull">第 %1$s 张光盘 - %2$s</string>
<string name="disc_titleless">第 %1$s 张光盘</string>
<string name="download_shuffle_all_subtitle">随机播放</string>
<string name="github_update_dialog_negative_button">稍后提醒</string>
<string name="github_update_dialog_positive_button">现在下载</string>
<string name="github_update_dialog_title">有可用更新</string>
<string name="home_rearrangement_dialog_negative_button">取消</string>
<string name="home_rearrangement_dialog_neutral_button">重置</string>
<string name="home_rearrangement_dialog_positive_button">保存</string>
<string name="home_title_last_month">上个月</string>
<string name="home_title_last_year">去年</string>
<string name="menu_last_week_name">上周</string>
<string name="menu_last_month_name">上个月</string>
<string name="menu_pin_button">添加到主屏幕</string>
<string name="menu_unpin_button">从主屏幕移除</string>
<string name="playlist_editor_dialog_action_delete_toast">长按删除</string>
<string name="server_signup_dialog_action_delete_toast">长按删除</string>
<string name="server_signup_dialog_hint_local_address">本地 URL</string>
</resources>

View File

@@ -90,8 +90,6 @@
<string name="filter_info_selection">Select two or more filters</string>
<string name="filter_title">Filter</string>
<string name="filter_title_expanded">Filter Genres</string>
<string name="generic_list_page_count">(%1$d)</string>
<string name="generic_list_page_count_unknown">(+%1$d)</string>
<string name="genre_catalogue_title">Genre Catalogue</string>
<string name="genre_catalogue_title_expanded">Browse Genres</string>
<string name="github_update_dialog_negative_button">Remind me later</string>
@@ -157,7 +155,6 @@
<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>
@@ -179,10 +176,6 @@
<string name="menu_sort_name">Name</string>
<string name="menu_sort_random">Random</string>
<string name="menu_sort_recently_added">Recently added</string>
<string name="menu_sort_recently_played">Recently played</string>
<string name="menu_sort_most_played">Most played</string>
<string name="menu_sort_most_recently_starred">Most recently starred</string>
<string name="menu_sort_least_recently_starred">Least recently starred</string>
<string name="menu_pin_button">Add to home screen</string>
<string name="menu_unpin_button">Remove from home screen</string>
<string name="menu_sort_year">Year</string>

View File

@@ -6,6 +6,4 @@
<locale android:name="zh-CN"/> <!-- Simplified Chinese-->
<locale android:name="ko-KR"/> <!-- Korean-->
<locale android:name="pt-BR"/> <!-- Brazilian Portuguese -->
<locale android:name="it-IT"/> <!-- Italian -->
<locale android:name="ru-RU"/> <!-- Russian -->
</locale-config>

View File

@@ -7,15 +7,9 @@
android:title="@string/menu_search_button"
app:showAsAction="always" />
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings"
android:title="@string/menu_settings_button"
app:showAsAction="ifRoom" />
app:showAsAction="never" />
</menu>

View File

@@ -1,497 +0,0 @@
package com.cappielloantonio.tempo.service
import android.net.Uri
import androidx.lifecycle.LifecycleOwner
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.SubtitleConfiguration
import androidx.media3.common.MediaMetadata
import androidx.media3.session.LibraryResult
import com.cappielloantonio.tempo.repository.AutomotiveRepository
import com.cappielloantonio.tempo.util.Preferences.getServerId
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
object MediaBrowserTree {
private lateinit var automotiveRepository: AutomotiveRepository
private var treeNodes: MutableMap<String, MediaItemNode> = mutableMapOf()
private var isInitialized = false
// Root
private const val ROOT_ID = "[rootID]"
// First level
private const val HOME_ID = "[homeID]"
private const val LIBRARY_ID = "[libraryID]"
private const val OTHER_ID = "[otherID]"
// Second level HOME_ID
private const val MOST_PLAYED_ID = "[mostPlayedID]"
private const val LAST_PLAYED_ID = "[lastPlayedID]"
private const val RECENTLY_ADDED_ID = "[recentlyAddedID]"
private const val RECENT_SONGS_ID = "[recentSongsID]"
private const val MADE_FOR_YOU_ID = "[madeForYouID]"
private const val STARRED_TRACKS_ID = "[starredTracksID]"
private const val STARRED_ALBUMS_ID = "[starredAlbumsID]"
private const val STARRED_ARTISTS_ID = "[starredArtistsID]"
private const val RANDOM_ID = "[randomID]"
// Second level LIBRARY_ID
private const val FOLDER_ID = "[folderID]"
private const val INDEX_ID = "[indexID]"
private const val DIRECTORY_ID = "[directoryID]"
private const val PLAYLIST_ID = "[playlistID]"
// Second level OTHER_ID
private const val PODCAST_ID = "[podcastID]"
private const val RADIO_ID = "[radioID]"
private const val ALBUM_ID = "[albumID]"
private const val ARTIST_ID = "[artistID]"
private class MediaItemNode(val item: MediaItem) {
private val children: MutableList<MediaItem> = ArrayList()
fun addChild(childID: String) {
this.children.add(treeNodes[childID]!!.item)
}
fun getChildren(): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
val listenableFuture = SettableFuture.create<LibraryResult<ImmutableList<MediaItem>>>()
val libraryResult = LibraryResult.ofItemList(children, null)
listenableFuture.set(libraryResult)
return listenableFuture
}
}
private fun buildMediaItem(
title: String,
mediaId: String,
isPlayable: Boolean,
isBrowsable: Boolean,
mediaType: @MediaMetadata.MediaType Int,
subtitleConfigurations: List<SubtitleConfiguration> = mutableListOf(),
album: String? = null,
artist: String? = null,
genre: String? = null,
sourceUri: Uri? = null,
imageUri: Uri? = null
): MediaItem {
val metadata =
MediaMetadata.Builder()
.setAlbumTitle(album)
.setTitle(title)
.setArtist(artist)
.setGenre(genre)
.setIsBrowsable(isBrowsable)
.setIsPlayable(isPlayable)
.setArtworkUri(imageUri)
.setMediaType(mediaType)
.build()
return MediaItem.Builder()
.setMediaId(mediaId)
.setSubtitleConfigurations(subtitleConfigurations)
.setMediaMetadata(metadata)
.setUri(sourceUri)
.build()
}
fun initialize(automotiveRepository: AutomotiveRepository) {
this.automotiveRepository = automotiveRepository
if (isInitialized) return
isInitialized = true
// Root level
treeNodes[ROOT_ID] =
MediaItemNode(
buildMediaItem(
title = "Root Folder",
mediaId = ROOT_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
// First level
treeNodes[HOME_ID] =
MediaItemNode(
buildMediaItem(
title = "Home",
mediaId = HOME_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
treeNodes[LIBRARY_ID] =
MediaItemNode(
buildMediaItem(
title = "Library",
mediaId = LIBRARY_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
treeNodes[OTHER_ID] =
MediaItemNode(
buildMediaItem(
title = "Other",
mediaId = OTHER_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
treeNodes[ROOT_ID]!!.addChild(HOME_ID)
treeNodes[ROOT_ID]!!.addChild(LIBRARY_ID)
treeNodes[ROOT_ID]!!.addChild(OTHER_ID)
// Second level HOME_ID
treeNodes[MOST_PLAYED_ID] =
MediaItemNode(
buildMediaItem(
title = "Most played",
mediaId = MOST_PLAYED_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS
)
)
treeNodes[LAST_PLAYED_ID] =
MediaItemNode(
buildMediaItem(
title = "Last played",
mediaId = LAST_PLAYED_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS
)
)
treeNodes[RECENTLY_ADDED_ID] =
MediaItemNode(
buildMediaItem(
title = "Recently added",
mediaId = RECENTLY_ADDED_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS
)
)
treeNodes[RECENT_SONGS_ID] =
MediaItemNode(
buildMediaItem(
title = "Recent songs",
mediaId = RECENT_SONGS_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
treeNodes[MADE_FOR_YOU_ID] =
MediaItemNode(
buildMediaItem(
title = "Made for you",
mediaId = MADE_FOR_YOU_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS
)
)
treeNodes[STARRED_TRACKS_ID] =
MediaItemNode(
buildMediaItem(
title = "Starred tracks",
mediaId = STARRED_TRACKS_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
treeNodes[STARRED_ALBUMS_ID] =
MediaItemNode(
buildMediaItem(
title = "Starred albums",
mediaId = STARRED_ALBUMS_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS
)
)
treeNodes[STARRED_ARTISTS_ID] =
MediaItemNode(
buildMediaItem(
title = "Starred artists",
mediaId = STARRED_ARTISTS_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS
)
)
treeNodes[RANDOM_ID] =
MediaItemNode(
buildMediaItem(
title = "Random",
mediaId = RANDOM_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
treeNodes[HOME_ID]!!.addChild(MOST_PLAYED_ID)
treeNodes[HOME_ID]!!.addChild(LAST_PLAYED_ID)
treeNodes[HOME_ID]!!.addChild(RECENTLY_ADDED_ID)
treeNodes[HOME_ID]!!.addChild(RECENT_SONGS_ID)
treeNodes[HOME_ID]!!.addChild(MADE_FOR_YOU_ID)
treeNodes[HOME_ID]!!.addChild(STARRED_TRACKS_ID)
treeNodes[HOME_ID]!!.addChild(STARRED_ALBUMS_ID)
treeNodes[HOME_ID]!!.addChild(STARRED_ARTISTS_ID)
treeNodes[HOME_ID]!!.addChild(RANDOM_ID)
// Second level LIBRARY_ID
treeNodes[FOLDER_ID] =
MediaItemNode(
buildMediaItem(
title = "Folders",
mediaId = FOLDER_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
treeNodes[PLAYLIST_ID] =
MediaItemNode(
buildMediaItem(
title = "Playlists",
mediaId = PLAYLIST_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS
)
)
treeNodes[LIBRARY_ID]!!.addChild(FOLDER_ID)
treeNodes[LIBRARY_ID]!!.addChild(PLAYLIST_ID)
// Second level OTHER_ID
treeNodes[PODCAST_ID] =
MediaItemNode(
buildMediaItem(
title = "Podcasts",
mediaId = PODCAST_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS
)
)
treeNodes[RADIO_ID] =
MediaItemNode(
buildMediaItem(
title = "Radio stations",
mediaId = RADIO_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_RADIO_STATIONS
)
)
treeNodes[OTHER_ID]!!.addChild(PODCAST_ID)
treeNodes[OTHER_ID]!!.addChild(RADIO_ID)
}
fun getRootItem(): MediaItem {
return treeNodes[ROOT_ID]!!.item
}
fun getChildren(
id: String
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
return when (id) {
ROOT_ID -> treeNodes[ROOT_ID]?.getChildren()!!
HOME_ID -> treeNodes[HOME_ID]?.getChildren()!!
LIBRARY_ID -> treeNodes[LIBRARY_ID]?.getChildren()!!
OTHER_ID -> treeNodes[OTHER_ID]?.getChildren()!!
MOST_PLAYED_ID -> automotiveRepository.getAlbums(id, "frequent", 100)
LAST_PLAYED_ID -> automotiveRepository.getAlbums(id, "recent", 100)
RECENTLY_ADDED_ID -> automotiveRepository.getAlbums(id, "newest", 100)
RECENT_SONGS_ID -> automotiveRepository.getRecentlyPlayedSongs(getServerId(),100)
MADE_FOR_YOU_ID -> automotiveRepository.getStarredArtists(id)
STARRED_TRACKS_ID -> automotiveRepository.starredSongs
STARRED_ALBUMS_ID -> automotiveRepository.getStarredAlbums(id)
STARRED_ARTISTS_ID -> automotiveRepository.getStarredArtists(id)
RANDOM_ID -> automotiveRepository.getRandomSongs(100)
FOLDER_ID -> automotiveRepository.getMusicFolders(id)
PLAYLIST_ID -> automotiveRepository.getPlaylists(id)
PODCAST_ID -> automotiveRepository.getNewestPodcastEpisodes(100)
RADIO_ID -> automotiveRepository.internetRadioStations
else -> {
if (id.startsWith(MOST_PLAYED_ID)) {
return automotiveRepository.getAlbumTracks(
id.removePrefix(
MOST_PLAYED_ID
)
)
}
if (id.startsWith(LAST_PLAYED_ID)) {
return automotiveRepository.getAlbumTracks(
id.removePrefix(
LAST_PLAYED_ID
)
)
}
if (id.startsWith(RECENTLY_ADDED_ID)) {
return automotiveRepository.getAlbumTracks(
id.removePrefix(
RECENTLY_ADDED_ID
)
)
}
if (id.startsWith(MADE_FOR_YOU_ID)) {
return automotiveRepository.getMadeForYou(
id.removePrefix(
MADE_FOR_YOU_ID
),
20
)
}
if (id.startsWith(STARRED_ALBUMS_ID)) {
return automotiveRepository.getAlbumTracks(
id.removePrefix(
STARRED_ALBUMS_ID
)
)
}
if (id.startsWith(STARRED_ARTISTS_ID)) {
return automotiveRepository.getArtistAlbum(
STARRED_ALBUMS_ID,
id.removePrefix(
STARRED_ARTISTS_ID
)
)
}
if (id.startsWith(FOLDER_ID)) {
return automotiveRepository.getIndexes(
INDEX_ID,
id.removePrefix(
FOLDER_ID
)
)
}
if (id.startsWith(INDEX_ID)) {
return automotiveRepository.getDirectories(
DIRECTORY_ID,
id.removePrefix(
INDEX_ID
)
)
}
if (id.startsWith(DIRECTORY_ID)) {
return automotiveRepository.getDirectories(
DIRECTORY_ID,
id.removePrefix(
DIRECTORY_ID
)
)
}
if (id.startsWith(PLAYLIST_ID)) {
return automotiveRepository.getPlaylistSongs(
id.removePrefix(
PLAYLIST_ID
)
)
}
if (id.startsWith(ALBUM_ID)) {
return automotiveRepository.getAlbumTracks(
id.removePrefix(
ALBUM_ID
)
)
}
if (id.startsWith(ARTIST_ID)) {
return automotiveRepository.getArtistAlbum(
ALBUM_ID,
id.removePrefix(
ARTIST_ID
)
)
}
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE))
}
}
}
// https://github.com/androidx/media/issues/156
fun getItems(mediaItems: List<MediaItem>): List<MediaItem> {
val updatedMediaItems = ArrayList<MediaItem>()
mediaItems.forEach {
if (it.localConfiguration?.uri != null) {
updatedMediaItems.add(it)
} else {
val sessionMediaItem = automotiveRepository.getSessionMediaItem(it.mediaId)
if (sessionMediaItem != null) {
var toAdd = automotiveRepository.getMetadatas(sessionMediaItem.timestamp!!)
val index = toAdd.indexOfFirst { mediaItem -> mediaItem.mediaId == it.mediaId }
toAdd = toAdd.subList(index, toAdd.size)
updatedMediaItems.addAll(toAdd)
}
}
}
return updatedMediaItems
}
fun search(query: String): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
return automotiveRepository.search(
query,
ALBUM_ID,
ARTIST_ID
)
}
}

View File

@@ -1,162 +0,0 @@
package com.cappielloantonio.tempo.service
import android.content.Context
import android.os.Bundle
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton
import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionResult
import com.cappielloantonio.tempo.R
import com.cappielloantonio.tempo.repository.AutomotiveRepository
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
open class MediaLibrarySessionCallback(
context: Context,
automotiveRepository: AutomotiveRepository
) :
MediaLibraryService.MediaLibrarySession.Callback {
init {
MediaBrowserTree.initialize(automotiveRepository)
}
private val customLayoutCommandButtons: List<CommandButton> = listOf(
CommandButton.Builder()
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
.setSessionCommand(
SessionCommand(
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY
)
).setIconResId(R.drawable.exo_icon_shuffle_off).build(),
CommandButton.Builder()
.setDisplayName(context.getString(R.string.exo_controls_shuffle_off_description))
.setSessionCommand(
SessionCommand(
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY
)
).setIconResId(R.drawable.exo_icon_shuffle_on).build()
)
@OptIn(UnstableApi::class)
val mediaNotificationSessionCommands =
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
.also { builder ->
customLayoutCommandButtons.forEach { commandButton ->
commandButton.sessionCommand?.let { builder.add(it) }
}
}.build()
@OptIn(UnstableApi::class)
override fun onConnect(
session: MediaSession, controller: MediaSession.ControllerInfo
): MediaSession.ConnectionResult {
if (session.isMediaNotificationController(controller) || session.isAutomotiveController(
controller
) || session.isAutoCompanionController(controller)
) {
val customLayout =
customLayoutCommandButtons[if (session.player.shuffleModeEnabled) 1 else 0]
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
.setAvailableSessionCommands(mediaNotificationSessionCommands)
.setCustomLayout(ImmutableList.of(customLayout)).build()
}
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
}
@OptIn(UnstableApi::class)
override fun onCustomCommand(
session: MediaSession,
controller: MediaSession.ControllerInfo,
customCommand: SessionCommand,
args: Bundle
): ListenableFuture<SessionResult> {
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
session.player.shuffleModeEnabled = true
session.setCustomLayout(
session.mediaNotificationControllerInfo!!,
ImmutableList.of(customLayoutCommandButtons[1])
)
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
session.player.shuffleModeEnabled = false
session.setCustomLayout(
session.mediaNotificationControllerInfo!!,
ImmutableList.of(customLayoutCommandButtons[0])
)
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
}
override fun onGetLibraryRoot(
session: MediaLibraryService.MediaLibrarySession,
browser: MediaSession.ControllerInfo,
params: MediaLibraryService.LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> {
return Futures.immediateFuture(LibraryResult.ofItem(MediaBrowserTree.getRootItem(), params))
}
override fun onGetChildren(
session: MediaLibraryService.MediaLibrarySession,
browser: MediaSession.ControllerInfo,
parentId: String,
page: Int,
pageSize: Int,
params: MediaLibraryService.LibraryParams?
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
return MediaBrowserTree.getChildren(parentId)
}
override fun onAddMediaItems(
mediaSession: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItems: List<MediaItem>
): ListenableFuture<List<MediaItem>> {
return super.onAddMediaItems(
mediaSession,
controller,
MediaBrowserTree.getItems(mediaItems)
)
}
override fun onSearch(
session: MediaLibraryService.MediaLibrarySession,
browser: MediaSession.ControllerInfo,
query: String,
params: MediaLibraryService.LibraryParams?
): ListenableFuture<LibraryResult<Void>> {
session.notifySearchResultChanged(browser, query, 60, params)
return Futures.immediateFuture(LibraryResult.ofVoid())
}
override fun onGetSearchResult(
session: MediaLibraryService.MediaLibrarySession,
browser: MediaSession.ControllerInfo,
query: String,
page: Int,
pageSize: Int,
params: MediaLibraryService.LibraryParams?
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
return MediaBrowserTree.search(query)
}
companion object {
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
"android.media3.session.demo.SHUFFLE_ON"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
"android.media3.session.demo.SHUFFLE_OFF"
}
}

View File

@@ -1,210 +0,0 @@
package com.cappielloantonio.tempo.service
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.TaskStackBuilder
import android.content.Intent
import androidx.media3.cast.CastPlayer
import androidx.media3.cast.SessionAvailabilityListener
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession.ControllerInfo
import com.cappielloantonio.tempo.repository.AutomotiveRepository
import com.cappielloantonio.tempo.ui.activity.MainActivity
import com.cappielloantonio.tempo.util.Constants
import com.cappielloantonio.tempo.util.DownloadUtil
import com.cappielloantonio.tempo.util.Preferences
import com.cappielloantonio.tempo.util.ReplayGainUtil
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
@UnstableApi
class MediaService : MediaLibraryService(), SessionAvailabilityListener {
private lateinit var automotiveRepository: AutomotiveRepository
private lateinit var player: ExoPlayer
private lateinit var castPlayer: CastPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
override fun onCreate() {
super.onCreate()
initializeRepository()
initializePlayer()
initializeCastPlayer()
initializeMediaLibrarySession()
initializePlayerListener()
setPlayer(
null,
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable) castPlayer else player
)
}
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
return mediaLibrarySession
}
override fun onTaskRemoved(rootIntent: Intent?) {
val player = mediaLibrarySession.player
if (!player.playWhenReady || player.mediaItemCount == 0) {
stopSelf()
}
}
override fun onDestroy() {
releasePlayer()
super.onDestroy()
}
private fun initializeRepository() {
automotiveRepository = AutomotiveRepository()
}
private fun initializePlayer() {
player = ExoPlayer.Builder(this)
.setRenderersFactory(getRenderersFactory())
.setMediaSourceFactory(getMediaSourceFactory())
.setAudioAttributes(AudioAttributes.DEFAULT, true)
.setHandleAudioBecomingNoisy(true)
.setWakeMode(C.WAKE_MODE_NETWORK)
.setLoadControl(initializeLoadControl())
.build()
}
private fun initializeCastPlayer() {
if (GoogleApiAvailability.getInstance()
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
) {
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
castPlayer.setSessionAvailabilityListener(this)
}
}
private fun initializeMediaLibrarySession() {
val sessionActivityPendingIntent =
TaskStackBuilder.create(this).run {
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
}
mediaLibrarySession =
MediaLibrarySession.Builder(this, player, createLibrarySessionCallback())
.setSessionActivity(sessionActivityPendingIntent)
.build()
}
private fun createLibrarySessionCallback(): MediaLibrarySession.Callback {
return MediaLibrarySessionCallback(this, automotiveRepository)
}
private fun initializePlayerListener() {
player.addListener(object : Player.Listener {
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
if (mediaItem == null) return
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
MediaManager.setLastPlayedTimestamp(mediaItem)
}
}
override fun onTracksChanged(tracks: Tracks) {
ReplayGainUtil.setReplayGain(player, tracks)
MediaManager.scrobble(player.currentMediaItem, false)
if (player.currentMediaItemIndex + 1 == player.mediaItemCount)
MediaManager.continuousPlay(player.currentMediaItem)
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
if (!isPlaying) {
MediaManager.setPlayingPausedTimestamp(
player.currentMediaItem,
player.currentPosition
)
} else {
MediaManager.scrobble(player.currentMediaItem, false)
}
}
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
if (!player.hasNextMediaItem() &&
playbackState == Player.STATE_ENDED &&
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
) {
MediaManager.scrobble(player.currentMediaItem, true)
MediaManager.saveChronology(player.currentMediaItem)
}
}
override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
MediaManager.scrobble(oldPosition.mediaItem, true)
MediaManager.saveChronology(oldPosition.mediaItem)
}
if (newPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
MediaManager.setLastPlayedTimestamp(newPosition.mediaItem)
}
}
}
})
}
private fun initializeLoadControl(): DefaultLoadControl {
return DefaultLoadControl.Builder()
.setBufferDurationsMs(
(DefaultLoadControl.DEFAULT_MIN_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
(DefaultLoadControl.DEFAULT_MAX_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
)
.build()
}
private fun setPlayer(oldPlayer: Player?, newPlayer: Player) {
if (oldPlayer === newPlayer) return
oldPlayer?.stop()
mediaLibrarySession.player = newPlayer
}
private fun releasePlayer() {
if (this::castPlayer.isInitialized) castPlayer.setSessionAvailabilityListener(null)
if (this::castPlayer.isInitialized) castPlayer.release()
player.release()
mediaLibrarySession.release()
automotiveRepository.deleteMetadata()
clearListener()
}
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
private fun getMediaSourceFactory() =
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
override fun onCastSessionAvailable() {
setPlayer(player, castPlayer)
}
override fun onCastSessionUnavailable() {
setPlayer(castPlayer, player)
}
}

View File

@@ -1,67 +0,0 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentToolbarBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.google.android.gms.cast.framework.CastButtonFactory;
@UnstableApi
public class ToolbarFragment extends Fragment {
private static final String TAG = "ToolbarFragment";
private FragmentToolbarBinding bind;
private MainActivity activity;
public ToolbarFragment() {
// Required empty public constructor
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentToolbarBinding.inflate(inflater, container, false);
View view = bind.getRoot();
return view;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.settingsFragment);
return true;
}
return false;
}
}

View File

@@ -1,14 +0,0 @@
package com.cappielloantonio.tempo.util;
import android.content.Context;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
public class Flavors {
public static void initializeCastContext(Context context) {
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS)
CastContext.getSharedInstance(context);
}
}

View File

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

View File

@@ -17,5 +17,6 @@ org.gradle.jvmargs=-Xmx2048m
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=false
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View File

@@ -1,6 +1,6 @@
#Wed Nov 06 17:17:57 CET 2024
#Wed Aug 16 22:52:26 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

File diff suppressed because one or more lines are too long