61 Commits

Author SHA1 Message Date
Robin Slot
d4d332ec3c Fix crash when sorting albums with a null artist 2025-12-07 21:24:09 +01:00
f8968766bf Merge pull request 'add-to-playlist-check-duplicate' (#1) from add-to-playlist-check-duplicate into main
Reviewed-on: #1
2025-12-07 20:04:00 +00:00
92ebba1d44 fix: isSonginPlayList condition 2025-12-07 20:54:46 +01:00
Jonas Resch
e2f82ccd12 Revert code format change 2025-12-07 20:46:39 +01:00
Jonas Resch
8d24659bd7 Add to playlist: check if song already exists in playlist and display a dialog if it does 2025-12-07 20:46:39 +01:00
Jonas Resch
2dd84e7c56 Fix: App crashes on second open when local address empty 2025-12-07 20:46:39 +01:00
5aa82bec70 fix: add compileSdk again 2025-12-07 20:38:19 +01:00
d293e3bcfe feat: dep updates 2025-12-07 19:34:07 +01:00
CappielloAntonio
9cf62c8c0c gradle: update gradle build tools 2025-01-31 11:05:39 +01:00
CappielloAntonio
330556ec33 Merge remote-tracking branch 'origin/main' 2025-01-31 10:32:53 +01:00
CappielloAntonio
4c8c5ce120 gradle: dependencies update 2025-01-31 10:32:39 +01:00
CappielloAntonio
55ae9a8442 fix: added missing decorators 2025-01-31 10:32:00 +01:00
CappielloAntonio
f8a53c7db2 Update README.md 2024-12-31 16:55:21 +01:00
CappielloAntonio
b58cae1ecd Update README.md 2024-12-31 16:51:37 +01:00
CappielloAntonio
e305f20811 fix: updated workflow 2024-12-31 12:54:28 +01:00
CappielloAntonio
c8c1bcfd3e fix: null checking 2024-12-30 17:23:55 +01:00
CappielloAntonio
e1c96d278f fix: null checking 2024-12-30 17:20:13 +01:00
CappielloAntonio
3783a2f790 fix: escape character 2024-12-30 17:17:05 +01:00
CappielloAntonio
63e288d147 Merge pull request #316 from potatoenergy/main
feature: update ru-RU translation
2024-12-30 16:53:40 +01:00
CappielloAntonio
d3ca43ed49 fix: delete deprecated code to check connectivity 2024-12-30 16:45:38 +01:00
CappielloAntonio
f0d31e425a fix: temporary opt out edge to edge enforcement 2024-12-30 16:45:07 +01:00
CappielloAntonio
609fc70d33 fix: temporary opt out edge to edge enforcement 2024-12-30 16:38:43 +01:00
CappielloAntonio
0b92c40d51 gradle: dependencies update 2024-12-30 16:37:53 +01:00
ponfertato
eb6a2b609e feat: update Russian localization 2024-12-25 18:07:18 +03:00
ponfertato
20ffc960df feat: fix build Russian localization 2024-12-25 17:56:34 +03:00
CappielloAntonio
48d9022f9a feat: add sorting and search functionality for album and artist list 2024-11-23 16:00:01 +01:00
CappielloAntonio
9e6926fc97 feat: add sorting and search functionality for song list 2024-11-22 21:57:27 +01:00
CappielloAntonio
780f1c3a2e feat: implemented additional sorting options for albums in the catalog screen 2024-11-20 23:02:43 +01:00
CappielloAntonio
0f471a7b9f feat: add search functionality for songs within playlists 2024-11-20 22:20:37 +01:00
CappielloAntonio
4ec1519063 feat: add ALAC codec support via Media3 FFmpeg module 2024-11-20 21:23:56 +01:00
CappielloAntonio
618cf23e6e gradle: dependencies update 2024-11-20 15:10:25 +01:00
CappielloAntonio
e8e24354ec fix: change the server address to the backup one if the first address is unreachable 2024-11-06 17:43:18 +01:00
CappielloAntonio
fd0fd0546c gradle: gradle update 2024-11-06 17:39:42 +01:00
CappielloAntonio
030ca82c3a fix: fixed the pull request for the Italian translation 2024-11-06 17:17:17 +01:00
CappielloAntonio
0e6b860e03 Merge remote-tracking branch 'origin/main' 2024-11-06 16:57:57 +01:00
CappielloAntonio
8c288e8938 Merge pull request #299 from SaregoA/main
feat: Added Italian translation
2024-11-06 16:57:17 +01:00
CappielloAntonio
4e968f3397 Merge pull request #286 from skyline75489/skyline/zh-cn-2
feat: update zh-cn translation
2024-11-06 16:55:46 +01:00
CappielloAntonio
b1e0f49ddb gradle: dependencies update 2024-11-06 15:43:49 +01:00
andrea sarego
73a1ab2330 Traduzione in italiano 2024-11-04 20:31:40 +01:00
Chester Liu
8540348670 Update zh-cn translation part 2 2024-08-31 19:07:40 +08:00
CappielloAntonio
67e4079732 fix: null checking 2024-08-30 17:19:58 +02:00
CappielloAntonio
6c6d56f451 Merge pull request #251 from albertcanales/main
feat: open album when clicking the song's title on player
2024-08-30 16:55:00 +02:00
CappielloAntonio
f967df8ef3 Merge remote-tracking branch 'origin/main' 2024-08-30 16:47:37 +02:00
CappielloAntonio
c5a78bf945 fix: strengthened controls on the presence of server address strings (local and remote) 2024-08-30 16:47:15 +02:00
CappielloAntonio
6f51fd92bb style: code cleanup 2024-08-30 15:51:49 +02:00
CappielloAntonio
3c2ea38f1e fix: further eliminate the use of HTML decode in titles and subtitles 2024-08-30 15:27:16 +02:00
CappielloAntonio
edf140fb5d Merge pull request #285 from skyline75489/skyline/zh-cn-1
Update zh-cn translation
2024-08-30 15:16:31 +02:00
Chester Liu
049ce7713a Update zh-cn translation 2024-08-29 20:27:31 +08:00
CappielloAntonio
8c49ceffdb Update privacy.html 2024-08-29 12:07:59 +02:00
CappielloAntonio
052e9d9068 Merge remote-tracking branch 'origin/main' 2024-08-28 14:25:31 +02:00
CappielloAntonio
4d1213c43d gradle: dependencies update 2024-08-28 14:25:19 +02:00
CappielloAntonio
1ec0c7b99c Merge pull request #264 from dnno/main
feat: update german localization
2024-08-22 16:58:13 +02:00
CappielloAntonio
07f8914a9f Merge pull request #261 from florent4014/patch-1
feat: update strings.xml
2024-08-22 16:57:35 +02:00
CappielloAntonio
965a80462c Merge remote-tracking branch 'origin/main' 2024-08-22 16:12:03 +02:00
CappielloAntonio
349c961f1a repo: add play variant 2024-08-22 16:11:56 +02:00
CappielloAntonio
eb9f824c01 gradle: gradle update 2024-08-22 15:20:51 +02:00
CappielloAntonio
e465892013 repo: add privacy policy page 2024-08-21 14:44:38 +02:00
CappielloAntonio
a49c78b9f1 gradle: dependencies update 2024-08-21 14:42:46 +02:00
Ryan Harg
436ef21a29 Update german localization 2024-07-03 10:12:38 +02:00
florent4014
be8decfac3 Update strings.xml
Corrected some minor mistakes
2024-06-24 13:54:29 +02:00
Albert Canales Ros
7c87ec2cbe feat: clicking the song's title opens the album on player 2024-06-08 23:08:39 +02:00
74 changed files with 3154 additions and 501 deletions

View File

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

2
.idea/compiler.xml generated
View File

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

2
.idea/gradle.xml generated
View File

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

4
.idea/misc.xml generated
View File

@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DesignSurface"> <component name="DesignSurface">
<option name="filePathToZoomLevelMap"> <option name="filePathToZoomLevelMap">
@@ -191,7 +192,8 @@
</map> </map>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -8,6 +8,8 @@
<p align="center"> <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> <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://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> <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> </p>
@@ -18,6 +20,8 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
**If you find Tempo useful, please consider starring the project on GitHub. It would mean a lot to me and help promote the app to a wider audience.** **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 ## Features
- **Subsonic Integration**: Tempo seamlessly integrates with your Subsonic server, providing you with easy access to your entire music collection on the go. - **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. - **Sleek and Intuitive UI**: Enjoy a clean and user-friendly interface designed to enhance your music listening experience, tailored to your preferences and listening history.
@@ -29,6 +33,7 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
- **Scrobbling Integration**: Optionally integrate Tempo with Last.fm to scrobble your played tracks, gather music insights, and further personalize your music recommendations, if supported by your Subsonic server. - **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. - **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. - **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"> <p align="center">
<img src="mockup/feat/1_screenshot.png" width=200> <img src="mockup/feat/1_screenshot.png" width=200>

View File

@@ -3,15 +3,16 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-parcelize'
android { android {
compileSdk = 34 buildToolsVersion = '35.0.0'
buildToolsVersion = '34.0.0'
compileSdk 35
defaultConfig { defaultConfig {
minSdkVersion 24 minSdkVersion 24
targetSdkVersion 34 targetSdk 35
versionCode 25 versionCode 26
versionName '3.8.1' versionName '3.9.0'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -37,6 +38,11 @@ android {
dimension = "default" dimension = "default"
applicationId "com.cappielloantonio.notquitemy.tempo" applicationId "com.cappielloantonio.notquitemy.tempo"
} }
play {
dimension = "default"
applicationId "com.cappielloantonio.play.tempo"
}
} }
buildTypes { buildTypes {
@@ -59,43 +65,47 @@ android {
buildFeatures { buildFeatures {
viewBinding true viewBinding true
buildConfig true
} }
namespace 'com.cappielloantonio.tempo' namespace 'com.cappielloantonio.tempo'
} }
dependencies { dependencies {
implementation files('../libs/lib-decoder-ffmpeg-release.aar')
// AndroidX // AndroidX
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0'
implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7' implementation 'androidx.navigation:navigation-fragment-ktx:2.9.6'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7' implementation 'androidx.navigation:navigation-ui-ktx:2.9.6'
implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.recyclerview:recyclerview:1.4.0'
implementation 'androidx.room:room-runtime:2.6.1' implementation 'androidx.room:room-runtime:2.8.4'
implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.core:core-splashscreen:1.2.0'
implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.appcompat:appcompat:1.7.1'
// Android Material // Android Material
implementation 'com.google.android.material:material:1.10.0' implementation 'com.google.android.material:material:1.13.0'
// Glide // Glide
implementation 'com.github.bumptech.glide:glide:4.16.0' implementation 'com.github.bumptech.glide:glide:5.0.5'
implementation 'com.github.bumptech.glide:annotations:4.16.0' implementation 'com.github.bumptech.glide:annotations:5.0.5'
// Media3 // Media3
implementation 'androidx.media3:media3-session:1.3.1' implementation 'androidx.media3:media3-session:1.8.0'
implementation 'androidx.media3:media3-common:1.3.1' implementation 'androidx.media3:media3-common:1.8.0'
implementation 'androidx.media3:media3-exoplayer:1.3.1' implementation 'androidx.media3:media3-exoplayer:1.8.0'
implementation 'androidx.media3:media3-ui:1.3.1' implementation 'androidx.media3:media3-ui:1.8.0'
implementation 'androidx.media3:media3-exoplayer-hls:1.3.1' implementation 'androidx.media3:media3-exoplayer-hls:1.8.0'
tempoImplementation 'androidx.media3:media3-cast:1.3.1' tempoImplementation 'androidx.media3:media3-cast:1.8.0'
playImplementation 'androidx.media3:media3-cast:1.8.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' annotationProcessor 'com.github.bumptech.glide:compiler:5.0.5'
annotationProcessor 'androidx.room:room-compiler:2.6.1' annotationProcessor 'androidx.room:room-compiler:2.8.4'
// Retrofit // Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.11.0' implementation 'com.squareup.retrofit2:retrofit:3.0.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14' implementation 'com.squareup.okhttp3:logging-interceptor:5.3.2'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0' implementation 'com.squareup.retrofit2:converter-gson:3.0.0'
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,8 @@ package com.cappielloantonio.tempo.subsonic.utils;
import android.content.Context; import android.content.Context;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.Network;
import android.net.NetworkCapabilities;
import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.App;
@@ -39,7 +40,19 @@ public class CacheUtil {
private boolean isConnected() { private boolean isConnected() {
ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
return (netInfo != null && netInfo.isConnected()); 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;
} }
} }

View File

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

View File

@@ -3,6 +3,8 @@ package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@@ -11,22 +13,60 @@ import com.cappielloantonio.tempo.databinding.ItemHorizontalAlbumBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest; import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3; 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.Constants;
import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontalAdapter.ViewHolder> { public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontalAdapter.ViewHolder> implements Filterable {
private final ClickCallback click; private final ClickCallback click;
private final boolean isOffline; private final boolean isOffline;
private List<AlbumID3> albumsFull;
private List<AlbumID3> albums; 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) { public AlbumHorizontalAdapter(ClickCallback click, boolean isOffline) {
this.click = click; this.click = click;
this.isOffline = isOffline; this.isOffline = isOffline;
this.albums = Collections.emptyList(); this.albums = Collections.emptyList();
this.albumsFull = Collections.emptyList();
this.currentFilter = "";
} }
@NonNull @NonNull
@@ -55,10 +95,16 @@ public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontal
} }
public void setItems(List<AlbumID3> albums) { public void setItems(List<AlbumID3> albums) {
this.albums = albums; this.albumsFull = albums != null ? albums : Collections.emptyList();
filtering.filter(currentFilter);
notifyDataSetChanged(); notifyDataSetChanged();
} }
@Override
public Filter getFilter() {
return filtering;
}
public AlbumID3 getItem(int id) { public AlbumID3 getItem(int id) {
return albums.get(id); return albums.get(id);
} }
@@ -95,4 +141,21 @@ public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontal
return true; 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 @Override
protected void publishResults(CharSequence constraint, FilterResults results) { protected void publishResults(CharSequence constraint, FilterResults results) {
artists.clear(); artists.clear();
artists.addAll((List) results.values); if (results.count > 0) artists.addAll((List) results.values);
notifyDataSetChanged(); notifyDataSetChanged();
} }
}; };

View File

@@ -4,6 +4,8 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@@ -11,21 +13,59 @@ import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHorizontalArtistBinding; import com.cappielloantonio.tempo.databinding.ItemHorizontalArtistBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest; import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3; import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizontalAdapter.ViewHolder> { public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizontalAdapter.ViewHolder> implements Filterable {
private final ClickCallback click; private final ClickCallback click;
private List<ArtistID3> artistsFull;
private List<ArtistID3> artists; 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) { public ArtistHorizontalAdapter(ClickCallback click) {
this.click = click; this.click = click;
this.artists = Collections.emptyList(); this.artists = Collections.emptyList();
this.artistsFull = Collections.emptyList();
this.currentFilter = "";
} }
@NonNull @NonNull
@@ -59,10 +99,16 @@ public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizont
} }
public void setItems(List<ArtistID3> artists) { public void setItems(List<ArtistID3> artists) {
this.artists = artists; this.artistsFull = artists != null ? artists : Collections.emptyList();
filtering.filter(currentFilter);
notifyDataSetChanged(); notifyDataSetChanged();
} }
@Override
public Filter getFilter() {
return filtering;
}
public ArtistID3 getItem(int id) { public ArtistID3 getItem(int id) {
return artists.get(id); return artists.get(id);
} }
@@ -109,4 +155,21 @@ public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizont
return true; 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,7 +13,6 @@ import com.cappielloantonio.tempo.databinding.ItemLibraryCatalogueGenreBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Genre; import com.cappielloantonio.tempo.subsonic.models.Genre;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -49,7 +48,7 @@ public class GenreCatalogueAdapter extends RecyclerView.Adapter<GenreCatalogueAd
@Override @Override
protected void publishResults(CharSequence constraint, FilterResults results) { protected void publishResults(CharSequence constraint, FilterResults results) {
genres.clear(); genres.clear();
genres.addAll((List) results.values); if (results.count > 0) genres.addAll((List) results.values);
notifyDataSetChanged(); notifyDataSetChanged();
} }
}; };

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,8 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
@@ -24,24 +26,60 @@ import com.cappielloantonio.tempo.util.Preferences;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@UnstableApi @UnstableApi
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> { public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> implements Filterable {
private final ClickCallback click; private final ClickCallback click;
private final boolean showCoverArt; private final boolean showCoverArt;
private final boolean showAlbum; private final boolean showAlbum;
private final AlbumID3 album; private final AlbumID3 album;
private List<Child> songsFull;
private List<Child> songs; 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) { public SongHorizontalAdapter(ClickCallback click, boolean showCoverArt, boolean showAlbum, AlbumID3 album) {
this.click = click; this.click = click;
this.showCoverArt = showCoverArt; this.showCoverArt = showCoverArt;
this.showAlbum = showAlbum; this.showAlbum = showAlbum;
this.songs = Collections.emptyList(); this.songs = Collections.emptyList();
this.songsFull = Collections.emptyList();
this.currentFilter = "";
this.album = album; this.album = album;
} }
@@ -95,7 +133,10 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
) )
) { ) {
holder.item.differentDiskDividerSector.setVisibility(View.VISIBLE); 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) { if (album.getDiscTitles() != null) {
Optional<DiscTitle> discTitle = album.getDiscTitles().stream().filter(title -> Objects.equals(title.getDisc(), songs.get(position).getDiscNumber())).findFirst(); Optional<DiscTitle> discTitle = album.getDiscTitles().stream().filter(title -> Objects.equals(title.getDisc(), songs.get(position).getDiscNumber())).findFirst();
@@ -132,7 +173,8 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
} }
public void setItems(List<Child> songs) { public void setItems(List<Child> songs) {
this.songs = songs != null ? songs : Collections.emptyList(); this.songsFull = songs != null ? songs : Collections.emptyList();
filtering.filter(currentFilter);
notifyDataSetChanged(); notifyDataSetChanged();
} }
@@ -146,6 +188,11 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
return position; return position;
} }
@Override
public Filter getFilter() {
return filtering;
}
public Child getItem(int id) { public Child getItem(int id) {
return songs.get(id); return songs.get(id);
} }
@@ -184,4 +231,20 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
return true; 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); Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT);
playlistChooserViewModel.isSongInPlaylist(Objects.requireNonNull(playlist).getId(), requireActivity()).observe(requireActivity(), songInPlaylist -> { playlistChooserViewModel.isSongInPlaylist(Objects.requireNonNull(playlist).getId(), requireActivity()).observe(requireActivity(), songInPlaylist -> {
if (songInPlaylist) { if (!songInPlaylist) {
playlistChooserViewModel.addSongToPlaylist(playlist.getId()); playlistChooserViewModel.addSongToPlaylist(playlist.getId());
dismiss(); dismiss();
} else { } else {

View File

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

View File

@@ -187,6 +187,12 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
} else if (menuItem.getItemId() == R.id.menu_album_sort_recently_added) { } else if (menuItem.getItemId() == R.id.menu_album_sort_recently_added) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_RECENTLY_ADDED); albumAdapter.sort(Constants.ALBUM_ORDER_BY_RECENTLY_ADDED);
return true; 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; return false;

View File

@@ -1,11 +1,21 @@
package com.cappielloantonio.tempo.ui.fragment; package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn; import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -17,12 +27,14 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentAlbumListPageBinding; import com.cappielloantonio.tempo.databinding.FragmentAlbumListPageBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback; 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.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumListPageViewModel; import com.cappielloantonio.tempo.viewmodel.AlbumListPageViewModel;
import java.util.List;
@OptIn(markerClass = UnstableApi.class) @OptIn(markerClass = UnstableApi.class)
public class AlbumListPageFragment extends Fragment implements ClickCallback { public class AlbumListPageFragment extends Fragment implements ClickCallback {
private FragmentAlbumListPageBinding bind; private FragmentAlbumListPageBinding bind;
@@ -31,6 +43,12 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
private AlbumListPageViewModel albumListPageViewModel; private AlbumListPageViewModel albumListPageViewModel;
private AlbumHorizontalAdapter albumHorizontalAdapter; private AlbumHorizontalAdapter albumHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity(); activity = (MainActivity) getActivity();
@@ -86,7 +104,10 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
activity.getSupportActionBar().setDisplayShowHomeEnabled(true); activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp()); bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> { bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) { if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
@@ -97,6 +118,7 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
}); });
} }
@SuppressLint("ClickableViewAccessibility")
private void initAlbumListView() { private void initAlbumListView() {
bind.albumListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.albumListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.albumListRecyclerView.setHasFixedSize(true); bind.albumListRecyclerView.setHasFixedSize(true);
@@ -107,7 +129,99 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
); );
bind.albumListRecyclerView.setAdapter(albumHorizontalAdapter); bind.albumListRecyclerView.setAdapter(albumHorizontalAdapter);
albumListPageViewModel.getAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> albumHorizontalAdapter.setItems(albums)); 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;
}
} }
@Override @Override

View File

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

View File

@@ -1,11 +1,21 @@
package com.cappielloantonio.tempo.ui.fragment; package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@@ -16,11 +26,15 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentArtistListPageBinding; import com.cappielloantonio.tempo.databinding.FragmentArtistListPageBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback; 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.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.ArtistListPageViewModel; import com.cappielloantonio.tempo.viewmodel.ArtistListPageViewModel;
import java.util.List;
@UnstableApi @UnstableApi
public class ArtistListPageFragment extends Fragment implements ClickCallback { public class ArtistListPageFragment extends Fragment implements ClickCallback {
private FragmentArtistListPageBinding bind; private FragmentArtistListPageBinding bind;
@@ -30,6 +44,12 @@ public class ArtistListPageFragment extends Fragment implements ClickCallback {
private ArtistHorizontalAdapter artistHorizontalAdapter; private ArtistHorizontalAdapter artistHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity(); activity = (MainActivity) getActivity();
@@ -69,7 +89,10 @@ public class ArtistListPageFragment extends Fragment implements ClickCallback {
activity.getSupportActionBar().setDisplayShowHomeEnabled(true); activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp()); bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> { bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.artistInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) { if ((bind.artistInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
@@ -80,18 +103,100 @@ public class ArtistListPageFragment extends Fragment implements ClickCallback {
}); });
} }
@SuppressLint("ClickableViewAccessibility")
private void initArtistListView() { private void initArtistListView() {
bind.artistListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.artistListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.artistListRecyclerView.setHasFixedSize(true); bind.artistListRecyclerView.setHasFixedSize(true);
artistHorizontalAdapter = new ArtistHorizontalAdapter(this); artistHorizontalAdapter = new ArtistHorizontalAdapter(this);
bind.artistListRecyclerView.setAdapter(artistHorizontalAdapter); bind.artistListRecyclerView.setAdapter(artistHorizontalAdapter);
artistListPageViewModel.getArtistList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> artistHorizontalAdapter.setItems(artists)); 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;
}
} }
@Override @Override
public void onArtistClick(Bundle bundle) { public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumListPageFragment, bundle); Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
} }
@Override @Override

View File

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

View File

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

View File

@@ -76,6 +76,7 @@ public class PlayerControllerFragment extends Fragment {
initQuickActionView(); initQuickActionView();
initCoverLyricsSlideView(); initCoverLyricsSlideView();
initMediaListenable(); initMediaListenable();
initMediaLabelButton();
initArtistLabelButton(); initArtistLabelButton();
return view; return view;
@@ -299,6 +300,19 @@ 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() { private void initArtistLabelButton() {
playerBottomSheetViewModel.getLiveArtist().observe(getViewLifecycleOwner(), artist -> { playerBottomSheetViewModel.getLiveArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) { if (artist != null) {

View File

@@ -1,6 +1,7 @@
package com.cappielloantonio.tempo.ui.fragment; package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@@ -8,6 +9,9 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -58,8 +62,28 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
@Override @Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.playlist_page_menu, menu); 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); initMenuOption(menu);
} }
@@ -152,11 +176,19 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
bind.playlistSongCountLabel.setText(getString(R.string.playlist_song_count, playlistPageViewModel.getPlaylist().getSongCount())); 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.playlistDurationLabel.setText(getString(R.string.playlist_duration, MusicUtil.getReadableDurationString(playlistPageViewModel.getPlaylist().getDuration(), false)));
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp()); bind.animToolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
Objects.requireNonNull(bind.animToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null)); 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() { private void initMusicButton() {
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> { playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) { if (bind != null) {

View File

@@ -1,12 +1,22 @@
package com.cappielloantonio.tempo.ui.fragment; package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@@ -22,14 +32,15 @@ import com.cappielloantonio.tempo.helper.recyclerview.PaginationScrollListener;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.SongListPageViewModel; import com.cappielloantonio.tempo.viewmodel.SongListPageViewModel;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections; import java.util.Collections;
import java.util.List;
@UnstableApi @UnstableApi
public class SongListPageFragment extends Fragment implements ClickCallback { public class SongListPageFragment extends Fragment implements ClickCallback {
@@ -45,6 +56,12 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
private boolean isLoading = true; private boolean isLoading = true;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity(); activity = (MainActivity) getActivity();
@@ -138,7 +155,10 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
} }
if (bind != null) if (bind != null)
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp()); bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
if (bind != null) if (bind != null)
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> { bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
@@ -153,6 +173,8 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
private void initButtons() { private void initButtons() {
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> { songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) { if (bind != null) {
setSongListPageSorter();
bind.songListShuffleImageView.setOnClickListener(v -> { bind.songListShuffleImageView.setOnClickListener(v -> {
Collections.shuffle(songs); Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(25, songs.size())), 0); MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(25, songs.size())), 0);
@@ -162,6 +184,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
}); });
} }
@SuppressLint("ClickableViewAccessibility")
private void initSongListView() { private void initSongListView() {
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songListRecyclerView.setHasFixedSize(true); bind.songListRecyclerView.setHasFixedSize(true);
@@ -171,6 +194,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> { songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
isLoading = false; isLoading = false;
songHorizontalAdapter.setItems(songs); songHorizontalAdapter.setItems(songs);
setSongListPageSubtitle(songs);
}); });
bind.songListRecyclerView.addOnScrollListener(new PaginationScrollListener((LinearLayoutManager) bind.songListRecyclerView.getLayoutManager()) { bind.songListRecyclerView.addOnScrollListener(new PaginationScrollListener((LinearLayoutManager) bind.songListRecyclerView.getLayoutManager()) {
@@ -185,6 +209,101 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
return isLoading; 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() { private void initializeMediaBrowser() {
@@ -197,6 +316,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
@Override @Override
public void onMediaClick(Bundle bundle) { public void onMediaClick(Bundle bundle) {
hideKeyboard(requireView());
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true); activity.setBottomSheetInPeek(true);
} }

View File

@@ -31,11 +31,17 @@ object Constants {
const val ALBUM_ORDER_BY_YEAR = "ALBUM_ORDER_BY_YEAR" 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_RANDOM = "ALBUM_ORDER_BY_RANDOM"
const val ALBUM_ORDER_BY_RECENTLY_ADDED = "ALBUM_ORDER_BY_RECENTLY_ADDED" 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_DOWNLOADED = "ARTIST_DOWNLOADED"
const val ARTIST_STARRED = "ARTIST_STARRED" const val ARTIST_STARRED = "ARTIST_STARRED"
const val ARTIST_ORDER_BY_NAME = "ARTIST_ORDER_BY_NAME" 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_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_NAME = "GENRE_ORDER_BY_NAME"
const val GENRE_ORDER_BY_RANDOM = "GENRE_ORDER_BY_RANDOM" const val GENRE_ORDER_BY_RANDOM = "GENRE_ORDER_BY_RANDOM"
@@ -74,6 +80,9 @@ object Constants {
const val MEDIA_MIX = "MEDIA_MIX" const val MEDIA_MIX = "MEDIA_MIX"
const val MEDIA_CHRONOLOGY = "MEDIA_CHRONOLOGY" const val MEDIA_CHRONOLOGY = "MEDIA_CHRONOLOGY"
const val MEDIA_BEST_OF = "MEDIA_BEST_OF" 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" const val DOWNLOAD_URI = "rest/download"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,11 +14,13 @@ import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.interfaces.StarCallback; import com.cappielloantonio.tempo.interfaces.StarCallback;
import com.cappielloantonio.tempo.model.Download; import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.model.Queue; import com.cappielloantonio.tempo.model.Queue;
import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository; import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.FavoriteRepository; import com.cappielloantonio.tempo.repository.FavoriteRepository;
import com.cappielloantonio.tempo.repository.OpenRepository; import com.cappielloantonio.tempo.repository.OpenRepository;
import com.cappielloantonio.tempo.repository.QueueRepository; import com.cappielloantonio.tempo.repository.QueueRepository;
import com.cappielloantonio.tempo.repository.SongRepository; 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.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.LyricsList; import com.cappielloantonio.tempo.subsonic.models.LyricsList;
@@ -40,6 +42,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
private static final String TAG = "PlayerBottomSheetViewModel"; private static final String TAG = "PlayerBottomSheetViewModel";
private final SongRepository songRepository; private final SongRepository songRepository;
private final AlbumRepository albumRepository;
private final ArtistRepository artistRepository; private final ArtistRepository artistRepository;
private final QueueRepository queueRepository; private final QueueRepository queueRepository;
private final FavoriteRepository favoriteRepository; private final FavoriteRepository favoriteRepository;
@@ -48,6 +51,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
private final MutableLiveData<LyricsList> lyricsListLiveData = new MutableLiveData<>(null); private final MutableLiveData<LyricsList> lyricsListLiveData = new MutableLiveData<>(null);
private final MutableLiveData<String> descriptionLiveData = new MutableLiveData<>(null); private final MutableLiveData<String> descriptionLiveData = new MutableLiveData<>(null);
private final MutableLiveData<Child> liveMedia = 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<ArtistID3> liveArtist = new MutableLiveData<>(null);
private final MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(null); private final MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(null);
private boolean lyricsSyncState = true; private boolean lyricsSyncState = true;
@@ -57,6 +61,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
super(application); super(application);
songRepository = new SongRepository(); songRepository = new SongRepository();
albumRepository = new AlbumRepository();
artistRepository = new ArtistRepository(); artistRepository = new ArtistRepository();
queueRepository = new QueueRepository(); queueRepository = new QueueRepository();
favoriteRepository = new FavoriteRepository(); favoriteRepository = new FavoriteRepository();
@@ -162,6 +167,23 @@ 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() { public LiveData<ArtistID3> getLiveArtist() {
return liveArtist; return liveArtist;
} }

View File

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

View File

@@ -33,17 +33,48 @@
<TextView <TextView
android:id="@+id/page_title_label" android:id="@+id/page_title_label"
style="@style/TitleLarge" style="@style/TitleLarge"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="16dp" android:paddingTop="16dp"
android:paddingEnd="16dp" android:paddingEnd="4dp"
android:paddingBottom="24dp" android:paddingBottom="24dp"
android:text="@string/label_placeholder" android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="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> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@@ -33,17 +33,48 @@
<TextView <TextView
android:id="@+id/page_title_label" android:id="@+id/page_title_label"
style="@style/TitleLarge" style="@style/TitleLarge"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="16dp" android:paddingTop="16dp"
android:paddingEnd="16dp" android:paddingEnd="4dp"
android:paddingBottom="24dp" android:paddingBottom="24dp"
android:text="@string/label_placeholder" android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="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> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@@ -33,17 +33,48 @@
<TextView <TextView
android:id="@+id/page_title_label" android:id="@+id/page_title_label"
style="@style/TitleLarge" style="@style/TitleLarge"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="16dp" android:paddingTop="16dp"
android:paddingEnd="16dp" android:paddingEnd="4dp"
android:paddingBottom="24dp" android:paddingBottom="24dp"
android:text="@string/label_placeholder" android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/song_list_shuffle_image_view"
app:layout_constraintStart_toStartOf="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/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 <Button
android:id="@+id/song_list_shuffle_image_view" android:id="@+id/song_list_shuffle_image_view"
style="@style/Widget.Material3.Button.TonalButton.Icon" style="@style/Widget.Material3.Button.TonalButton.Icon"

View File

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

View File

@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> 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 <item
android:id="@+id/action_download_playlist" android:id="@+id/action_download_playlist"
android:icon="@drawable/ic_file_download" android:icon="@drawable/ic_file_download"

View File

@@ -15,4 +15,10 @@
<item <item
android:id="@+id/menu_album_sort_recently_added" android:id="@+id/menu_album_sort_recently_added"
android:title="@string/menu_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> </menu>

View File

@@ -0,0 +1,12 @@
<?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

@@ -0,0 +1,12 @@
<?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

@@ -0,0 +1,12 @@
<?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,5 +8,4 @@
android:title="@string/menu_search_button" android:title="@string/menu_search_button"
app:actionViewClass="android.widget.SearchView" app:actionViewClass="android.widget.SearchView"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
</menu> </menu>

View File

@@ -23,7 +23,10 @@
<string name="album_list_page_title">Alben</string> <string name="album_list_page_title">Alben</string>
<string name="album_page_extra_info_button">Ähnliches</string> <string name="album_page_extra_info_button">Ähnliches</string>
<string name="album_page_play_button">Wiedergabe</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_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="app_name">Tempo</string>
<string name="artist_adapter_radio_station_starting">Suche…</string> <string name="artist_adapter_radio_station_starting">Suche…</string>
<string name="artist_bottom_sheet_instant_mix">Instant mix</string> <string name="artist_bottom_sheet_instant_mix">Instant mix</string>
@@ -52,15 +55,23 @@
<string name="connection_alert_dialog_positive_button">OK</string> <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_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="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_negative_button">Abbrechen</string>
<string name="delete_download_storage_dialog_positive_button">Weiter</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_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="delete_download_storage_dialog_title">Heruntergeladene Inhalte löschen</string>
<string name="description_empty_title">Keine Beschreibung verfügbar</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_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_info_empty_title">Bisher keine Downloads!</string>
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s items</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_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_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_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> <string name="download_storage_dialog_title">Wähle den Speicherort aus</string>
@@ -81,6 +92,16 @@
<string name="filter_title_expanded">Genres filtern</string> <string name="filter_title_expanded">Genres filtern</string>
<string name="genre_catalogue_title">Genre Übersicht</string> <string name="genre_catalogue_title">Genre Übersicht</string>
<string name="genre_catalogue_title_expanded">Genres durchsuchen</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_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_made_for_you">Ein Mix von einem deiner Lieblingslieder erstellen</string>
<string name="home_subtitle_new_internet_radio_station">Radio hinzufügen</string> <string name="home_subtitle_new_internet_radio_station">Radio hinzufügen</string>
@@ -97,11 +118,14 @@
<string name="home_title_last_played">Zuletzt gespielt</string> <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_played_see_all_button">Alle zeigen</string>
<string name="home_title_last_week">Letzte Woche</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_made_for_you">Wie für Dich gemacht</string>
<string name="home_title_most_played">Oft gespielt</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_most_played_see_all_button">Alle zeigen</string>
<string name="home_title_new_releases">Neue Releases</string> <string name="home_title_new_releases">Neue Releases</string>
<string name="home_title_newest_podcasts">Neueste Podcasts</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">Kanäle</string>
<string name="home_title_podcast_channels_see_all_button">Alle zeigen</string> <string name="home_title_podcast_channels_see_all_button">Alle zeigen</string>
<string name="home_title_radio_station">Radio Stationen</string> <string name="home_title_radio_station">Radio Stationen</string>
@@ -115,6 +139,7 @@
<string name="home_title_starred_tracks">★ Lieblingslieder</string> <string name="home_title_starred_tracks">★ Lieblingslieder</string>
<string name="home_title_starred_tracks_see_all_button">Alle zeigen</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_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">Alben</string>
<string name="library_title_album_see_all_button">Alle zeigen</string> <string name="library_title_album_see_all_button">Alle zeigen</string>
<string name="library_title_artist">Künstler</string> <string name="library_title_artist">Künstler</string>
@@ -139,14 +164,21 @@
<string name="menu_group_by_track">Track</string> <string name="menu_group_by_track">Track</string>
<string name="menu_group_by_year">Jahr</string> <string name="menu_group_by_year">Jahr</string>
<string name="menu_home_label">Start</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_library_label">Sammlung</string>
<string name="menu_search_button">Suche</string> <string name="menu_search_button">Suche</string>
<string name="menu_settings_button">Einstellungen</string> <string name="menu_settings_button">Einstellungen</string>
<string name="menu_sort_artist">Künstler</string> <string name="menu_sort_artist">Künstler</string>
<string name="menu_sort_name">Name</string> <string name="menu_sort_name">Name</string>
<string name="menu_sort_random">Zufall</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="menu_sort_year">Jahr</string>
<string name="player_playback_speed">%1$.2fx</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="player_server_priority">Server Priorität</string>
<string name="playlist_catalogue_title">Playlisten</string> <string name="playlist_catalogue_title">Playlisten</string>
<string name="playlist_catalogue_title_expanded">Playlisten durchsuchen</string> <string name="playlist_catalogue_title_expanded">Playlisten durchsuchen</string>
@@ -156,6 +188,7 @@
<string name="playlist_chooser_dialog_title">Zu einer Playliste hinzufügen</string> <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_counted_tracks">%1$d Tracks • %2$s</string>
<string name="playlist_duration">Länge • %1$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_hint_name">Name der Playliste</string>
<string name="playlist_editor_dialog_negative_button">Abbrechen</string> <string name="playlist_editor_dialog_negative_button">Abbrechen</string>
<string name="playlist_editor_dialog_neutral_button">Löschen</string> <string name="playlist_editor_dialog_neutral_button">Löschen</string>
@@ -201,6 +234,8 @@
<string name="search_title_artist">Künstler</string> <string name="search_title_artist">Künstler</string>
<string name="search_title_song">Tracks</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_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_name">Server Name</string>
<string name="server_signup_dialog_hint_password">Passwort</string> <string name="server_signup_dialog_hint_password">Passwort</string>
<string name="server_signup_dialog_hint_url">Server URL</string> <string name="server_signup_dialog_hint_url">Server URL</string>
@@ -216,6 +251,7 @@
<string name="server_unreachable_dialog_title">Server nicht erreichbar</string> <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_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_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_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_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> <string name="settings_audio_transcode_download_priority_title">Transkodierungseinstellungen des Servers für Downloads bevorzugen.</string>
@@ -226,6 +262,10 @@
<string name="settings_audio_transcode_priority_summary">Diese Option deaktiviert die weiter unten folgenden Transkodierungseinstellungen.</string> <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_title">Transkodierungseinstellungen des Servers bevorzugen</string>
<string name="settings_audio_transcode_priority_toast">Servereinstellungen zur Transkodierung des Tracks werden bevorzugt</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_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_mobile">Transkodierungsformat im mobilen Netz</string>
<string name="settings_audio_transcode_format_wifi">Transkodierungsformat im Wi-Fi</string> <string name="settings_audio_transcode_format_wifi">Transkodierungsformat im Wi-Fi</string>
@@ -251,6 +291,10 @@
<string name="settings_music_directory_summary">Zeige den Bereich für Musikverzeichnisse. Der Server muss das Feature unterstützen.</string> <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">Podcasts anzeigen</string>
<string name="settings_podcast_summary">Zeige den Bereich für Podcasts.</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_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_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> <string name="settings_queue_syncing_title">Warteschlange für diesen User synchronisieren</string>
@@ -262,10 +306,17 @@
<string name="settings_rounded_corner_size_summary">Definiert den Eckenradius.</string> <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_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_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_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_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_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_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">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_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> <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>
@@ -274,7 +325,11 @@
<string name="settings_theme">Design</string> <string name="settings_theme">Design</string>
<string name="settings_title_data">Daten</string> <string name="settings_title_data">Daten</string>
<string name="settings_title_general">Allgemein</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_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_share">Teilen</string>
<string name="settings_title_syncing">Sychronisierung</string> <string name="settings_title_syncing">Sychronisierung</string>
<string name="settings_title_transcoding">Transkodierung</string> <string name="settings_title_transcoding">Transkodierung</string>
@@ -319,6 +374,12 @@
<string name="starred_sync_dialog_positive_button">Weiter und Herunterladen</string> <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_summary">Das Herunterladen deiner Lieblingslieder kann viel Datenvolumen verbrauchen.</string>
<string name="starred_sync_dialog_title">Lieblingslieder synchronisieren</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_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_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> <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="downloaded_bottom_sheet_shuffle">Mélanger</string>
<string name="empty_string" /> <string name="empty_string" />
<string name="error_required">Requis</string> <string name="error_required">Requis</string>
<string name="error_server_prefix">prefix http ou https requis</string> <string name="error_server_prefix">préfixe http ou https requis</string>
<string name="exo_download_notification_channel_name">Téléchargements</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_info_selection">Sélectionnez deux filtres ou plus</string>
<string name="filter_title">Filtrer</string> <string name="filter_title">Filtrer</string>
@@ -201,7 +201,7 @@
<string name="rating_dialog_positive_button">Enregistrer</string> <string name="rating_dialog_positive_button">Enregistrer</string>
<string name="rating_dialog_title">Noter</string> <string name="rating_dialog_title">Noter</string>
<string name="search_hint">Rechercher des titres, artistes ou albums</string> <string name="search_hint">Rechercher des titres, artistes ou albums</string>
<string name="search_info_minimum_characters">Entrez 3 charactères minimum</string> <string name="search_info_minimum_characters">Entrez 3 caractères minimum</string>
<string name="search_title_album">Albums</string> <string name="search_title_album">Albums</string>
<string name="search_title_artist">Artistes</string> <string name="search_title_artist">Artistes</string>
<string name="search_title_song">Pistes</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_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_about_title">À propos</string>
<string name="settings_audio_transcode_download_format">Format de transcodage</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ètre 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ètres 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_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_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> <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_positive_button">Enregistrer</string>
<string name="share_update_dialog_title">Partager</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_playlist">Ajouter à une playlist</string>
<string name="song_bottom_sheet_add_to_queue">Ajouter à la fil d\'attente</string> <string name="song_bottom_sheet_add_to_queue">Ajouter à la file d\'attente</string>
<string name="song_bottom_sheet_download">Télécharger</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_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> <string name="song_bottom_sheet_error_retrieving_artist">Erreur de récupération de l\'artiste</string>

View File

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

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

@@ -0,0 +1,41 @@
<?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,6 +32,21 @@
<item>300</item> <item>300</item>
</string-array> </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"> <string-array name="max_bitrate_wifi_list_titles">
<item>Оригинал</item> <item>Оригинал</item>
<item>32 kbps</item> <item>32 kbps</item>

View File

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

View File

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

View File

@@ -90,6 +90,8 @@
<string name="filter_info_selection">Select two or more filters</string> <string name="filter_info_selection">Select two or more filters</string>
<string name="filter_title">Filter</string> <string name="filter_title">Filter</string>
<string name="filter_title_expanded">Filter Genres</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">Genre Catalogue</string>
<string name="genre_catalogue_title_expanded">Browse Genres</string> <string name="genre_catalogue_title_expanded">Browse Genres</string>
<string name="github_update_dialog_negative_button">Remind me later</string> <string name="github_update_dialog_negative_button">Remind me later</string>
@@ -176,6 +178,10 @@
<string name="menu_sort_name">Name</string> <string name="menu_sort_name">Name</string>
<string name="menu_sort_random">Random</string> <string name="menu_sort_random">Random</string>
<string name="menu_sort_recently_added">Recently added</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_pin_button">Add to home screen</string>
<string name="menu_unpin_button">Remove from home screen</string> <string name="menu_unpin_button">Remove from home screen</string>
<string name="menu_sort_year">Year</string> <string name="menu_sort_year">Year</string>

View File

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

View File

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

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

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

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

@@ -0,0 +1,14 @@
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() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.4.1' classpath 'com.android.tools.build:gradle:8.13.1'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21'
} }
} }

View File

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

View File

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

Binary file not shown.

12
privacy.html Normal file

File diff suppressed because one or more lines are too long