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

View File

@ -8,7 +8,8 @@ import java.util.Locale
import java.util.TimeZone
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okio.Okio
import okio.buffer
import okio.source
import org.amshove.kluent.`should be`
import org.amshove.kluent.`should contain`
import org.amshove.kluent.`should not be`
@ -40,12 +41,12 @@ fun MockWebServer.enqueueResponse(resourceName: 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"))
}
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()
}

View File

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

View File

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

View File

@ -18,7 +18,7 @@ class PasswordHexInterceptor(private val password: String) : Interceptor {
override fun intercept(chain: Chain): Response {
val originalRequest = chain.request()
val updatedUrl = originalRequest.url().newBuilder()
val updatedUrl = originalRequest.url.newBuilder()
.addEncodedQueryParameter("p", passwordHex).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 {
val originalRequest = chain.request()
val salt = getSalt()
val updatedUrl = originalRequest.url().newBuilder()
val updatedUrl = originalRequest.url.newBuilder()
.addQueryParameter("t", getPasswordMD5Hash(salt))
.addQueryParameter("s", salt)
.build()

View File

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

View File

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

View File

@ -22,9 +22,9 @@ 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"
retrofit = "2.9.0"
jackson = "2.10.1"
okhttp = "4.9.1"
koin = "3.0.2"
picasso = "2.71828"

View File

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

View File

@ -7,6 +7,8 @@
package org.moire.ultrasonic.activity
import android.app.SearchManager
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.res.ColorStateList
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.util.Constants
import org.moire.ultrasonic.util.InfoDialog
import org.moire.ultrasonic.util.LocaleHelper
import org.moire.ultrasonic.util.ServerColor
import org.moire.ultrasonic.util.Settings
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() {
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.RequestHandler
import java.io.IOException
import okio.Okio
import okio.source
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
/**
@ -29,7 +29,7 @@ class AvatarRequestHandler(
if (response.hasError() || response.stream == null) {
throw IOException("${response.apiError}")
} 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.RequestHandler
import java.io.IOException
import okio.Okio
import okio.source
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.toStreamResponse
import org.moire.ultrasonic.util.FileUtil.SUFFIX_LARGE
@ -44,7 +44,7 @@ class CoverArtRequestHandler(private val client: SubsonicAPIClient) : RequestHan
// Handle the response
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

View File

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

View File

@ -116,6 +116,7 @@ object Constants {
const val PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE = "pauseOnBluetoothDevice"
const val PREFERENCES_KEY_SINGLE_BUTTON_PLAY_PAUSE = "singleButtonPlayPause"
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_A2DP = 1
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
)
@JvmStatic
var debugLogToFile by BooleanSetting(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE, false)
@JvmStatic
val preferences: SharedPreferences
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)
// 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>
<!-- 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_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>
<!-- 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_description">Utilice el sistema de calificación de cinco estrellas para canciones
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>
<!-- 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_description">Toon vijf sterren om nummers te beoordelen
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>
<!-- Subsonic features -->
<string name="settings.features_title">特性</string>
<string name="settings.five_star_rating_title">为歌曲使用五星评分</string>
<string name="settings.five_star_rating_description">对歌曲使用五星级评级系统
而不是简单地为项目加星标/取消星标。</string>

View File

@ -236,5 +236,40 @@
<item>@string/settings.playback.bluetooth_a2dp</item>
<item>@string/settings.playback.bluetooth_disabled</item>
</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>

View File

@ -96,6 +96,21 @@
<string name="error.label">Error</string>
<string name="jukebox.is_default">Jukebox By Default</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_alphaByName">By Name</string>
<string name="main.albums_frequent">Most Played</string>
@ -148,7 +163,7 @@
<string name="search.title">Search</string>
<string name="select_album.empty">No media found</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.play_all">Play All</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.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.download_transition">Stay in Downloads 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">Show Now Playing on Play</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_summary">Enable gapless playback</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.network_title">Network</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.preload">Songs To Preload</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>
<!-- 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_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:title="@string/settings.playback.single_button_bluetooth_device"
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
a:title="@string/settings.notifications_title"
@ -349,16 +355,14 @@
a:summary="@string/settings.hide_media_summary"
a:title="@string/settings.hide_media_title"
app:iconSpaceReserved="false"/>
</PreferenceCategory>
<PreferenceCategory
a:title="@string/settings.features_title"
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" />
<ListPreference
a:defaultValue=""
a:entries="@array/languageNames"
a:entryValues="@array/languageValues"
a:key="overrideLanguage"
a:title="@string/settings.override_language"
a:summary="@string/settings.override_language_summary"
app:iconSpaceReserved="false"/>
</PreferenceCategory>
<PreferenceCategory
a:title="@string/settings.debug.title"

View File

@ -1,9 +1,10 @@
package org.moire.ultrasonic.imageloader
import java.io.InputStream
import okio.Okio
import okio.buffer
import okio.source
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()
}