diff --git a/.circleci/config.yml b/.circleci/config.yml index 5bacbf21..18e42157 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,9 +10,9 @@ jobs: - checkout - restore_cache: keys: - - v1-ultrasonic-{{ .Branch }}-{{ checksum "dependencies.gradle" }} - - v1-ultrasonic-{{ .Branch }} - - v1-ultrasonic + - v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }} + - v2-ultrasonic-{{ .Branch }} + - v2-ultrasonic - run: name: configure gradle.properties for CI building command: | @@ -44,7 +44,7 @@ jobs: - save_cache: paths: - ~/.gradle - key: v1-ultrasonic-{{ .Branch }}-{{ checksum "dependencies.gradle" }} + key: v1-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }} - store_artifacts: path: ultrasonic/build/reports destination: reports @@ -81,9 +81,9 @@ jobs: - checkout - restore_cache: keys: - - v1-ultrasonic-{{ .Branch }}-{{ checksum "dependencies.gradle" }} - - v1-ultrasonic-{{ .Branch }} - - v1-ultrasonic + - v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }} + - v2-ultrasonic-{{ .Branch }} + - v2-ultrasonic - run: name: decrypt ultrasonic-keystore command: openssl aes-256-cbc -K ${ULTRASONIC_KEYSTORE_KEY} -iv ${ULTRASONIC_KEYSTORE_IV} -in ultrasonic-keystore.enc -out ultrasonic-keystore -d diff --git a/build.gradle b/build.gradle index d6d7c22f..925569fa 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - apply from: 'dependencies.gradle' + apply from: 'gradle/versions.gradle' ext.bootstrap = [ kotlinModule : "${project.rootDir}/gradle_scripts/kotlin-module-bootstrap.gradle", @@ -13,11 +13,11 @@ buildscript { maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath gradlePlugins.gradle - classpath gradlePlugins.kotlin - classpath gradlePlugins.ktlintGradle - classpath gradlePlugins.detekt - classpath gradlePlugins.jacoco + classpath libs.gradle + classpath libs.kotlin + classpath libs.ktlintGradle + classpath libs.detekt + classpath libs.jacoco } } @@ -47,6 +47,6 @@ allprojects { apply from: 'gradle_scripts/jacoco.gradle' wrapper { - gradleVersion(versions.gradle) + gradleVersion(libs.versions.gradle.get()) distributionType("all") } diff --git a/core/domain/build.gradle b/core/domain/build.gradle index 734c977b..0b0ab96c 100644 --- a/core/domain/build.gradle +++ b/core/domain/build.gradle @@ -8,7 +8,7 @@ ext { } dependencies { - implementation androidSupport.roomRuntime - implementation androidSupport.roomKtx - kapt androidSupport.room + implementation libs.roomRuntime + implementation libs.roomKtx + kapt libs.room } diff --git a/core/subsonic-api/build.gradle b/core/subsonic-api/build.gradle index a95c4078..9ad09193 100644 --- a/core/subsonic-api/build.gradle +++ b/core/subsonic-api/build.gradle @@ -1,24 +1,24 @@ apply from: bootstrap.kotlinModule dependencies { - api other.retrofit - api other.jacksonConverter - api other.koinCore + api libs.retrofit + api libs.jacksonConverter + api libs.koinCore - implementation(other.jacksonKotlin) { + implementation(libs.jacksonKotlin) { exclude module: 'kotlin-reflect' } - implementation other.kotlinReflect // for jackson kotlin, but to use the same version - implementation other.okhttpLogging - implementation other.timber + implementation libs.kotlinReflect // for jackson kotlin, but to use the same version + implementation libs.okhttpLogging + implementation libs.timber - testImplementation testing.kotlinJunit - testImplementation testing.mockito - testImplementation testing.mockitoInline - testImplementation testing.mockitoKotlin - testImplementation testing.kluent - testImplementation testing.mockWebServer - testImplementation testing.apacheCodecs + testImplementation libs.kotlinJunit + testImplementation libs.mockito + testImplementation libs.mockitoInline + testImplementation libs.mockitoKotlin + testImplementation libs.kluent + testImplementation libs.mockWebServer + testImplementation libs.apacheCodecs } ext { diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt index d6a11332..1c070086 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetAvatarTest.kt @@ -1,8 +1,8 @@ package org.moire.ultrasonic.api.subsonic import okhttp3.mockwebserver.MockResponse -import org.amshove.kluent.`should be equal to` import org.amshove.kluent.`should be` +import org.amshove.kluent.`should be equal to` import org.amshove.kluent.`should not be` import org.junit.Test diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt index 2c047ee8..8e54d42c 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetCoverArtTest.kt @@ -1,8 +1,8 @@ package org.moire.ultrasonic.api.subsonic import okhttp3.mockwebserver.MockResponse -import org.amshove.kluent.`should be equal to` import org.amshove.kluent.`should be` +import org.amshove.kluent.`should be equal to` import org.amshove.kluent.`should not be` import org.junit.Test diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetMusicDirectoryTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetMusicDirectoryTest.kt index 9249eb35..51579a72 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetMusicDirectoryTest.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiGetMusicDirectoryTest.kt @@ -1,7 +1,7 @@ package org.moire.ultrasonic.api.subsonic -import org.amshove.kluent.`should be equal to` import org.amshove.kluent.`should be` +import org.amshove.kluent.`should be equal to` import org.amshove.kluent.`should not be` import org.junit.Test import org.moire.ultrasonic.api.subsonic.models.MusicDirectory diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt index ad16462b..d56e37f9 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiStreamTest.kt @@ -1,8 +1,8 @@ package org.moire.ultrasonic.api.subsonic import okhttp3.mockwebserver.MockResponse -import org.amshove.kluent.`should be equal to` import org.amshove.kluent.`should be` +import org.amshove.kluent.`should be equal to` import org.amshove.kluent.`should not be` import org.junit.Test diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index b5847c95..324d672f 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -83,7 +83,7 @@ class SubsonicAPIClient( // Create the Retrofit instance, and register a special converter factory // It will update our protocol version to the correct version, once we made a successful call - val retrofit: Retrofit = Retrofit.Builder() + private val retrofit: Retrofit = Retrofit.Builder() .baseUrl("${config.baseUrl}/rest/") .client(okHttpClient) .addConverterFactory( @@ -113,13 +113,16 @@ class SubsonicAPIClient( this.addInterceptor(loggingInterceptor) } - @SuppressWarnings("TrustAllX509TrustManager", "EmptyFunctionBlock") private fun OkHttpClient.Builder.allowSelfSignedCertificates() { - val trustManager = object : X509TrustManager { - override fun checkClientTrusted(p0: Array?, p1: String?) {} - override fun checkServerTrusted(p0: Array?, p1: String?) {} - override fun getAcceptedIssuers(): Array = emptyArray() - } + val trustManager = + @Suppress("CustomX509TrustManager") + object : X509TrustManager { + @Suppress("TrustAllX509TrustManager") + override fun checkClientTrusted(p0: Array?, p1: String?) {} + @Suppress("TrustAllX509TrustManager") + override fun checkServerTrusted(p0: Array?, p1: String?) {} + override fun getAcceptedIssuers(): Array = emptyArray() + } val sslContext = SSLContext.getInstance("SSL") sslContext.init(null, arrayOf(trustManager), SecureRandom()) diff --git a/dependencies.gradle b/dependencies.gradle deleted file mode 100644 index dc209fe0..00000000 --- a/dependencies.gradle +++ /dev/null @@ -1,110 +0,0 @@ -ext.versions = [ - minSdk : 21, - targetSdk : 30, - compileSdk : 30, - // You need to run ./gradlew wrapper after updating the version - gradle : '7.2', - - navigation : "2.3.5", - gradlePlugin : "4.2.2", - androidxcore : "1.6.0", - ktlint : "0.37.1", - ktlintGradle : "10.2.0", - detekt : "1.19.0", - jacoco : "0.8.7", - preferences : "1.1.1", - media : "1.3.1", - - androidSupport : "28.0.0", - androidLegacySupport : "1.0.0", - androidSupportDesign : "1.4.0", - constraintLayout : "2.1.1", - multidex : "2.0.1", - room : "2.3.0", - kotlin : "1.5.31", - kotlinxCoroutines : "1.6.0-native-mt", - viewModelKtx : "2.3.0", - - retrofit : "2.6.4", - jackson : "2.9.5", - okhttp : "3.12.13", - koin : "3.0.2", - picasso : "2.71828", - - junit4 : "4.13.2", - junit5 : "5.8.1", - mockito : "4.1.0", - mockitoKotlin : "4.0.0", - kluent : "1.68", - apacheCodecs : "1.15", - robolectric : "4.6.1", - timber : "4.7.1", - fastScroll : "2.0.1", - colorPicker : "2.2.3", - rxJava : "3.1.2", - rxAndroid : "3.0.0", - multiType : "4.3.0", -] - -ext.gradlePlugins = [ - gradle : "com.android.tools.build:gradle:$versions.gradlePlugin", - kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin", - ktlintGradle : "org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle", - detekt : "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt", - jacoco : "org.jacoco:org.jacoco.core:$versions.jacoco", -] - -ext.androidSupport = [ - core : "androidx.core:core-ktx:$versions.androidxcore", - support : "androidx.legacy:legacy-support-v4:$versions.androidLegacySupport", - design : "com.google.android.material:material:$versions.androidSupportDesign", - annotations : "com.android.support:support-annotations:$versions.androidSupport", - multidex : "androidx.multidex:multidex:$versions.multidex", - constraintLayout : "androidx.constraintlayout:constraintlayout:$versions.constraintLayout", - room : "androidx.room:room-compiler:$versions.room", - roomRuntime : "androidx.room:room-runtime:$versions.room", - roomKtx : "androidx.room:room-ktx:$versions.room", - viewModelKtx : "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.viewModelKtx", - navigationFragment : "androidx.navigation:navigation-fragment:$versions.navigation", - navigationUi : "androidx.navigation:navigation-ui:$versions.navigation", - navigationFragmentKtx : "androidx.navigation:navigation-fragment-ktx:$versions.navigation", - navigationUiKtx : "androidx.navigation:navigation-ui-ktx:$versions.navigation", - navigationFeature : "androidx.navigation:navigation-dynamic-features-fragment:$versions.navigation", - preferences : "androidx.preference:preference:$versions.preferences", - media : "androidx.media:media:$versions.media", -] - -ext.other = [ - kotlinStdlib : "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin", - kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin", - kotlinxCoroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.kotlinxCoroutines", - retrofit : "com.squareup.retrofit2:retrofit:$versions.retrofit", - gsonConverter : "com.squareup.retrofit2:converter-gson:$versions.retrofit", - jacksonConverter : "com.squareup.retrofit2:converter-jackson:$versions.retrofit", - jacksonKotlin : "com.fasterxml.jackson.module:jackson-module-kotlin:$versions.jackson", - okhttpLogging : "com.squareup.okhttp3:logging-interceptor:$versions.okhttp", - koinCore : "io.insert-koin:koin-core:$versions.koin", - koinAndroid : "io.insert-koin:koin-android:$versions.koin", - koinViewModel : "io.insert-koin:koin-android-viewmodel:$versions.koin", - picasso : "com.squareup.picasso:picasso:$versions.picasso", - timber : "com.jakewharton.timber:timber:$versions.timber", - fastScroll : "com.simplecityapps:recyclerview-fastscroll:$versions.fastScroll", - colorPickerView : "com.github.skydoves:colorpickerview:$versions.colorPicker", - rxJava : "io.reactivex.rxjava3:rxjava:$versions.rxJava", - rxAndroid : "io.reactivex.rxjava3:rxandroid:$versions.rxAndroid", - multiType : "com.drakeet.multitype:multitype:$versions.multiType", -] - -ext.testing = [ - junit : "junit:junit:$versions.junit4", - junitVintage : "org.junit.vintage:junit-vintage-engine:$versions.junit5", - kotlinJunit : "org.jetbrains.kotlin:kotlin-test-junit:$versions.kotlin", - mockitoKotlin : "org.mockito.kotlin:mockito-kotlin:$versions.mockitoKotlin", - mockito : "org.mockito:mockito-core:$versions.mockito", - mockitoInline : "org.mockito:mockito-inline:$versions.mockito", - kluent : "org.amshove.kluent:kluent:$versions.kluent", - kluentAndroid : "org.amshove.kluent:kluent-android:$versions.kluent", - mockWebServer : "com.squareup.okhttp3:mockwebserver:$versions.okhttp", - apacheCodecs : "commons-codec:commons-codec:$versions.apacheCodecs", - robolectric : "org.robolectric:robolectric:$versions.robolectric" -] diff --git a/fastlane/metadata/android/en-US/changelogs/100.txt b/fastlane/metadata/android/en-US/changelogs/100.txt new file mode 100644 index 00000000..77f90ee8 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/100.txt @@ -0,0 +1,4 @@ +Others + +- #671: Bump versions.mockito from 4.1.0 to 4.3.1. +- Update translations. diff --git a/fastlane/metadata/android/es-ES/changelogs/100.txt b/fastlane/metadata/android/es-ES/changelogs/100.txt new file mode 100644 index 00000000..6f2ce6d2 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/100.txt @@ -0,0 +1,4 @@ +Otros + +- #671: Actualizado versions.mockito de 4.1.0 a 4.3.1. +- Traducciones actualizadas. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..3fe75ef6 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,100 @@ +[versions] +# You need to run ./gradlew wrapper after updating the version +gradle = "7.3.2" + +navigation = "2.3.5" +gradlePlugin = "7.0.4" +androidxcore = "1.6.0" +ktlint = "0.43.2" +ktlintGradle = "10.2.0" +detekt = "1.19.0" +jacoco = "0.8.7" +preferences = "1.1.1" +media = "1.3.1" + +androidSupport = "28.0.0" +androidLegacySupport = "1.0.0" +androidSupportDesign = "1.4.0" +constraintLayout = "2.1.1" +multidex = "2.0.1" +room = "2.4.0" +kotlin = "1.6.10" +kotlinxCoroutines = "1.6.0-native-mt" +viewModelKtx = "2.3.0" + +retrofit = "2.6.4" +jackson = "2.9.5" +okhttp = "3.12.13" +koin = "3.0.2" +picasso = "2.71828" + +junit4 = "4.13.2" +junit5 = "5.8.1" +mockito = "4.3.1" +mockitoKotlin = "4.0.0" +kluent = "1.68" +apacheCodecs = "1.15" +robolectric = "4.6.1" +timber = "4.7.1" +fastScroll = "2.0.1" +colorPicker = "2.2.3" +rxJava = "3.1.2" +rxAndroid = "3.0.0" +multiType = "4.3.0" + +[libraries] +gradle = { module = "com.android.tools.build:gradle", version.ref = "gradlePlugin" } +kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +ktlintGradle = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlintGradle" } +detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } +jacoco = { module = "org.jacoco:org.jacoco.core", version.ref = "jacoco" } + +core = { module = "androidx.core:core-ktx", version.ref = "androidxcore" } +support = { module = "androidx.legacy:legacy-support-v4", version.ref = "androidLegacySupport" } +design = { module = "com.google.android.material:material", version.ref = "androidSupportDesign" } +annotations = { module = "com.android.support:support-annotations", version.ref = "androidSupport" } +multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" } +constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintLayout" } +room = { module = "androidx.room:room-compiler", version.ref = "room" } +roomRuntime = { module = "androidx.room:room-runtime", version.ref = "room" } +roomKtx = { module = "androidx.room:room-ktx", version.ref = "room" } +viewModelKtx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "viewModelKtx" } +navigationFragment = { module = "androidx.navigation:navigation-fragment", version.ref = "navigation" } +navigationUi = { module = "androidx.navigation:navigation-ui", version.ref = "navigation" } +navigationFragmentKtx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" } +navigationUiKtx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" } +navigationFeature = { module = "androidx.navigation:navigation-dynamic-features-fragment", version.ref = "navigation" } +preferences = { module = "androidx.preference:preference", version.ref = "preferences" } +media = { module = "androidx.media:media", version.ref = "media" } + +kotlinStdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } +kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } +kotlinxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } +retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } +gsonConverter = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" } +jacksonConverter = { module = "com.squareup.retrofit2:converter-jackson", version.ref = "retrofit" } +jacksonKotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } +okhttpLogging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } +koinCore = { module = "io.insert-koin:koin-core", version.ref = "koin" } +koinAndroid = { module = "io.insert-koin:koin-android", version.ref = "koin" } +koinViewModel = { module = "io.insert-koin:koin-android-viewmodel", version.ref = "koin" } +picasso = { module = "com.squareup.picasso:picasso", version.ref = "picasso" } +timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } +fastScroll = { module = "com.simplecityapps:recyclerview-fastscroll", version.ref = "fastScroll" } +colorPickerView = { module = "com.github.skydoves:colorpickerview", version.ref = "colorPicker" } +rxJava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxJava" } +rxAndroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxAndroid" } +multiType = { module = "com.drakeet.multitype:multitype", version.ref = "multiType" } + +junit = { module = "junit:junit", version.ref = "junit4" } +junitVintage = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit5" } +kotlinJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +mockitoKotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlin" } +mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } +mockitoInline = { module = "org.mockito:mockito-inline", version.ref = "mockito" } +kluent = { module = "org.amshove.kluent:kluent", version.ref = "kluent" } +kluentAndroid = { module = "org.amshove.kluent:kluent-android", version.ref = "kluent" } +mockWebServer = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" } +apacheCodecs = { module = "commons-codec:commons-codec", version.ref = "apacheCodecs" } +robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } + diff --git a/gradle/versions.gradle b/gradle/versions.gradle new file mode 100644 index 00000000..1a8f14d4 --- /dev/null +++ b/gradle/versions.gradle @@ -0,0 +1,5 @@ +ext.versions = [ + minSdk : 21, + targetSdk : 30, + compileSdk : 31, +] \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c0..7454180f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a0f7639f..ac0b842f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle_scripts/android-module-bootstrap.gradle b/gradle_scripts/android-module-bootstrap.gradle index 6bbfec2e..84aa4e41 100644 --- a/gradle_scripts/android-module-bootstrap.gradle +++ b/gradle_scripts/android-module-bootstrap.gradle @@ -55,14 +55,14 @@ tasks.withType(Test) { } dependencies { - api other.kotlinStdlib + api libs.kotlinStdlib - testImplementation testing.junit - testRuntimeOnly testing.junitVintage + testImplementation libs.junit + testRuntimeOnly libs.junitVintage } jacoco { - toolVersion(versions.jacoco) + toolVersion(libs.versions.jacoco.get()) } ext { diff --git a/gradle_scripts/code_quality.gradle b/gradle_scripts/code_quality.gradle index e6a23903..1b90102b 100644 --- a/gradle_scripts/code_quality.gradle +++ b/gradle_scripts/code_quality.gradle @@ -6,7 +6,7 @@ if (isCodeQualityEnabled) { apply plugin: "org.jlleitschuh.gradle.ktlint" ktlint { - version = versions.ktlint + version = libs.versions.ktlint.get() outputToConsole = true android = true } @@ -21,7 +21,7 @@ if (isCodeQualityEnabled) { detekt { buildUponDefaultConfig = true - toolVersion = versions.detekt + toolVersion = libs.versions.detekt.get() // Builds the AST in parallel. Rules are always executed in parallel. // Can lead to speedups in larger projects. parallel = true diff --git a/gradle_scripts/jacoco.gradle b/gradle_scripts/jacoco.gradle index b7f3cdc5..c8ba2c07 100644 --- a/gradle_scripts/jacoco.gradle +++ b/gradle_scripts/jacoco.gradle @@ -1,7 +1,7 @@ apply plugin: 'jacoco' jacoco { - toolVersion(versions.jacoco) + toolVersion(libs.versions.jacoco.get()) } def mergedJacocoExec = file("${project.buildDir}/jacoco/jacocoMerged.exec") diff --git a/gradle_scripts/kotlin-module-bootstrap.gradle b/gradle_scripts/kotlin-module-bootstrap.gradle index fc84bbd8..4a17c80f 100644 --- a/gradle_scripts/kotlin-module-bootstrap.gradle +++ b/gradle_scripts/kotlin-module-bootstrap.gradle @@ -15,14 +15,14 @@ sourceSets { dependencies { - api other.kotlinStdlib + api libs.kotlinStdlib - testImplementation testing.junit - testRuntimeOnly testing.junitVintage + testImplementation libs.junit + testRuntimeOnly libs.junitVintage } jacoco { - toolVersion(versions.jacoco) + toolVersion(libs.versions.jacoco.get()) } ext { diff --git a/gradlew b/gradlew index 4f906e0c..c53aefaa 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/settings.gradle b/settings.gradle index 8fdc7dca..272f548b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,5 @@ +enableFeaturePreview("VERSION_CATALOGS") + include ':core:domain' include ':core:subsonic-api' include ':ultrasonic' diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 34bf88de..d9d1e948 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -9,8 +9,8 @@ android { defaultConfig { applicationId "org.moire.ultrasonic" - versionCode 99 - versionName "3.0.0" + versionCode 100 + versionName "3.0.1" minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk @@ -85,54 +85,54 @@ dependencies { implementation project(':core:domain') implementation project(':core:subsonic-api') - api(other.picasso) { + api(libs.picasso) { exclude group: "com.android.support" } - implementation androidSupport.core - implementation androidSupport.support - implementation androidSupport.design - implementation androidSupport.multidex - implementation androidSupport.roomRuntime - implementation androidSupport.roomKtx - implementation androidSupport.viewModelKtx - implementation androidSupport.constraintLayout - implementation androidSupport.preferences - implementation androidSupport.media + implementation libs.core + implementation libs.support + implementation libs.design + implementation libs.multidex + implementation libs.roomRuntime + implementation libs.roomKtx + implementation libs.viewModelKtx + implementation libs.constraintLayout + implementation libs.preferences + implementation libs.media - implementation androidSupport.navigationFragment - implementation androidSupport.navigationUi - implementation androidSupport.navigationFragmentKtx - implementation androidSupport.navigationUiKtx - implementation androidSupport.navigationFeature + implementation libs.navigationFragment + implementation libs.navigationUi + implementation libs.navigationFragmentKtx + implementation libs.navigationUiKtx + implementation libs.navigationFeature - implementation other.kotlinStdlib - implementation other.kotlinxCoroutines - implementation other.koinAndroid - implementation other.okhttpLogging - implementation other.fastScroll - implementation other.colorPickerView - implementation other.rxJava - implementation other.rxAndroid - implementation other.multiType + implementation libs.kotlinStdlib + implementation libs.kotlinxCoroutines + implementation libs.koinAndroid + implementation libs.okhttpLogging + implementation libs.fastScroll + implementation libs.colorPickerView + implementation libs.rxJava + implementation libs.rxAndroid + implementation libs.multiType - kapt androidSupport.room + kapt libs.room - testImplementation other.kotlinReflect - testImplementation testing.junit - testRuntimeOnly testing.junitVintage - testImplementation testing.kotlinJunit - testImplementation testing.kluent - testImplementation testing.mockito - testImplementation testing.mockitoInline - testImplementation testing.mockitoKotlin - testImplementation testing.robolectric + testImplementation libs.kotlinReflect + testImplementation libs.junit + testRuntimeOnly libs.junitVintage + testImplementation libs.kotlinJunit + testImplementation libs.kluent + testImplementation libs.mockito + testImplementation libs.mockitoInline + testImplementation libs.mockitoKotlin + testImplementation libs.robolectric - implementation other.timber + implementation libs.timber } jacoco { - toolVersion(versions.jacoco) + toolVersion(libs.versions.jacoco.get()) } // Excluding all java classes and stuff that should not be covered @@ -156,7 +156,7 @@ ext { } jacoco { - toolVersion(versions.jacoco) + toolVersion(libs.versions.jacoco.get()) } tasks.withType(Test) { diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml index 600b459a..cd999750 100644 --- a/ultrasonic/lint-baseline.xml +++ b/ultrasonic/lint-baseline.xml @@ -1,19 +1,5 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . - - Copyright 2009 (C) Sindre Mehus - */ -package org.moire.ultrasonic.util; - -import java.lang.ref.SoftReference; -import java.util.HashMap; -import java.util.Map; - -/** - * @author Sindre Mehus - */ -public class LRUCache -{ - - private final int capacity; - private final Map map; - - public LRUCache(int capacity) - { - map = new HashMap(capacity); - this.capacity = capacity; - } - - public synchronized V get(K key) - { - TimestampedValue value = map.get(key); - - V result = null; - if (value != null) - { - value.updateTimestamp(); - result = value.getValue(); - } - - return result; - } - - public synchronized void put(K key, V value) - { - if (map.size() >= capacity) - { - removeOldest(); - } - map.put(key, new TimestampedValue(value)); - } - - public void clear() - { - map.clear(); - } - - private void removeOldest() - { - K oldestKey = null; - long oldestTimestamp = Long.MAX_VALUE; - - for (Map.Entry entry : map.entrySet()) - { - K key = entry.getKey(); - TimestampedValue value = entry.getValue(); - if (value.getTimestamp() < oldestTimestamp) - { - oldestTimestamp = value.getTimestamp(); - oldestKey = key; - } - } - - if (oldestKey != null) - { - map.remove(oldestKey); - } - } - - private final class TimestampedValue - { - - private final SoftReference value; - private long timestamp; - - public TimestampedValue(V value) - { - this.value = new SoftReference(value); - updateTimestamp(); - } - - public V getValue() - { - return value.get(); - } - - public long getTimestamp() - { - return timestamp; - } - - public void updateTimestamp() - { - timestamp = System.currentTimeMillis(); - } - } -} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt index 3dfbe830..2a915be3 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt @@ -208,8 +208,8 @@ class NavigationActivity : AppCompatActivity() { selectServerButton?.text = getString(R.string.main_setup_server, activeServer.name) else selectServerButton?.text = activeServer.name - val foregroundColor = ServerColor.getForegroundColor(this, null) - val backgroundColor = ServerColor.getBackgroundColor(this, null) + val foregroundColor = ServerColor.getForegroundColor(this, activeServer.color) + val backgroundColor = ServerColor.getBackgroundColor(this, activeServer.color) if (activeServer.index == 0) selectServerButton?.icon = diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ServerRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ServerRowAdapter.kt index 1247ac66..89c7a5ea 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ServerRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/ServerRowAdapter.kt @@ -108,8 +108,8 @@ internal class ServerRowAdapter( } // Set colors - icon?.setTint(ServerColor.getForegroundColor(context, null)) - background?.setTint(ServerColor.getBackgroundColor(context, null)) + icon?.setTint(ServerColor.getForegroundColor(context, setting?.color)) + background?.setTint(ServerColor.getBackgroundColor(context, setting?.color)) // Set the final drawables image?.setImageDrawable(icon) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt index 2096928f..fb83096f 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt @@ -38,7 +38,10 @@ class UApp : MultiDexApplication() { } startKoin { - logger(TimberKoinLogger(Level.INFO)) + // TODO Currently there is a bug in Koin which makes necessary to set the loglevel to ERROR + logger(TimberKoinLogger(Level.ERROR)) + // logger(TimberKoinLogger(Level.INFO)) + // declare Android context androidContext(this@UApp) // declare modules to use diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt index e12339ae..2eecb7d7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt @@ -9,7 +9,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase * Room Database to be used to store global data for the whole app. * This could be settings or data that are not specific to any remote music database */ -@Database(entities = [ServerSetting::class], version = 3) +@Database(entities = [ServerSetting::class], version = 4) abstract class AppDatabase : RoomDatabase() { /** diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt index 705f526a..e3fb9b04 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt @@ -23,6 +23,7 @@ data class ServerSetting( @ColumnInfo(name = "index") var index: Int, @ColumnInfo(name = "name") var name: String, @ColumnInfo(name = "url") var url: String, + @ColumnInfo(name = "color") var color: Int? = null, @ColumnInfo(name = "userName") var userName: String, @ColumnInfo(name = "password") var password: String, @ColumnInfo(name = "jukeboxByDefault") var jukeboxByDefault: Boolean, @@ -36,9 +37,9 @@ data class ServerSetting( @ColumnInfo(name = "podcastSupport") var podcastSupport: Boolean? = null ) { constructor() : this ( - -1, 0, "", "", "", "", false, false, false, null, null + -1, 0, "", "", null, "", "", false, false, false, null, null ) constructor(name: String, url: String) : this( - -1, 0, name, url, "", "", false, false, false, null, null + -1, 0, name, url, null, "", "", false, false, false, null, null ) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt index 916c93ca..f0128e62 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt @@ -6,13 +6,15 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.ImageView -import android.widget.TextView import androidx.core.content.ContextCompat -import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.textfield.TextInputLayout +import com.skydoves.colorpickerview.ColorPickerDialog +import com.skydoves.colorpickerview.flag.BubbleFlag +import com.skydoves.colorpickerview.flag.FlagMode +import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener import java.io.IOException import java.net.MalformedURLException import java.net.URL @@ -41,6 +43,8 @@ import org.moire.ultrasonic.util.Util import retrofit2.Response import timber.log.Timber +private const val DIALOG_PADDING = 12 + /** * Displays a form where server settings can be created / edited */ @@ -68,8 +72,6 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { private var currentColor: Int = 0 private var selectedColor: Int? = null - private var editServerColorText: TextView? = null - @Override override fun onCreate(savedInstanceState: Bundle?) { Util.applyTheme(this.context) @@ -97,7 +99,6 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { jukeboxSwitch = view.findViewById(R.id.edit_jukebox) saveButton = view.findViewById(R.id.edit_save) testButton = view.findViewById(R.id.edit_test) - editServerColorText = view.findViewById(R.id.edit_server_color_text) val index = arguments?.getInt( EDIT_SERVER_INTENT_INDEX, @@ -146,7 +147,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { } else { // Creating a new server FragmentTitle.setTitle(this, R.string.server_editor_new_label) - // updateColor(null) + updateColor(null) currentServerSetting = ServerSetting() saveButton!!.setOnClickListener { if (getFields()) { @@ -162,37 +163,33 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { } } -// serverColorImageView!!.setOnClickListener { -// val bubbleFlag = BubbleFlag(context) -// bubbleFlag.flagMode = FlagMode.LAST -// ColorPickerDialog.Builder(context).apply { -// this.colorPickerView.setInitialColor(currentColor) -// this.colorPickerView.flagView = bubbleFlag -// } -// .attachAlphaSlideBar(false) -// .setPositiveButton( -// getString(R.string.common_ok), -// ColorEnvelopeListener { envelope, _ -> -// selectedColor = envelope.color -// updateColor(envelope.color) -// } -// ) -// .setNegativeButton(getString(R.string.common_cancel)) { -// dialogInterface, _ -> -// dialogInterface.dismiss() -// } -// .setBottomSpace(DIALOG_PADDING) -// .show() -// } - - serverColorImageView?.isVisible = false - editServerColorText?.isVisible = false + serverColorImageView!!.setOnClickListener { + val bubbleFlag = BubbleFlag(context) + bubbleFlag.flagMode = FlagMode.LAST + ColorPickerDialog.Builder(context).apply { + this.colorPickerView.setInitialColor(currentColor) + this.colorPickerView.flagView = bubbleFlag + } + .attachAlphaSlideBar(false) + .setPositiveButton( + getString(R.string.common_ok), + ColorEnvelopeListener { envelope, _ -> + selectedColor = envelope.color + updateColor(envelope.color) + } + ) + .setNegativeButton(getString(R.string.common_cancel)) { + dialogInterface, _ -> + dialogInterface.dismiss() + } + .setBottomSpace(DIALOG_PADDING) + .show() + } } - @Suppress("unused") - private fun updateColor() { + private fun updateColor(color: Int?) { val image = ContextCompat.getDrawable(requireContext(), R.drawable.thumb_drawable) - currentColor = ServerColor.getBackgroundColor(requireContext(), null) + currentColor = ServerColor.getBackgroundColor(requireContext(), color) image?.setTint(currentColor) serverColorImageView?.background = image } @@ -257,7 +254,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { selfSignedSwitch!!.isChecked = savedInstanceState.getBoolean(::selfSignedSwitch.name) ldapSwitch!!.isChecked = savedInstanceState.getBoolean(::ldapSwitch.name) jukeboxSwitch!!.isChecked = savedInstanceState.getBoolean(::jukeboxSwitch.name) - // updateColor(savedInstanceState.getInt(::serverColorImageView.name)) + updateColor(savedInstanceState.getInt(::serverColorImageView.name)) if (savedInstanceState.containsKey(::selectedColor.name)) selectedColor = savedInstanceState.getInt(::selectedColor.name) isInstanceStateSaved = savedInstanceState.getBoolean(::isInstanceStateSaved.name) @@ -276,7 +273,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { selfSignedSwitch!!.isChecked = currentServerSetting!!.allowSelfSignedCertificate ldapSwitch!!.isChecked = currentServerSetting!!.ldapSupport jukeboxSwitch!!.isChecked = currentServerSetting!!.jukeboxByDefault - // updateColor(currentServerSetting!!.color) + updateColor(currentServerSetting!!.color) } /** @@ -325,7 +322,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { if (isValid) { currentServerSetting!!.name = serverNameEditText!!.editText?.text.toString() currentServerSetting!!.url = serverAddressEditText!!.editText?.text.toString() - // currentServerSetting!!.color = selectedColor + currentServerSetting!!.color = selectedColor currentServerSetting!!.userName = userNameEditText!!.editText?.text.toString() currentServerSetting!!.password = passwordEditText!!.editText?.text.toString() currentServerSetting!!.allowSelfSignedCertificate = selfSignedSwitch!!.isChecked @@ -459,11 +456,11 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { override fun done(responseString: String) { var dialogText = responseString if (arrayOf( - currentServerSetting!!.chatSupport, - currentServerSetting!!.bookmarkSupport, - currentServerSetting!!.shareSupport, - currentServerSetting!!.podcastSupport - ).any { x -> x == false } + currentServerSetting!!.chatSupport, + currentServerSetting!!.bookmarkSupport, + currentServerSetting!!.shareSupport, + currentServerSetting!!.podcastSupport + ).any { x -> x == false } ) { dialogText = String.format( Locale.ROOT, diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt index e1a145ae..02cfb4be 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt @@ -338,9 +338,9 @@ class PlayerFragment : registerForContextMenu(playlistView) if (arguments != null && requireArguments().getBoolean( - Constants.INTENT_SHUFFLE, - false - ) + Constants.INTENT_SHUFFLE, + false + ) ) { networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() mediaPlayerController.isShufflePlayEnabled = true diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ServerSettingsModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ServerSettingsModel.kt index 9261c6c2..2f520617 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ServerSettingsModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ServerSettingsModel.kt @@ -212,6 +212,7 @@ class ServerSettingsModel( serverId, settings.getString(PREFERENCES_KEY_SERVER_NAME + preferenceId, "")!!, url, + null, userName, settings.getString(PREFERENCES_KEY_PASSWORD + preferenceId, "")!!, settings.getBoolean(PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + preferenceId, false), diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt index a0e79d73..f6f34f7f 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt @@ -150,19 +150,19 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, @Throws(Exception::class) override fun getArtist(id: String, name: String?, refresh: Boolean): List { - checkSettingsChanged() - var cache = if (refresh) null else cachedArtist[id] - var dir = cache?.get() - if (dir == null) { - dir = musicService.getArtist(id, name, refresh) - cache = TimeLimitedCache( - Settings.directoryCacheTime.toLong(), TimeUnit.SECONDS - ) - cache.set(dir) - cachedArtist.put(id, cache) - } - return dir + checkSettingsChanged() + var cache = if (refresh) null else cachedArtist[id] + var dir = cache?.get() + if (dir == null) { + dir = musicService.getArtist(id, name, refresh) + cache = TimeLimitedCache( + Settings.directoryCacheTime.toLong(), TimeUnit.SECONDS + ) + cache.set(dir) + cachedArtist.put(id, cache) } + return dir + } @Throws(Exception::class) override fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt index 379b6d60..87288df8 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt @@ -149,9 +149,9 @@ class MediaPlayerLifecycleSupport : KoinComponent { } else if (state == 1) { if (!mediaPlayerController.isJukeboxEnabled && sp.getBoolean( - spKey, - false - ) && mediaPlayerController.playerState === PlayerState.PAUSED + spKey, + false + ) && mediaPlayerController.playerState === PlayerState.PAUSED ) { mediaPlayerController.start() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt index 3fc586dd..52276ded 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -456,8 +456,8 @@ class OfflineMusicService : MusicService, KoinComponent { @Throws(OfflineException::class) override fun getArtist(id: String, name: String?, refresh: Boolean): List { - throw OfflineException("getArtist isn't available in offline mode") - } + throw OfflineException("getArtist isn't available in offline mode") + } @Throws(OfflineException::class) override fun getAlbum(id: String, name: String?, refresh: Boolean): MusicDirectory { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/CommunicationError.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/CommunicationError.kt index 0bd4e0b9..725f095c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/CommunicationError.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/CommunicationError.kt @@ -30,13 +30,13 @@ import timber.log.Timber object CommunicationError { fun getHandler(context: Context?, handler: ((CoroutineContext, Throwable) -> Unit)? = null): CoroutineExceptionHandler { - return CoroutineExceptionHandler { coroutineContext, exception -> - Handler(Looper.getMainLooper()).post { - handleError(exception, context) - handler?.invoke(coroutineContext, exception) - } + return CoroutineExceptionHandler { coroutineContext, exception -> + Handler(Looper.getMainLooper()).post { + handleError(exception, context) + handler?.invoke(coroutineContext, exception) } } + } @JvmStatic fun handleError(error: Throwable?, context: Context?) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/LRUCache.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/LRUCache.kt new file mode 100644 index 00000000..22ab7f60 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/LRUCache.kt @@ -0,0 +1,79 @@ +/* + * LRUCache.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ +package org.moire.ultrasonic.util + +import java.lang.ref.SoftReference +import java.util.HashMap + +/** + * A cache that deletes the least-recently-used items. + */ +class LRUCache(capacity: Int) { + private val capacity: Int + private val map: MutableMap + + @Synchronized + operator fun get(key: K): V? { + val value = map[key] + var result: V? = null + if (value != null) { + value.updateTimestamp() + result = value.getValue() + } + return result + } + + @Synchronized + fun put(key: K, value: V) { + if (map.size >= capacity) { + removeOldest() + } + map[key] = TimestampedValue(value) + } + + fun clear() { + map.clear() + } + + private fun removeOldest() { + var oldestKey: K? = null + var oldestTimestamp = Long.MAX_VALUE + for ((key, value) in map) { + if (value.timestamp < oldestTimestamp) { + oldestTimestamp = value.timestamp + oldestKey = key + } + } + if (oldestKey != null) { + map.remove(oldestKey) + } + } + + private inner class TimestampedValue(value: V) { + private val value: SoftReference = SoftReference(value) + + var timestamp: Long = 0 + private set + + fun getValue(): V? { + return value.get() + } + + fun updateTimestamp() { + timestamp = System.currentTimeMillis() + } + + init { + updateTimestamp() + } + } + + init { + map = HashMap(capacity) + this.capacity = capacity + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt index d025563e..814a926c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt @@ -133,55 +133,55 @@ object Settings { @JvmStatic var shouldUseFolderForArtistName - by BooleanSetting(Constants.PREFERENCES_KEY_USE_FOLDER_FOR_ALBUM_ARTIST, false) + by BooleanSetting(Constants.PREFERENCES_KEY_USE_FOLDER_FOR_ALBUM_ARTIST, false) @JvmStatic var shouldShowTrackNumber - by BooleanSetting(Constants.PREFERENCES_KEY_SHOW_TRACK_NUMBER, false) + by BooleanSetting(Constants.PREFERENCES_KEY_SHOW_TRACK_NUMBER, false) @JvmStatic var defaultAlbums - by StringIntSetting(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS, "5") + by StringIntSetting(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS, "5") @JvmStatic var maxAlbums - by StringIntSetting(Constants.PREFERENCES_KEY_MAX_ALBUMS, "20") + by StringIntSetting(Constants.PREFERENCES_KEY_MAX_ALBUMS, "20") @JvmStatic var defaultSongs - by StringIntSetting(Constants.PREFERENCES_KEY_DEFAULT_SONGS, "10") + by StringIntSetting(Constants.PREFERENCES_KEY_DEFAULT_SONGS, "10") @JvmStatic var maxSongs - by StringIntSetting(Constants.PREFERENCES_KEY_MAX_SONGS, "25") + by StringIntSetting(Constants.PREFERENCES_KEY_MAX_SONGS, "25") @JvmStatic var maxArtists - by StringIntSetting(Constants.PREFERENCES_KEY_MAX_ARTISTS, "10") + by StringIntSetting(Constants.PREFERENCES_KEY_MAX_ARTISTS, "10") @JvmStatic var defaultArtists - by StringIntSetting(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS, "3") + by StringIntSetting(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS, "3") @JvmStatic var bufferLength - by StringIntSetting(Constants.PREFERENCES_KEY_BUFFER_LENGTH, "5") + by StringIntSetting(Constants.PREFERENCES_KEY_BUFFER_LENGTH, "5") @JvmStatic var incrementTime - by StringIntSetting(Constants.PREFERENCES_KEY_INCREMENT_TIME, "5") + by StringIntSetting(Constants.PREFERENCES_KEY_INCREMENT_TIME, "5") @JvmStatic var mediaButtonsEnabled - by BooleanSetting(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true) + by BooleanSetting(Constants.PREFERENCES_KEY_MEDIA_BUTTONS, true) @JvmStatic var showNowPlaying - by BooleanSetting(Constants.PREFERENCES_KEY_SHOW_NOW_PLAYING, true) + by BooleanSetting(Constants.PREFERENCES_KEY_SHOW_NOW_PLAYING, true) @JvmStatic var gaplessPlayback - by BooleanSetting(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK, false) + by BooleanSetting(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK, false) @JvmStatic var shouldTransitionOnPlayback by BooleanSetting( @@ -191,7 +191,7 @@ object Settings { @JvmStatic var shouldUseId3Tags - by BooleanSetting(Constants.PREFERENCES_KEY_ID3_TAGS, false) + by BooleanSetting(Constants.PREFERENCES_KEY_ID3_TAGS, false) @JvmStatic var tempLoss by StringIntSetting(Constants.PREFERENCES_KEY_TEMP_LOSS, "1") @@ -224,19 +224,19 @@ object Settings { ) var shouldClearPlaylist - by BooleanSetting(Constants.PREFERENCES_KEY_CLEAR_PLAYLIST, false) + by BooleanSetting(Constants.PREFERENCES_KEY_CLEAR_PLAYLIST, false) var shouldSortByDisc - by BooleanSetting(Constants.PREFERENCES_KEY_DISC_SORT, false) + by BooleanSetting(Constants.PREFERENCES_KEY_DISC_SORT, false) var shouldClearBookmark - by BooleanSetting(Constants.PREFERENCES_KEY_CLEAR_BOOKMARK, false) + by BooleanSetting(Constants.PREFERENCES_KEY_CLEAR_BOOKMARK, false) var singleButtonPlayPause - by BooleanSetting( - Constants.PREFERENCES_KEY_SINGLE_BUTTON_PLAY_PAUSE, - false - ) + by BooleanSetting( + Constants.PREFERENCES_KEY_SINGLE_BUTTON_PLAY_PAUSE, + false + ) // Inverted for readability var shouldSendBluetoothNotifications by BooleanSetting( @@ -245,20 +245,20 @@ object Settings { ) var shouldSendBluetoothAlbumArt - by BooleanSetting(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART, true) + by BooleanSetting(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART, true) var shouldDisableNowPlayingListSending - by BooleanSetting(Constants.PREFERENCES_KEY_DISABLE_SEND_NOW_PLAYING_LIST, false) + by BooleanSetting(Constants.PREFERENCES_KEY_DISABLE_SEND_NOW_PLAYING_LIST, false) @JvmStatic var viewRefreshInterval - by StringIntSetting(Constants.PREFERENCES_KEY_VIEW_REFRESH, "1000") + by StringIntSetting(Constants.PREFERENCES_KEY_VIEW_REFRESH, "1000") var shouldAskForShareDetails - by BooleanSetting(Constants.PREFERENCES_KEY_ASK_FOR_SHARE_DETAILS, true) + by BooleanSetting(Constants.PREFERENCES_KEY_ASK_FOR_SHARE_DETAILS, true) var defaultShareDescription - by StringSetting(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION, "") + by StringSetting(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION, "") @JvmStatic val shareGreeting: String? diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt index 89f6173d..a57b41c6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt @@ -708,6 +708,7 @@ object Util { return versionName } + @Suppress("DEPRECATION") fun getVersionCode(context: Context): Int { var versionCode = 0 val pm = context.packageManager diff --git a/ultrasonic/src/main/res/values-de/strings.xml b/ultrasonic/src/main/res/values-de/strings.xml index ba429dd3..efb96fa7 100644 --- a/ultrasonic/src/main/res/values-de/strings.xml +++ b/ultrasonic/src/main/res/values-de/strings.xml @@ -29,14 +29,14 @@ Nachricht senden Album Ultrasonic - Künstler*in + Künstler Abbrechen Kommentar Bestätigen Löschen Herunterladen Details - Mehrere Genre + Mehrere Genres Name OK Anheften @@ -44,15 +44,14 @@ Abspielen Zuletzt spielen Als nächstes spielen - Vorheriges abspielen - + Vorheriges abspielen Jetzt spielen Zufällig spielen Öffentlich Speichern Titel Lösen - Verschiedene Künstler*innen + Verschiedene Künstler Möchtest du %1$s löschen Lesezeichen entfernt Lesezeichen gesetzt als %s. @@ -94,7 +93,7 @@ file:///android_asset/html/de/index.html Jukebox als Standard Keine Liedtexte gefunden - Nach Künstler*innen + Nach Künstler Nach Namen Am häufigsten gespielt Am besten bewertet @@ -103,10 +102,11 @@ Kürzlich gespielt Mit Stern Alben - Künstler*innen + Künstler Genres Musik Offline + %s - Server einrichten Gemischte Wiedergabe Zufällig Mit Stern @@ -125,7 +125,7 @@ Medienbibliothek Offline Medien Netzwerkfehler. Neuer Versuch %1$d von %2$d. - %d Künstler*in gefunden + %d Künstler gefunden Lese vom Server. Lese vom Server. Fertig! Wiedergabelisten @@ -134,7 +134,7 @@ Aktualisierung der Wiedergabeliste %s ist fehlgeschlagen Bitte warten… Alben - Künstler*innen + Künstler Suche Zeige mehr Keine Treffer, bitte erneut versuchen @@ -149,7 +149,7 @@ Ordner wählen Keine Genres gefunden Keine Wiedergabelisten auf dem Server - Kontaktierse Server, bitte warten. + Kontaktiere Server, bitte warten. Aussehen Puffer-Länge Deaktiviert @@ -218,7 +218,7 @@ Bitte eine gültige URL angeben. Bitte einen gültigen Benutzernamen eingeben (ohne führende Leerzeichen). Maximale Alben - Maximale Künstler*innen + Max Künstler 112 Kbps 128 Kbps 160 Kbps @@ -229,12 +229,12 @@ 64 Kbps 80 Kbps 96 Kbps - Maximale Bitragte - Mobil + Max Bitrate - Mobil Unbegrenzt - Maximale Bitrate - WLAN + Max Bitrate - WLAN Maximale Titel Auf Telefon, Headset und Bluetooth-Media-Tasten reagieren - Media Tasten + Medien Tasten Netzwerk Zeitüberschreitung 105 Sekunden 120 Sekunden @@ -257,6 +257,7 @@ Unbegrenzt Fortsetzen mit Kopfhörer Die App setzt eine pausierte Wiedergabe beim Anschließen der Kopfhörer fort. + Gespielte Musik scrobbeln 1 10 100 @@ -301,6 +302,7 @@ Verbindung OK, Server nicht lizensiert. Hell Dunkel + Schwarz Thema Selbst-signierte HTTPS Zertifikate erlauben LDAP Benutzeranmeldung aktivieren @@ -310,6 +312,8 @@ Durchsuchen von ID3-Tags Nutze ID3 Tag Methode anstatt Dateisystem-Methode Film + Medien nur über gebührenfreie Verbindungen herunterladen + Nur über WLAN herunterladen %1$s%2$s %d kbps 0 B @@ -322,11 +326,13 @@ SD Karte nicht verfügbar Keine SD Karte Standard Beschreibung einer Freigabe - Teilen + Freigaben Immer nach Details fragen Standard Ablaufzeit Dialog nicht wieder anzeigen - Ferigabeoptionen + Freigabeoptionen + Freigabe auf Server erstellen + Teilen erzeugt eine Freigabe auf dem Server und sendet die URL. Wenn ausgeschaltet, werden nur die Lied-Details geteilt. Kein Ablaufdatum Wiedergabeliste umschalten Lesezeichen setzen @@ -350,13 +356,13 @@ %s wurde von der Wiedergabeliste entfernt Wiedergabeliste teilen Aktuelles Lied teilen - Standard Begrüßung beim Teilen + Standard Freigabe-Begrüßung Hör dir mal die Musik an, die ich mit dir über %s geteilt habe. Titel teilen über Freigabe - Alle Titel nach Künstler*innen sortieren + Alle Titel nach Künstler sortieren Einen neuen Eintrag in der Künstleransicht hinzufügen, um auf alle Lieder eines Künstlers zuzugreifen - Künstler*in zeigen + Künstler zeigen Mehrere Jahre Wiedergabe fortsetzen, wenn ein Bluetooth Gerät verbunden wurde Wiedergabe pausieren, wenn ein Bluetooth Gerät getrennt wurde @@ -379,4 +385,9 @@ Inkompatible Versionen. Bitte die Ultrasonic App aktualisieren. Inkompatible Versionen. Bitte den subsonic Server aktualisieren. - + + Besonderheiten + Fünf-Stern Bewertung + Benutze Bewertungssystem mit fünf Sternen anstatt Lieder mit bloß einem Stern zu markieren. + +