Merge branch 'develop' into detailed-media-info

This commit is contained in:
tzugen 2022-03-23 11:48:04 +01:00 committed by GitHub
commit d05ac1489e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 170 additions and 51 deletions

View File

@ -2,7 +2,7 @@ version: 3
jobs: jobs:
build: build:
docker: docker:
- image: circleci/android:api-30 - image: cimg/android:2022.03.1
working_directory: ~/ultrasonic working_directory: ~/ultrasonic
environment: environment:
JVM_OPTS: -Xmx3200m JVM_OPTS: -Xmx3200m
@ -55,7 +55,7 @@ jobs:
path: build/reports/jacoco/jacocoFullReport/ path: build/reports/jacoco/jacocoFullReport/
push_translations: push_translations:
docker: docker:
- image: circleci/python:3.6 - image: cimg/python:3.6
working_directory: ~/ultrasonic working_directory: ~/ultrasonic
steps: steps:
- checkout - checkout
@ -75,7 +75,7 @@ jobs:
tx push -s tx push -s
generate_signed_apk: generate_signed_apk:
docker: docker:
- image: circleci/android:api-30 - image: cimg/android:2022.03.1
working_directory: ~/ultrasonic working_directory: ~/ultrasonic
steps: steps:
- checkout - checkout
@ -104,7 +104,7 @@ jobs:
- ultrasonic-*.apk* - ultrasonic-*.apk*
publish_github_signed_apk: publish_github_signed_apk:
docker: docker:
- image: circleci/golang - image: cimg/golang
steps: steps:
- attach_workspace: - attach_workspace:
at: /tmp/ultrasonic-release at: /tmp/ultrasonic-release

View File

@ -8,7 +8,8 @@ import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import okio.Okio import okio.buffer
import okio.source
import org.amshove.kluent.`should be` import org.amshove.kluent.`should be`
import org.amshove.kluent.`should contain` import org.amshove.kluent.`should contain`
import org.amshove.kluent.`should not be` import org.amshove.kluent.`should not be`
@ -40,12 +41,12 @@ fun MockWebServer.enqueueResponse(resourceName: String) {
} }
fun Any.loadJsonResponse(name: String): String { fun Any.loadJsonResponse(name: String): String {
val source = Okio.buffer(Okio.source(javaClass.classLoader.getResourceAsStream(name))) val source = javaClass.classLoader.getResourceAsStream(name)!!.source().buffer()
return source.readString(Charset.forName("UTF-8")) return source.readString(Charset.forName("UTF-8"))
} }
fun Any.loadResourceStream(name: String): InputStream { fun Any.loadResourceStream(name: String): InputStream {
val source = Okio.buffer(Okio.source(javaClass.classLoader.getResourceAsStream(name))) val source = javaClass.classLoader.getResourceAsStream(name)!!.source().buffer()
return source.inputStream() return source.inputStream()
} }

View File

@ -17,8 +17,8 @@ fun Response<out ResponseBody>.toStreamResponse(): StreamResponse {
val contentType = responseBody?.contentType() val contentType = responseBody?.contentType()
if ( if (
contentType != null && contentType != null &&
contentType.type().equals("application", true) && contentType.type.equals("application", true) &&
contentType.subtype().equals("json", true) contentType.subtype.equals("json", true)
) { ) {
val error = SubsonicAPIClient.jacksonMapper.readValue<SubsonicResponse>( val error = SubsonicAPIClient.jacksonMapper.readValue<SubsonicResponse>(
responseBody.byteStream() responseBody.byteStream()
@ -40,7 +40,7 @@ fun Response<out ResponseBody>.toStreamResponse(): StreamResponse {
* It creates Exceptions from the results returned by the Subsonic API * It creates Exceptions from the results returned by the Subsonic API
*/ */
@Suppress("ThrowsCount") @Suppress("ThrowsCount")
fun <T : SubsonicResponse> Response<out T>.throwOnFailure(): Response<out T> { fun <T : SubsonicResponse> Response<T>.throwOnFailure(): Response<T> {
val response = this val response = this
if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) { if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) {

View File

@ -68,7 +68,7 @@ class SubsonicAPIClient(
.addInterceptor { chain -> .addInterceptor { chain ->
// Adds default request params // Adds default request params
val originalRequest = chain.request() val originalRequest = chain.request()
val newUrl = originalRequest.url().newBuilder() val newUrl = originalRequest.url.newBuilder()
.addQueryParameter("u", config.username) .addQueryParameter("u", config.username)
.addQueryParameter("c", config.clientID) .addQueryParameter("c", config.clientID)
.addQueryParameter("f", "json") .addQueryParameter("f", "json")

View File

@ -18,7 +18,7 @@ class PasswordHexInterceptor(private val password: String) : Interceptor {
override fun intercept(chain: Chain): Response { override fun intercept(chain: Chain): Response {
val originalRequest = chain.request() val originalRequest = chain.request()
val updatedUrl = originalRequest.url().newBuilder() val updatedUrl = originalRequest.url.newBuilder()
.addEncodedQueryParameter("p", passwordHex).build() .addEncodedQueryParameter("p", passwordHex).build()
return chain.proceed(originalRequest.newBuilder().url(updatedUrl).build()) return chain.proceed(originalRequest.newBuilder().url(updatedUrl).build())
} }

View File

@ -21,7 +21,7 @@ class PasswordMD5Interceptor(private val password: String) : Interceptor {
override fun intercept(chain: Chain): Response { override fun intercept(chain: Chain): Response {
val originalRequest = chain.request() val originalRequest = chain.request()
val salt = getSalt() val salt = getSalt()
val updatedUrl = originalRequest.url().newBuilder() val updatedUrl = originalRequest.url.newBuilder()
.addQueryParameter("t", getPasswordMD5Hash(salt)) .addQueryParameter("t", getPasswordMD5Hash(salt))
.addQueryParameter("s", salt) .addQueryParameter("s", salt)
.build() .build()

View File

@ -19,7 +19,7 @@ internal const val TIMEOUT_MILLIS_PER_OFFSET_BYTE = 0.02
internal class RangeHeaderInterceptor : Interceptor { internal class RangeHeaderInterceptor : Interceptor {
override fun intercept(chain: Chain): Response { override fun intercept(chain: Chain): Response {
val originalRequest = chain.request() val originalRequest = chain.request()
val headers = originalRequest.headers() val headers = originalRequest.headers
return if (headers.names().contains("Range")) { return if (headers.names().contains("Range")) {
val offsetValue = headers["Range"] ?: "0" val offsetValue = headers["Range"] ?: "0"
val offset = "bytes=$offsetValue-" val offset = "bytes=$offsetValue-"

View File

@ -18,7 +18,7 @@ internal class VersionInterceptor(
val newRequest = originalRequest.newBuilder() val newRequest = originalRequest.newBuilder()
.url( .url(
originalRequest originalRequest
.url() .url
.newBuilder() .newBuilder()
.addQueryParameter("v", protocolVersion.restApiVersion) .addQueryParameter("v", protocolVersion.restApiVersion)
.build() .build()

View File

@ -22,9 +22,9 @@ kotlin = "1.6.10"
kotlinxCoroutines = "1.6.0-native-mt" kotlinxCoroutines = "1.6.0-native-mt"
viewModelKtx = "2.3.0" viewModelKtx = "2.3.0"
retrofit = "2.6.4" retrofit = "2.9.0"
jackson = "2.9.5" jackson = "2.10.1"
okhttp = "3.12.13" okhttp = "4.9.1"
koin = "3.0.2" koin = "3.0.2"
picasso = "2.71828" picasso = "2.71828"

View File

@ -15,9 +15,10 @@ android {
minSdkVersion versions.minSdk minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
resConfigs 'cs', 'de', 'en', 'es', 'fr', 'hu', 'it', 'nl', 'pl', 'pt', 'pt-rBR', 'ru', 'zh-rCN', 'zh-rTW' resConfigs 'cs', 'de', 'en', 'es', 'fr', 'hu', 'it', 'nl', 'pl', 'pt', 'pt-rBR', 'ru', 'zh-rCN', 'zh-rTW'
} }
bundle.language.enableSplit = false
buildTypes { buildTypes {
release { release {
minifyEnabled true minifyEnabled true

View File

@ -7,6 +7,8 @@
package org.moire.ultrasonic.activity package org.moire.ultrasonic.activity
import android.app.SearchManager import android.app.SearchManager
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.Resources import android.content.res.Resources
@ -53,6 +55,7 @@ import org.moire.ultrasonic.service.RxBus
import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.InfoDialog import org.moire.ultrasonic.util.InfoDialog
import org.moire.ultrasonic.util.LocaleHelper
import org.moire.ultrasonic.util.ServerColor import org.moire.ultrasonic.util.ServerColor
import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Storage import org.moire.ultrasonic.util.Storage
@ -351,6 +354,15 @@ class NavigationActivity : AppCompatActivity() {
} }
} }
/**
* Apply the customized language settings if needed
*/
override fun attachBaseContext(newBase: Context?) {
val locale = Settings.overrideLanguage
val localeUpdatedContext: ContextWrapper = LocaleHelper.wrap(newBase, locale)
super.attachBaseContext(localeUpdatedContext)
}
private fun loadSettings() { private fun loadSettings() {
PreferenceManager.setDefaultValues(this, R.xml.settings, false) PreferenceManager.setDefaultValues(this, R.xml.settings, false)
} }

View File

@ -4,7 +4,7 @@ import com.squareup.picasso.Picasso
import com.squareup.picasso.Request import com.squareup.picasso.Request
import com.squareup.picasso.RequestHandler import com.squareup.picasso.RequestHandler
import java.io.IOException import java.io.IOException
import okio.Okio import okio.source
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
/** /**
@ -29,7 +29,7 @@ class AvatarRequestHandler(
if (response.hasError() || response.stream == null) { if (response.hasError() || response.stream == null) {
throw IOException("${response.apiError}") throw IOException("${response.apiError}")
} else { } else {
return Result(Okio.source(response.stream!!), Picasso.LoadedFrom.NETWORK) return Result(response.stream!!.source(), Picasso.LoadedFrom.NETWORK)
} }
} }
} }

View File

@ -5,7 +5,7 @@ import com.squareup.picasso.Picasso.LoadedFrom.NETWORK
import com.squareup.picasso.Request import com.squareup.picasso.Request
import com.squareup.picasso.RequestHandler import com.squareup.picasso.RequestHandler
import java.io.IOException import java.io.IOException
import okio.Okio import okio.source
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.toStreamResponse import org.moire.ultrasonic.api.subsonic.toStreamResponse
import org.moire.ultrasonic.util.FileUtil.SUFFIX_LARGE import org.moire.ultrasonic.util.FileUtil.SUFFIX_LARGE
@ -44,7 +44,7 @@ class CoverArtRequestHandler(private val client: SubsonicAPIClient) : RequestHan
// Handle the response // Handle the response
if (!response.hasError() && response.stream != null) { if (!response.hasError() && response.stream != null) {
return Result(Okio.source(response.stream!!), NETWORK) return Result(response.stream!!.source(), NETWORK)
} }
// Throw an error if still not successful // Throw an error if still not successful

View File

@ -10,12 +10,11 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import okhttp3.Protocol import okhttp3.Protocol
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.models.AlbumListType.Companion.fromName import org.moire.ultrasonic.api.subsonic.models.AlbumListType.Companion.fromName
import org.moire.ultrasonic.api.subsonic.models.JukeboxAction import org.moire.ultrasonic.api.subsonic.models.JukeboxAction
import org.moire.ultrasonic.api.subsonic.response.StreamResponse
import org.moire.ultrasonic.api.subsonic.throwOnFailure import org.moire.ultrasonic.api.subsonic.throwOnFailure
import org.moire.ultrasonic.api.subsonic.toStreamResponse import org.moire.ultrasonic.api.subsonic.toStreamResponse
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
@ -425,14 +424,13 @@ open class RESTMusicService(
save: Boolean save: Boolean
): Pair<InputStream, Boolean> { ): Pair<InputStream, Boolean> {
val songOffset = if (offset < 0) 0 else offset val songOffset = if (offset < 0) 0 else offset
lateinit var response: StreamResponse
// Use semantically correct call // Use semantically correct call
if (save) { val response = if (save) {
response = API.download(song.id, maxBitrate, offset = songOffset) API.download(song.id, maxBitrate, offset = songOffset)
.execute().toStreamResponse() .execute().toStreamResponse()
} else { } else {
response = API.stream(song.id, maxBitrate, offset = songOffset) API.stream(song.id, maxBitrate, offset = songOffset)
.execute().toStreamResponse() .execute().toStreamResponse()
} }
@ -463,7 +461,7 @@ open class RESTMusicService(
// Returns a dummy response // Returns a dummy response
Response.Builder() Response.Builder()
.code(100) .code(100)
.body(ResponseBody.create(null, "")) .body("".toResponseBody(null))
.protocol(Protocol.HTTP_2) .protocol(Protocol.HTTP_2)
.message("Empty response") .message("Empty response")
.request(chain.request()) .request(chain.request())
@ -480,7 +478,7 @@ open class RESTMusicService(
val response = client.newCall(request).execute() val response = client.newCall(request).execute()
// The complete url :) // The complete url :)
val url = response.request().url() val url = response.request.url
return url.toString() return url.toString()
} }

View File

@ -116,6 +116,7 @@ object Constants {
const val PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE = "pauseOnBluetoothDevice" const val PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE = "pauseOnBluetoothDevice"
const val PREFERENCES_KEY_SINGLE_BUTTON_PLAY_PAUSE = "singleButtonPlayPause" const val PREFERENCES_KEY_SINGLE_BUTTON_PLAY_PAUSE = "singleButtonPlayPause"
const val PREFERENCES_KEY_DEBUG_LOG_TO_FILE = "debugLogToFile" const val PREFERENCES_KEY_DEBUG_LOG_TO_FILE = "debugLogToFile"
const val PREFERENCES_KEY_OVERRIDE_LANGUAGE = "overrideLanguage"
const val PREFERENCE_VALUE_ALL = 0 const val PREFERENCE_VALUE_ALL = 0
const val PREFERENCE_VALUE_A2DP = 1 const val PREFERENCE_VALUE_A2DP = 1
const val PREFERENCE_VALUE_DISABLED = 2 const val PREFERENCE_VALUE_DISABLED = 2

View File

@ -0,0 +1,50 @@
/*
* LocaleHelper.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.util
import android.annotation.TargetApi
import android.content.Context
import android.content.ContextWrapper
import android.content.res.Configuration
import android.os.Build
import java.util.Locale
/**
* Simple Helper class to "wrap" a context with a new locale.
*/
class LocaleHelper(base: Context?) : ContextWrapper(base) {
companion object {
fun wrap(ctx: Context?, language: String): ContextWrapper {
var context = ctx
if (context != null && language != "") {
val config = context.resources.configuration
val locale = Locale.forLanguageTag(language)
Locale.setDefault(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSystemLocale(config, locale)
} else {
setSystemLocaleLegacy(config, locale)
}
config.setLayoutDirection(locale)
context = context.createConfigurationContext(config)
}
return LocaleHelper(context)
}
@Suppress("DEPRECATION")
private fun setSystemLocaleLegacy(config: Configuration, locale: Locale?) {
config.locale = locale
}
@TargetApi(Build.VERSION_CODES.N)
fun setSystemLocale(config: Configuration, locale: Locale?) {
config.setLocale(locale)
}
}
}

View File

@ -312,12 +312,16 @@ object Settings {
Constants.PREFERENCE_VALUE_A2DP Constants.PREFERENCE_VALUE_A2DP
) )
@JvmStatic
var debugLogToFile by BooleanSetting(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE, false) var debugLogToFile by BooleanSetting(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE, false)
@JvmStatic @JvmStatic
val preferences: SharedPreferences val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(Util.appContext()) get() = PreferenceManager.getDefaultSharedPreferences(Util.appContext())
@JvmStatic
val overrideLanguage by StringSetting(Constants.PREFERENCES_KEY_OVERRIDE_LANGUAGE, "")
var useFiveStarRating by BooleanSetting(Constants.PREFERENCES_KEY_USE_FIVE_STAR_RATING, false) var useFiveStarRating by BooleanSetting(Constants.PREFERENCES_KEY_USE_FIVE_STAR_RATING, false)
// TODO: Remove in December 2022 // TODO: Remove in December 2022

View File

@ -398,7 +398,6 @@
<string name="api.subsonic.upgrade_server">Inkompatible Versionen. Bitte den subsonic Server aktualisieren.</string> <string name="api.subsonic.upgrade_server">Inkompatible Versionen. Bitte den subsonic Server aktualisieren.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.features_title">Besonderheiten</string>
<string name="settings.five_star_rating_title">Fünf-Stern Bewertung</string> <string name="settings.five_star_rating_title">Fünf-Stern Bewertung</string>
<string name="settings.five_star_rating_description">Benutze Bewertungssystem mit fünf Sternen anstatt Lieder mit bloß einem Stern zu markieren.</string> <string name="settings.five_star_rating_description">Benutze Bewertungssystem mit fünf Sternen anstatt Lieder mit bloß einem Stern zu markieren.</string>

View File

@ -460,7 +460,6 @@
<string name="api.subsonic.upgrade_server">Versiones incompatibles. Por favor actualiza el servidor de Subsonic.</string> <string name="api.subsonic.upgrade_server">Versiones incompatibles. Por favor actualiza el servidor de Subsonic.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.features_title">Características</string>
<string name="settings.five_star_rating_title">Use cinco estrellas para las canciones</string> <string name="settings.five_star_rating_title">Use cinco estrellas para las canciones</string>
<string name="settings.five_star_rating_description">Utilice el sistema de calificación de cinco estrellas para canciones <string name="settings.five_star_rating_description">Utilice el sistema de calificación de cinco estrellas para canciones
en lugar de simplemente marcar / desmarcar elementos. en lugar de simplemente marcar / desmarcar elementos.

View File

@ -460,7 +460,6 @@
<string name="api.subsonic.upgrade_server">Incompatibele versies. Werk je Subsonic-server bij.</string> <string name="api.subsonic.upgrade_server">Incompatibele versies. Werk je Subsonic-server bij.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.features_title">Functies</string>
<string name="settings.five_star_rating_title">Vijf sterren gebruiken voor nummers</string> <string name="settings.five_star_rating_title">Vijf sterren gebruiken voor nummers</string>
<string name="settings.five_star_rating_description">Toon vijf sterren om nummers te beoordelen <string name="settings.five_star_rating_description">Toon vijf sterren om nummers te beoordelen
in plaats van items toe te voegen aan of te verwijderen uit de favorieten. in plaats van items toe te voegen aan of te verwijderen uit de favorieten.

View File

@ -443,7 +443,6 @@
<string name="api.subsonic.upgrade_server">不兼容的版本。请升级Subsonic 服务。</string> <string name="api.subsonic.upgrade_server">不兼容的版本。请升级Subsonic 服务。</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.features_title">特性</string>
<string name="settings.five_star_rating_title">为歌曲使用五星评分</string> <string name="settings.five_star_rating_title">为歌曲使用五星评分</string>
<string name="settings.five_star_rating_description">对歌曲使用五星级评级系统 <string name="settings.five_star_rating_description">对歌曲使用五星级评级系统
而不是简单地为项目加星标/取消星标。</string> 而不是简单地为项目加星标/取消星标。</string>

View File

@ -236,5 +236,40 @@
<item>@string/settings.playback.bluetooth_a2dp</item> <item>@string/settings.playback.bluetooth_a2dp</item>
<item>@string/settings.playback.bluetooth_disabled</item> <item>@string/settings.playback.bluetooth_disabled</item>
</string-array> </string-array>
<string-array name="languageNames" translatable="false">
<item>@string/language.default</item>
<item>@string/language.zh_CN</item>
<item>@string/language.zh_TW</item>
<item>@string/language.cs</item>
<item>@string/language.nl</item>
<item>@string/language.en</item>
<item>@string/language.fr</item>
<item>@string/language.de</item>
<item>@string/language.hu</item>
<item>@string/language.it</item>
<item>@string/language.es</item>
<item>@string/language.pl</item>
<item>@string/language.pt</item>
<item>@string/language.pt_BR</item>
<item>@string/language.ru</item>
</string-array>
<string-array name="languageValues" translatable="false">
<!--suppress CheckTagEmptyBody -->
<item></item>
<item>zh-CN</item>
<item>zh-TW</item>
<item>cs</item>
<item>nl</item>
<item>en</item>
<item>fr</item>
<item>de</item>
<item>hu</item>
<item>it</item>
<item>es</item>
<item>pl</item>
<item>pt</item>
<item>pt-BR</item>
<item>ru</item>
</string-array>
</resources> </resources>

View File

@ -96,6 +96,21 @@
<string name="error.label">Error</string> <string name="error.label">Error</string>
<string name="jukebox.is_default">Jukebox By Default</string> <string name="jukebox.is_default">Jukebox By Default</string>
<string name="lyrics.nomatch">No lyrics found</string> <string name="lyrics.nomatch">No lyrics found</string>
<string name="language.default">System default</string>
<string name="language.zh_CN">Chinese (China)</string>
<string name="language.zh_TW">Chinese (Taiwan)</string>
<string name="language.cs">Czech</string>
<string name="language.nl">Dutch</string>
<string name="language.en">English</string>
<string name="language.fr">French</string>
<string name="language.de">German</string>
<string name="language.hu">Hungarian</string>
<string name="language.it">Italian</string>
<string name="language.es">Spanish</string>
<string name="language.pl">Polish</string>
<string name="language.pt">Portuguese</string>
<string name="language.pt_BR">Portuguese (Brazil)</string>
<string name="language.ru">Russian</string>
<string name="main.albums_alphaByArtist">By Artist</string> <string name="main.albums_alphaByArtist">By Artist</string>
<string name="main.albums_alphaByName">By Name</string> <string name="main.albums_alphaByName">By Name</string>
<string name="main.albums_frequent">Most Played</string> <string name="main.albums_frequent">Most Played</string>
@ -148,7 +163,7 @@
<string name="search.title">Search</string> <string name="search.title">Search</string>
<string name="select_album.empty">No media found</string> <string name="select_album.empty">No media found</string>
<string name="select_album.n_selected">%d tracks selected</string> <string name="select_album.n_selected">%d tracks selected</string>
<string name="select_album.no_network">Warning: No network available.</string> <string name="select_album.no_network">Warning: No usable network available.\n If you are using mobile data, you may need to allow downloads on metered connections in the settings.</string>
<string name="select_album.no_sdcard">Error: No SD card available.</string> <string name="select_album.no_sdcard">Error: No SD card available.</string>
<string name="select_album.play_all">Play All</string> <string name="select_album.play_all">Play All</string>
<string name="select_artist.all_folders">All Folders</string> <string name="select_artist.all_folders">All Folders</string>
@ -217,8 +232,8 @@
<string name="settings.disc_sort_summary">Sort song list by disc number and track number</string> <string name="settings.disc_sort_summary">Sort song list by disc number and track number</string>
<string name="settings.display_bitrate">Display Bitrate and File Suffix</string> <string name="settings.display_bitrate">Display Bitrate and File Suffix</string>
<string name="settings.display_bitrate_summary">Append artist name with bitrate and file suffix</string> <string name="settings.display_bitrate_summary">Append artist name with bitrate and file suffix</string>
<string name="settings.download_transition">Stay in Downloads on Play</string> <string name="settings.download_transition">Show Now Playing on Play</string>
<string name="settings.download_transition_summary">Stay in media view when starting playback (do not switch to player view)</string> <string name="settings.download_transition_summary">Switch to Now Playing after starting playback in media view</string>
<string name="settings.gapless_playback">Gapless Playback</string> <string name="settings.gapless_playback">Gapless Playback</string>
<string name="settings.gapless_playback_summary">Enable gapless playback</string> <string name="settings.gapless_playback_summary">Enable gapless playback</string>
<string name="settings.hide_media_summary">Hide music files from other apps.</string> <string name="settings.hide_media_summary">Hide music files from other apps.</string>
@ -257,6 +272,8 @@
<string name="settings.notifications_title">Notifications</string> <string name="settings.notifications_title">Notifications</string>
<string name="settings.network_title">Network</string> <string name="settings.network_title">Network</string>
<string name="settings.other_title">Other Settings</string> <string name="settings.other_title">Other Settings</string>
<string name="settings.override_language">Override the language</string>
<string name="settings.override_language_summary">You need to restart the app after changing the language</string>
<string name="settings.playback_control_title">Playback Control Settings</string> <string name="settings.playback_control_title">Playback Control Settings</string>
<string name="settings.preload">Songs To Preload</string> <string name="settings.preload">Songs To Preload</string>
<string name="settings.preload_1">1 song</string> <string name="settings.preload_1">1 song</string>
@ -468,7 +485,6 @@
<string name="api.subsonic.upgrade_server">Incompatible versions. Please upgrade Subsonic server.</string> <string name="api.subsonic.upgrade_server">Incompatible versions. Please upgrade Subsonic server.</string>
<!-- Subsonic features --> <!-- Subsonic features -->
<string name="settings.features_title">Features</string>
<string name="settings.five_star_rating_title">Use five star rating for songs</string> <string name="settings.five_star_rating_title">Use five star rating for songs</string>
<string name="settings.five_star_rating_description">Use five star rating system for songs instead of simply starring/unstarring items.</string> <string name="settings.five_star_rating_description">Use five star rating system for songs instead of simply starring/unstarring items.</string>

View File

@ -128,6 +128,12 @@
a:summary="@string/settings.playback.single_button_bluetooth_device_summary" a:summary="@string/settings.playback.single_button_bluetooth_device_summary"
a:title="@string/settings.playback.single_button_bluetooth_device" a:title="@string/settings.playback.single_button_bluetooth_device"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"/>
<CheckBoxPreference
a:defaultValue="false"
a:key="use_five_star_rating"
a:summary="@string/settings.five_star_rating_description"
a:title="@string/settings.five_star_rating_title"
app:iconSpaceReserved="false" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
a:title="@string/settings.notifications_title" a:title="@string/settings.notifications_title"
@ -349,16 +355,14 @@
a:summary="@string/settings.hide_media_summary" a:summary="@string/settings.hide_media_summary"
a:title="@string/settings.hide_media_title" a:title="@string/settings.hide_media_title"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"/>
</PreferenceCategory> <ListPreference
<PreferenceCategory a:defaultValue=""
a:title="@string/settings.features_title" a:entries="@array/languageNames"
app:iconSpaceReserved="false"> a:entryValues="@array/languageValues"
<CheckBoxPreference a:key="overrideLanguage"
a:defaultValue="false" a:title="@string/settings.override_language"
a:key="use_five_star_rating" a:summary="@string/settings.override_language_summary"
a:summary="@string/settings.five_star_rating_description" app:iconSpaceReserved="false"/>
a:title="@string/settings.five_star_rating_title"
app:iconSpaceReserved="false" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
a:title="@string/settings.debug.title" a:title="@string/settings.debug.title"

View File

@ -1,9 +1,10 @@
package org.moire.ultrasonic.imageloader package org.moire.ultrasonic.imageloader
import java.io.InputStream import java.io.InputStream
import okio.Okio import okio.buffer
import okio.source
fun Any.loadResourceStream(name: String): InputStream { fun Any.loadResourceStream(name: String): InputStream {
val source = Okio.buffer(Okio.source(javaClass.classLoader!!.getResourceAsStream(name))) val source = javaClass.classLoader!!.getResourceAsStream(name).source().buffer()
return source.inputStream() return source.inputStream()
} }