Merge branch 'develop' into AndroidAuto

This commit is contained in:
James Wells 2021-06-06 16:36:33 -04:00
commit be4ffc2c7e
No known key found for this signature in database
GPG Key ID: DB1528F6EED16127
48 changed files with 269 additions and 137 deletions

View File

@ -35,6 +35,13 @@ allprojects {
google() google()
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
} }
// Set Kotlin JVM target to the same for all subprojects
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "1.8"
}
}
} }
apply from: 'gradle_scripts/jacoco.gradle' apply from: 'gradle_scripts/jacoco.gradle'

View File

@ -39,7 +39,7 @@ class PasswordMD5Interceptor(private val password: String) : Interceptor {
val md5Digest = MessageDigest.getInstance("MD5") val md5Digest = MessageDigest.getInstance("MD5")
return md5Digest.digest( return md5Digest.digest(
"$password$salt".toByteArray() "$password$salt".toByteArray()
).toHexBytes().toLowerCase(Locale.getDefault()) ).toHexBytes().lowercase(Locale.getDefault())
} catch (e: NoSuchAlgorithmException) { } catch (e: NoSuchAlgorithmException) {
throw IllegalStateException(e) throw IllegalStateException(e)
} }

View File

@ -10,7 +10,7 @@ ext.versions = [
androidxcore : "1.5.0", androidxcore : "1.5.0",
ktlint : "0.37.1", ktlint : "0.37.1",
ktlintGradle : "9.2.1", ktlintGradle : "9.2.1",
detekt : "1.17.0", detekt : "1.17.1",
jacoco : "0.8.7", jacoco : "0.8.7",
preferences : "1.1.1", preferences : "1.1.1",
media : "1.3.1", media : "1.3.1",
@ -20,16 +20,16 @@ ext.versions = [
androidSupportDesign : "1.3.0", androidSupportDesign : "1.3.0",
constraintLayout : "2.0.4", constraintLayout : "2.0.4",
multidex : "2.0.1", multidex : "2.0.1",
room : "2.2.6", room : "2.3.0",
kotlin : "1.4.32", kotlin : "1.5.10",
kotlinxCoroutines : "1.4.3-native-mt", kotlinxCoroutines : "1.5.0-native-mt",
viewModelKtx : "2.2.0", viewModelKtx : "2.2.0",
retrofit : "2.6.4", retrofit : "2.6.4",
jackson : "2.9.5", jackson : "2.9.5",
okhttp : "3.12.13", okhttp : "3.12.13",
twitterSerial : "0.1.6", twitterSerial : "0.1.6",
koin : "2.2.2", koin : "3.0.2",
picasso : "2.71828", picasso : "2.71828",
sortListView : "1.0.1", sortListView : "1.0.1",

View File

@ -0,0 +1,14 @@
Bug fixes
- #411: getContext() can return null in Fragments.
- #415: App crashes when pressing the album cover in player.
- #447: Fix all service notification issues.
- #484: Add error handling to SelectAlbumModel functions.
Enhancements
- #433: Lock screen playback controls should include next/previous track.
- #476: Remove jcenter().
- #477: Make all preference operations context-free.
- #485: Remove MusicService.
- #493: Introduce new Generic Fragments, ViewModels, and Adapters for the display of API data.
- #505: Implement server feature checking.
- #506: Convert all remaining Music servic classes to Kotlin.

View File

@ -0,0 +1,14 @@
Correción de errores
- #411: getContext() puede retornar fragmentos nulos.
- #415: La aplicación se bloquea al presionar sobre la portada del álbum en el reproductor.
- #447: Solucionar todos los problemas de notificación de servicio.
- #484: Agregar manejo de errores a las funciones de SelectAlbumModel.
Mejoras
- #433: Los controles de reproducción de la pantalla de bloqueo deben incluir la pista siguiente/anterior.
- #476: Elimina jcenter().
- #477: Hacer que todas las operaciones de preferencias estén libres de contexto.
- #485: Elimina MusicService.
- #493: Introducir nuevos fragmentos genéricos, modelos de vista y adaptadores para la visualización de datos de API.
- #505: Implementar la verificación de características del servidor.
- #506: Convertir todas las clases de servicios de música restantes a Kotlin.

View File

@ -13,10 +13,6 @@ android {
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
} }
kotlinOptions {
jvmTarget = "1.8"
}
compileOptions { compileOptions {
// Sets Java compatibility to Java 8 // Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8

View File

@ -9,8 +9,8 @@ android {
defaultConfig { defaultConfig {
applicationId "org.moire.ultrasonic" applicationId "org.moire.ultrasonic"
versionCode 92 versionCode 93
versionName "2.20.0" versionName "2.21.0"
minSdkVersion versions.minSdk minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
@ -56,7 +56,6 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
freeCompilerArgs += "-Xopt-in=org.koin.core.component.KoinApiExtension"
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -94,7 +93,6 @@ dependencies {
implementation other.kotlinStdlib implementation other.kotlinStdlib
implementation other.kotlinxCoroutines implementation other.kotlinxCoroutines
implementation other.koinAndroid implementation other.koinAndroid
implementation other.koinViewModel
implementation other.okhttpLogging implementation other.okhttpLogging
implementation other.fastScroll implementation other.fastScroll
implementation other.sortListView implementation other.sortListView

View File

@ -162,7 +162,8 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur
setHasOptionsMenu(true); setHasOptionsMenu(true);
useFiveStarRating = KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING); FeatureStorage features = KoinJavaComponent.get(FeatureStorage.class);
useFiveStarRating = features.isFeatureEnabled(Feature.FIVE_STAR_RATING);
swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100; swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100;
swipeVelocity = swipeDistance; swipeVelocity = swipeDistance;

View File

@ -23,7 +23,7 @@ import kotlin.Lazy;
import static org.koin.java.KoinJavaComponent.inject; import static org.koin.java.KoinJavaComponent.inject;
/** /**
* Responsible for cleaning up files from the offline download cache on the filesystem * Responsible for cleaning up files from the offline download cache on the filesystem.
*/ */
public class CacheCleaner public class CacheCleaner
{ {

View File

@ -594,16 +594,16 @@ public class Util
} }
private static void showDialog(Context context, int icon, int titleId, int messageId) // The AlertDialog requires an Activity context, app context is not enough
// See https://stackoverflow.com/questions/5436822/
public static void showDialog(Context context, int icon, int titleId, String message)
{ {
new AlertDialog.Builder(context).setIcon(icon).setTitle(titleId).setMessage(messageId).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() new AlertDialog.Builder(context)
{ .setIcon(icon)
@Override .setTitle(titleId)
public void onClick(DialogInterface dialog, int i) .setMessage(message)
{ .setPositiveButton(R.string.common_ok, (dialog, i) -> dialog.dismiss())
dialog.dismiss(); .show();
}
}).show();
} }

View File

@ -28,8 +28,10 @@ import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.java.KoinJavaComponent.inject
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.PlayerState import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.fragment.OnBackPressedHandler import org.moire.ultrasonic.fragment.OnBackPressedHandler
@ -368,10 +370,18 @@ class NavigationActivity : AppCompatActivity() {
} }
private fun setMenuForServerSetting() { private fun setMenuForServerSetting() {
val visibility = !isOffline() if (isOffline()) {
chatMenuItem?.isVisible = visibility chatMenuItem?.isVisible = false
bookmarksMenuItem?.isVisible = visibility bookmarksMenuItem?.isVisible = false
sharesMenuItem?.isVisible = visibility sharesMenuItem?.isVisible = false
podcastsMenuItem?.isVisible = visibility podcastsMenuItem?.isVisible = false
return
}
val activeServerProvider: ActiveServerProvider by inject()
val activeServer = activeServerProvider.getActiveServer()
chatMenuItem?.isVisible = activeServer.chatSupport != false
bookmarksMenuItem?.isVisible = activeServer.bookmarkSupport != false
sharesMenuItem?.isVisible = activeServer.shareSupport != false
podcastsMenuItem?.isVisible = activeServer.podcastSupport != false
} }
} }

View File

@ -8,7 +8,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
/** /**
* Room Database to be used to store data for Ultrasonic * Room Database to be used to store data for Ultrasonic
*/ */
@Database(entities = [ServerSetting::class], version = 2) @Database(entities = [ServerSetting::class], version = 3)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
/** /**
@ -24,3 +24,20 @@ val MIGRATION_1_2: Migration = object : Migration(1, 2) {
) )
} }
} }
val MIGRATION_2_3: Migration = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"ALTER TABLE ServerSetting ADD COLUMN chatSupport INTEGER"
)
database.execSQL(
"ALTER TABLE ServerSetting ADD COLUMN bookmarkSupport INTEGER"
)
database.execSQL(
"ALTER TABLE ServerSetting ADD COLUMN shareSupport INTEGER"
)
database.execSQL(
"ALTER TABLE ServerSetting ADD COLUMN podcastSupport INTEGER"
)
}
}

View File

@ -29,7 +29,11 @@ data class ServerSetting(
@ColumnInfo(name = "allowSelfSignedCertificate") var allowSelfSignedCertificate: Boolean, @ColumnInfo(name = "allowSelfSignedCertificate") var allowSelfSignedCertificate: Boolean,
@ColumnInfo(name = "ldapSupport") var ldapSupport: Boolean, @ColumnInfo(name = "ldapSupport") var ldapSupport: Boolean,
@ColumnInfo(name = "musicFolderId") var musicFolderId: String?, @ColumnInfo(name = "musicFolderId") var musicFolderId: String?,
@ColumnInfo(name = "minimumApiVersion") var minimumApiVersion: String? @ColumnInfo(name = "minimumApiVersion") var minimumApiVersion: String?,
@ColumnInfo(name = "chatSupport") var chatSupport: Boolean? = null,
@ColumnInfo(name = "bookmarkSupport") var bookmarkSupport: Boolean? = null,
@ColumnInfo(name = "shareSupport") var shareSupport: Boolean? = null,
@ColumnInfo(name = "podcastSupport") var podcastSupport: Boolean? = null
) { ) {
constructor() : this ( constructor() : this (
-1, 0, "", "", "", "", false, false, false, null, null -1, 0, "", "", "", "", false, false, false, null, null

View File

@ -2,11 +2,12 @@ package org.moire.ultrasonic.di
import androidx.room.Room import androidx.room.Room
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.qualifier.named import org.koin.core.qualifier.named
import org.koin.dsl.module import org.koin.dsl.module
import org.moire.ultrasonic.data.AppDatabase import org.moire.ultrasonic.data.AppDatabase
import org.moire.ultrasonic.data.MIGRATION_1_2 import org.moire.ultrasonic.data.MIGRATION_1_2
import org.moire.ultrasonic.data.MIGRATION_2_3
import org.moire.ultrasonic.fragment.ServerSettingsModel import org.moire.ultrasonic.fragment.ServerSettingsModel
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
@ -25,6 +26,7 @@ val appPermanentStorage = module {
"ultrasonic-database" "ultrasonic-database"
) )
.addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.fallbackToDestructiveMigrationOnDowngrade() .fallbackToDestructiveMigrationOnDowngrade()
.build() .build()
} }

View File

@ -6,7 +6,6 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
@ -15,7 +14,6 @@ import org.moire.ultrasonic.util.Constants
* Displays a list of Albums from the media library * Displays a list of Albums from the media library
* TODO: Check refresh is working * TODO: Check refresh is working
*/ */
@KoinApiExtension
class AlbumListFragment : GenericListFragment<MusicDirectory.Entry, AlbumRowAdapter>() { class AlbumListFragment : GenericListFragment<MusicDirectory.Entry, AlbumRowAdapter>() {
/** /**

View File

@ -5,14 +5,12 @@ import android.os.Bundle
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.api.subsonic.models.AlbumListType import org.moire.ultrasonic.api.subsonic.models.AlbumListType
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.service.MusicService import org.moire.ultrasonic.service.MusicService
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
@KoinApiExtension
class AlbumListModel(application: Application) : GenericListModel(application) { class AlbumListModel(application: Application) : GenericListModel(application) {
val albumList: MutableLiveData<List<MusicDirectory.Entry>> = MutableLiveData() val albumList: MutableLiveData<List<MusicDirectory.Entry>> = MutableLiveData()

View File

@ -3,7 +3,6 @@ package org.moire.ultrasonic.fragment
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
@ -11,7 +10,6 @@ import org.moire.ultrasonic.util.Constants
/** /**
* Displays the list of Artists from the media library * Displays the list of Artists from the media library
*/ */
@KoinApiExtension
class ArtistListFragment : GenericListFragment<Artist, ArtistRowAdapter>() { class ArtistListFragment : GenericListFragment<Artist, ArtistRowAdapter>() {
/** /**

View File

@ -23,14 +23,12 @@ import android.os.Bundle
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.service.MusicService import org.moire.ultrasonic.service.MusicService
/** /**
* Provides ViewModel which contains the list of available Artists * Provides ViewModel which contains the list of available Artists
*/ */
@KoinApiExtension
class ArtistListModel(application: Application) : GenericListModel(application) { class ArtistListModel(application: Application) : GenericListModel(application) {
private val artists: MutableLiveData<List<Artist>> = MutableLiveData() private val artists: MutableLiveData<List<Artist>> = MutableLiveData()

View File

@ -96,7 +96,7 @@ class ArtistRowAdapter(
} }
private fun getSectionFromName(name: String): String { private fun getSectionFromName(name: String): String {
var section = name.first().toUpperCase() var section = name.first().uppercaseChar()
if (!section.isLetter()) section = '#' if (!section.isLetter()) section = '#'
return section.toString() return section.toString()
} }

View File

@ -11,23 +11,28 @@ import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import java.io.IOException
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URL import java.net.URL
import java.util.Locale
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.BuildConfig import org.moire.ultrasonic.BuildConfig
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.data.ServerSetting import org.moire.ultrasonic.data.ServerSetting
import org.moire.ultrasonic.service.ApiCallResponseChecker import org.moire.ultrasonic.service.ApiCallResponseChecker
import org.moire.ultrasonic.service.MusicServiceFactory import org.moire.ultrasonic.service.MusicServiceFactory
import org.moire.ultrasonic.service.SubsonicRESTException
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.ErrorDialog import org.moire.ultrasonic.util.ErrorDialog
import org.moire.ultrasonic.util.ModalBackgroundTask import org.moire.ultrasonic.util.ModalBackgroundTask
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import retrofit2.Response
import timber.log.Timber import timber.log.Timber
/** /**
@ -295,14 +300,41 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
* Tests if the network connection to the entered Server Settings can be made * Tests if the network connection to the entered Server Settings can be made
*/ */
private fun testConnection() { private fun testConnection() {
val task: ModalBackgroundTask<Boolean> = object : ModalBackgroundTask<Boolean>( val task: ModalBackgroundTask<String> = object : ModalBackgroundTask<String>(
activity, activity,
false false
) { ) {
fun boolToMark(value: Boolean?): String {
if (value == null)
return ""
return if (value) "✔️" else ""
}
fun getProgress(): String {
return String.format(
"""
|%s - ${resources.getString(R.string.button_bar_chat)}
|%s - ${resources.getString(R.string.button_bar_bookmarks)}
|%s - ${resources.getString(R.string.button_bar_shares)}
|%s - ${resources.getString(R.string.button_bar_podcasts)}
""".trimMargin(),
boolToMark(currentServerSetting!!.chatSupport),
boolToMark(currentServerSetting!!.bookmarkSupport),
boolToMark(currentServerSetting!!.shareSupport),
boolToMark(currentServerSetting!!.podcastSupport)
)
}
@Throws(Throwable::class) @Throws(Throwable::class)
override fun doInBackground(): Boolean { override fun doInBackground(): String {
updateProgress(R.string.settings_testing_connection)
currentServerSetting!!.chatSupport = null
currentServerSetting!!.bookmarkSupport = null
currentServerSetting!!.shareSupport = null
currentServerSetting!!.podcastSupport = null
updateProgress(getProgress())
val configuration = SubsonicClientConfiguration( val configuration = SubsonicClientConfiguration(
currentServerSetting!!.url, currentServerSetting!!.url,
currentServerSetting!!.userName, currentServerSetting!!.userName,
@ -330,17 +362,62 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
pingResponse = subsonicApiClient.api.ping().execute() pingResponse = subsonicApiClient.api.ping().execute()
ApiCallResponseChecker.checkResponseSuccessful(pingResponse) ApiCallResponseChecker.checkResponseSuccessful(pingResponse)
currentServerSetting!!.chatSupport = isServerFunctionAvailable {
subsonicApiClient.api.getChatMessages().execute()
}
updateProgress(getProgress())
currentServerSetting!!.bookmarkSupport = isServerFunctionAvailable {
subsonicApiClient.api.getBookmarks().execute()
}
updateProgress(getProgress())
currentServerSetting!!.shareSupport = isServerFunctionAvailable {
subsonicApiClient.api.getShares().execute()
}
updateProgress(getProgress())
currentServerSetting!!.podcastSupport = isServerFunctionAvailable {
subsonicApiClient.api.getPodcasts().execute()
}
updateProgress(getProgress())
val licenseResponse = subsonicApiClient.api.getLicense().execute() val licenseResponse = subsonicApiClient.api.getLicense().execute()
ApiCallResponseChecker.checkResponseSuccessful(licenseResponse) ApiCallResponseChecker.checkResponseSuccessful(licenseResponse)
return licenseResponse.body()!!.license.valid if (!licenseResponse.body()!!.license.valid) {
return getProgress() + "\n" +
resources.getString(R.string.settings_testing_unlicensed)
}
return getProgress()
} }
override fun done(licenseValid: Boolean) { override fun done(responseString: String) {
if (licenseValid) { var dialogText = responseString
Util.toast(activity, R.string.settings_testing_ok) if (arrayOf(
} else { currentServerSetting!!.chatSupport,
Util.toast(activity, R.string.settings_testing_unlicensed) currentServerSetting!!.bookmarkSupport,
currentServerSetting!!.shareSupport,
currentServerSetting!!.podcastSupport
).any { x -> x == false }
) {
dialogText = String.format(
Locale.ROOT,
"%s\n\n%s",
responseString,
resources.getString(R.string.server_editor_disabled_feature)
)
} }
Util.showDialog(
activity,
android.R.drawable.ic_dialog_info,
R.string.settings_testing_ok,
dialogText
)
} }
override fun error(error: Throwable) { override fun error(error: Throwable) {
@ -359,6 +436,18 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
task.execute() task.execute()
} }
private fun isServerFunctionAvailable(function: () -> Response<out SubsonicResponse>): Boolean {
return try {
val response = function()
ApiCallResponseChecker.checkResponseSuccessful(response)
true
} catch (_: IOException) {
false
} catch (_: SubsonicRESTException) {
false
}
}
/** /**
* Finishes the Activity, after confirmation from the user if needed * Finishes the Activity, after confirmation from the user if needed
*/ */

View File

@ -13,8 +13,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Artist
@ -31,7 +30,6 @@ import org.moire.ultrasonic.view.SelectMusicFolderView
* @param T: The type of data which will be used (must extend GenericEntry) * @param T: The type of data which will be used (must extend GenericEntry)
* @param TA: The Adapter to use (must extend GenericRowAdapter) * @param TA: The Adapter to use (must extend GenericRowAdapter)
*/ */
@KoinApiExtension
abstract class GenericListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> : Fragment() { abstract class GenericListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> : Fragment() {
internal val activeServerProvider: ActiveServerProvider by inject() internal val activeServerProvider: ActiveServerProvider by inject()
internal val serverSettingsModel: ServerSettingsModel by viewModel() internal val serverSettingsModel: ServerSettingsModel by viewModel()

View File

@ -15,7 +15,6 @@ import java.net.UnknownHostException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinApiExtension
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
@ -29,7 +28,6 @@ import org.moire.ultrasonic.util.Util
/** /**
* An abstract Model, which can be extended to retrieve a list of items from the API * An abstract Model, which can be extended to retrieve a list of items from the API
*/ */
@KoinApiExtension
open class GenericListModel(application: Application) : open class GenericListModel(application: Application) :
AndroidViewModel(application), KoinComponent { AndroidViewModel(application), KoinComponent {

View File

@ -15,7 +15,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.fragment.EditServerFragment.Companion.EDIT_SERVER_INTENT_INDEX import org.moire.ultrasonic.fragment.EditServerFragment.Companion.EDIT_SERVER_INTENT_INDEX

View File

@ -33,7 +33,6 @@ import java.util.Random
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
@ -60,7 +59,6 @@ import timber.log.Timber
* Displays a group of tracks, eg. the songs of an album, of a playlist etc. * Displays a group of tracks, eg. the songs of an album, of a playlist etc.
* TODO: Refactor this fragment and model to extend the GenericListFragment * TODO: Refactor this fragment and model to extend the GenericListFragment
*/ */
@KoinApiExtension
class TrackCollectionFragment : Fragment() { class TrackCollectionFragment : Fragment() {
private var refreshAlbumListView: SwipeRefreshLayout? = null private var refreshAlbumListView: SwipeRefreshLayout? = null

View File

@ -13,7 +13,6 @@ import androidx.lifecycle.MutableLiveData
import java.util.LinkedList import java.util.LinkedList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.service.MusicService import org.moire.ultrasonic.service.MusicService
@ -24,7 +23,6 @@ import org.moire.ultrasonic.util.Util
* Model for retrieving different collections of tracks from the API * Model for retrieving different collections of tracks from the API
* TODO: Refactor this model to extend the GenericListModel * TODO: Refactor this model to extend the GenericListModel
*/ */
@KoinApiExtension
class TrackCollectionModel(application: Application) : GenericListModel(application) { class TrackCollectionModel(application: Application) : GenericListModel(application) {
private val allSongsId = "-1" private val allSongsId = "-1"

View File

@ -18,7 +18,8 @@ import timber.log.Timber
class AudioFocusHandler(private val context: Context) { class AudioFocusHandler(private val context: Context) {
// TODO: This is a circular reference, try to remove it // TODO: This is a circular reference, try to remove it
// This should be doable by using the native MediaController framework // This should be doable by using the native MediaController framework
private val mediaPlayerControllerLazy = inject(MediaPlayerController::class.java) private val mediaPlayerControllerLazy =
inject<MediaPlayerController>(MediaPlayerController::class.java)
private val audioManager by lazy { private val audioManager by lazy {
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager context.getSystemService(Context.AUDIO_SERVICE) as AudioManager

View File

@ -9,7 +9,8 @@ package org.moire.ultrasonic.service
import android.graphics.Bitmap import android.graphics.Bitmap
import java.io.InputStream import java.io.InputStream
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import org.koin.java.KoinJavaComponent.inject import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.Bookmark import org.moire.ultrasonic.domain.Bookmark
import org.moire.ultrasonic.domain.ChatMessage import org.moire.ultrasonic.domain.ChatMessage
@ -31,10 +32,9 @@ import org.moire.ultrasonic.util.TimeLimitedCache
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
class CachedMusicService(private val musicService: MusicService) : MusicService { class CachedMusicService(private val musicService: MusicService) : MusicService, KoinComponent {
private val activeServerProvider = inject( private val activeServerProvider: ActiveServerProvider by inject()
ActiveServerProvider::class.java
)
private val cachedMusicDirectories: LRUCache<String?, TimeLimitedCache<MusicDirectory?>> private val cachedMusicDirectories: LRUCache<String?, TimeLimitedCache<MusicDirectory?>>
private val cachedArtist: LRUCache<String?, TimeLimitedCache<MusicDirectory?>> private val cachedArtist: LRUCache<String?, TimeLimitedCache<MusicDirectory?>>
private val cachedAlbum: LRUCache<String?, TimeLimitedCache<MusicDirectory?>> private val cachedAlbum: LRUCache<String?, TimeLimitedCache<MusicDirectory?>>
@ -308,8 +308,8 @@ class CachedMusicService(private val musicService: MusicService) : MusicService
} }
private fun checkSettingsChanged() { private fun checkSettingsChanged() {
val newUrl = activeServerProvider.value.getRestUrl(null) val newUrl = activeServerProvider.getRestUrl(null)
val newFolderId = activeServerProvider.value.getActiveServer().musicFolderId val newFolderId = activeServerProvider.getActiveServer().musicFolderId
if (!Util.equals(newUrl, restUrl) || !Util.equals(cachedMusicFolderId, newFolderId)) { if (!Util.equals(newUrl, restUrl) || !Util.equals(cachedMusicFolderId, newFolderId)) {
cachedMusicFolders.clear() cachedMusicFolders.clear()
cachedMusicDirectories.clear() cachedMusicDirectories.clear()

View File

@ -19,8 +19,8 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.io.RandomAccessFile import java.io.RandomAccessFile
import org.koin.core.component.KoinApiExtension import org.koin.core.component.KoinComponent
import org.koin.java.KoinJavaComponent.inject import org.koin.core.component.inject
import org.moire.ultrasonic.app.UApp import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
@ -36,11 +36,10 @@ import timber.log.Timber
* @author Sindre Mehus * @author Sindre Mehus
* @version $Id$ * @version $Id$
*/ */
@KoinApiExtension
class DownloadFile( class DownloadFile(
val song: MusicDirectory.Entry, val song: MusicDirectory.Entry,
private val save: Boolean private val save: Boolean
) { ) : KoinComponent {
val partialFile: File val partialFile: File
val completeFile: File val completeFile: File
private val saveFile: File = FileUtil.getSongFile(song) private val saveFile: File = FileUtil.getSongFile(song)
@ -59,7 +58,7 @@ class DownloadFile(
@Volatile @Volatile
private var completeWhenDone = false private var completeWhenDone = false
private val downloader = inject(Downloader::class.java) private val downloader: Downloader by inject()
val progress: MutableLiveData<Int> = MutableLiveData(0) val progress: MutableLiveData<Int> = MutableLiveData(0)
@ -201,7 +200,6 @@ class DownloadFile(
return String.format("DownloadFile (%s)", song) return String.format("DownloadFile (%s)", song)
} }
@KoinApiExtension
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught")
private inner class DownloadTask : CancellableTask() { private inner class DownloadTask : CancellableTask() {
override fun execute() { override fun execute() {
@ -310,7 +308,7 @@ class DownloadFile(
} }
wifiLock?.release() wifiLock?.release()
CacheCleaner().cleanSpace() CacheCleaner().cleanSpace()
downloader.value.checkDownloads() downloader.checkDownloads()
} }
} }

View File

@ -25,7 +25,6 @@ import java.net.URLEncoder
import java.util.Locale import java.util.Locale
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.audiofx.EqualizerController import org.moire.ultrasonic.audiofx.EqualizerController
import org.moire.ultrasonic.audiofx.VisualizerController import org.moire.ultrasonic.audiofx.VisualizerController
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
@ -40,7 +39,6 @@ import timber.log.Timber
/** /**
* Represents a Media Player which uses the mobile's resources for playback * Represents a Media Player which uses the mobile's resources for playback
*/ */
@KoinApiExtension
class LocalMediaPlayer( class LocalMediaPlayer(
private val audioFocusHandler: AudioFocusHandler, private val audioFocusHandler: AudioFocusHandler,
private val context: Context private val context: Context

View File

@ -7,9 +7,9 @@
package org.moire.ultrasonic.service package org.moire.ultrasonic.service
import android.content.Intent import android.content.Intent
import org.koin.core.component.KoinApiExtension import org.koin.core.component.KoinComponent
import org.koin.java.KoinJavaComponent.get import org.koin.core.component.get
import org.koin.java.KoinJavaComponent.inject import org.koin.core.component.inject
import org.moire.ultrasonic.app.UApp import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
@ -30,7 +30,6 @@ import timber.log.Timber
* This class contains everything that is necessary for the Application UI * This class contains everything that is necessary for the Application UI
* to control the Media Player implementation. * to control the Media Player implementation.
*/ */
@KoinApiExtension
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
class MediaPlayerController( class MediaPlayerController(
private val downloadQueueSerializer: DownloadQueueSerializer, private val downloadQueueSerializer: DownloadQueueSerializer,
@ -38,7 +37,7 @@ class MediaPlayerController(
private val downloader: Downloader, private val downloader: Downloader,
private val shufflePlayBuffer: ShufflePlayBuffer, private val shufflePlayBuffer: ShufflePlayBuffer,
private val localMediaPlayer: LocalMediaPlayer private val localMediaPlayer: LocalMediaPlayer
) { ) : KoinComponent {
private var created = false private var created = false
var suggestedPlaylistName: String? = null var suggestedPlaylistName: String? = null
@ -46,8 +45,8 @@ class MediaPlayerController(
var showVisualization = false var showVisualization = false
private var autoPlayStart = false private var autoPlayStart = false
private val jukeboxMediaPlayer = inject(JukeboxMediaPlayer::class.java).value private val jukeboxMediaPlayer: JukeboxMediaPlayer by inject()
private val activeServerProvider = inject(ActiveServerProvider::class.java).value private val activeServerProvider: ActiveServerProvider by inject()
fun onCreate() { fun onCreate() {
if (created) return if (created) return
@ -462,7 +461,8 @@ class MediaPlayerController(
@Suppress("TooGenericExceptionCaught") // The interface throws only generic exceptions @Suppress("TooGenericExceptionCaught") // The interface throws only generic exceptions
fun setSongRating(rating: Int) { fun setSongRating(rating: Int) {
if (!get(FeatureStorage::class.java).isFeatureEnabled(Feature.FIVE_STAR_RATING)) return val features: FeatureStorage = get()
if (!features.isFeatureEnabled(Feature.FIVE_STAR_RATING)) return
if (localMediaPlayer.currentPlaying == null) return if (localMediaPlayer.currentPlaying == null) return
val song = localMediaPlayer.currentPlaying!!.song val song = localMediaPlayer.currentPlaying!!.song
song.userRating = rating song.userRating = rating

View File

@ -18,7 +18,6 @@
*/ */
package org.moire.ultrasonic.service package org.moire.ultrasonic.service
import org.koin.core.component.KoinApiExtension
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import org.koin.core.context.loadKoinModules import org.koin.core.context.loadKoinModules
@ -30,7 +29,6 @@ import org.moire.ultrasonic.di.ONLINE_MUSIC_SERVICE
import org.moire.ultrasonic.di.musicServiceModule import org.moire.ultrasonic.di.musicServiceModule
// TODO Refactor everywhere to use DI way to get MusicService, and then remove this class // TODO Refactor everywhere to use DI way to get MusicService, and then remove this class
@KoinApiExtension
object MusicServiceFactory : KoinComponent { object MusicServiceFactory : KoinComponent {
@JvmStatic @JvmStatic
fun getMusicService(): MusicService { fun getMusicService(): MusicService {

View File

@ -22,7 +22,8 @@ import java.util.Locale
import java.util.Random import java.util.Random
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.regex.Pattern import java.util.regex.Pattern
import org.koin.java.KoinJavaComponent.inject import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.Bookmark import org.moire.ultrasonic.domain.Bookmark
@ -47,10 +48,8 @@ import timber.log.Timber
// TODO: There are quite a number of deeply nested and complicated functions in this class.. // TODO: There are quite a number of deeply nested and complicated functions in this class..
// Simplify them :) // Simplify them :)
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
class OfflineMusicService : MusicService { class OfflineMusicService : MusicService, KoinComponent {
private val activeServerProvider = inject( private val activeServerProvider: ActiveServerProvider by inject()
ActiveServerProvider::class.java
)
override fun getIndexes(musicFolderId: String?, refresh: Boolean): Indexes { override fun getIndexes(musicFolderId: String?, refresh: Boolean): Indexes {
val artists: MutableList<Artist> = ArrayList() val artists: MutableList<Artist> = ArrayList()
@ -67,8 +66,8 @@ class OfflineMusicService : MusicService {
val ignoredArticlesString = "The El La Los Las Le Les" val ignoredArticlesString = "The El La Los Las Le Les"
val ignoredArticles = COMPILE.split(ignoredArticlesString) val ignoredArticles = COMPILE.split(ignoredArticlesString)
artists.sortWith { lhsArtist, rhsArtist -> artists.sortWith { lhsArtist, rhsArtist ->
var lhs = lhsArtist.name!!.toLowerCase(Locale.ROOT) var lhs = lhsArtist.name!!.lowercase(Locale.ROOT)
var rhs = rhsArtist.name!!.toLowerCase(Locale.ROOT) var rhs = rhsArtist.name!!.lowercase(Locale.ROOT)
val lhs1 = lhs[0] val lhs1 = lhs[0]
val rhs1 = rhs[0] val rhs1 = rhs[0]
if (Character.isDigit(lhs1) && !Character.isDigit(rhs1)) { if (Character.isDigit(lhs1) && !Character.isDigit(rhs1)) {
@ -79,13 +78,13 @@ class OfflineMusicService : MusicService {
} }
for (article in ignoredArticles) { for (article in ignoredArticles) {
var index = lhs.indexOf( var index = lhs.indexOf(
String.format(Locale.ROOT, "%s ", article.toLowerCase(Locale.ROOT)) String.format(Locale.ROOT, "%s ", article.lowercase(Locale.ROOT))
) )
if (index == 0) { if (index == 0) {
lhs = lhs.substring(article.length + 1) lhs = lhs.substring(article.length + 1)
} }
index = rhs.indexOf( index = rhs.indexOf(
String.format(Locale.ROOT, "%s ", article.toLowerCase(Locale.ROOT)) String.format(Locale.ROOT, "%s ", article.lowercase(Locale.ROOT))
) )
if (index == 0) { if (index == 0) {
rhs = rhs.substring(article.length + 1) rhs = rhs.substring(article.length + 1)
@ -253,7 +252,7 @@ class OfflineMusicService : MusicService {
@Throws(Exception::class) @Throws(Exception::class)
override fun createPlaylist(id: String, name: String, entries: List<MusicDirectory.Entry>) { override fun createPlaylist(id: String, name: String, entries: List<MusicDirectory.Entry>) {
val playlistFile = val playlistFile =
FileUtil.getPlaylistFile(activeServerProvider.value.getActiveServer().name, name) FileUtil.getPlaylistFile(activeServerProvider.getActiveServer().name, name)
val fw = FileWriter(playlistFile) val fw = FileWriter(playlistFile)
val bw = BufferedWriter(fw) val bw = BufferedWriter(fw)
try { try {
@ -668,10 +667,10 @@ class OfflineMusicService : MusicService {
} }
private fun matchCriteria(criteria: SearchCriteria, name: String?): Int { private fun matchCriteria(criteria: SearchCriteria, name: String?): Int {
val query = criteria.query.toLowerCase(Locale.ROOT) val query = criteria.query.lowercase(Locale.ROOT)
val queryParts = COMPILE.split(query) val queryParts = COMPILE.split(query)
val nameParts = COMPILE.split( val nameParts = COMPILE.split(
name!!.toLowerCase(Locale.ROOT) name!!.lowercase(Locale.ROOT)
) )
var closeness = 0 var closeness = 0
for (queryPart in queryParts) { for (queryPart in queryParts) {

View File

@ -5,7 +5,6 @@ import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import java.util.Collections import java.util.Collections
import java.util.LinkedList import java.util.LinkedList
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
@ -20,7 +19,6 @@ import org.moire.ultrasonic.util.Util
* Retrieves a list of songs and adds them to the now playing list * Retrieves a list of songs and adds them to the now playing list
*/ */
@Suppress("LongParameterList") @Suppress("LongParameterList")
@KoinApiExtension
class DownloadHandler( class DownloadHandler(
val mediaPlayerController: MediaPlayerController, val mediaPlayerController: MediaPlayerController,
val networkAndStorageChecker: NetworkAndStorageChecker val networkAndStorageChecker: NetworkAndStorageChecker

View File

@ -1,10 +1,10 @@
package org.moire.ultrasonic.subsonic package org.moire.ultrasonic.subsonic
import android.content.Context import android.content.Context
import org.koin.java.KoinJavaComponent.get import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.moire.ultrasonic.featureflags.Feature import org.moire.ultrasonic.featureflags.Feature
import org.moire.ultrasonic.featureflags.FeatureStorage import org.moire.ultrasonic.featureflags.FeatureStorage
import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader
import org.moire.ultrasonic.util.ImageLoader import org.moire.ultrasonic.util.ImageLoader
import org.moire.ultrasonic.util.LegacyImageLoader import org.moire.ultrasonic.util.LegacyImageLoader
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
@ -12,7 +12,7 @@ import org.moire.ultrasonic.util.Util
/** /**
* Handles the lifetime of the Image Loader * Handles the lifetime of the Image Loader
*/ */
class ImageLoaderProvider(val context: Context) { class ImageLoaderProvider(val context: Context) : KoinComponent {
private var imageLoader: ImageLoader? = null private var imageLoader: ImageLoader? = null
@Synchronized @Synchronized
@ -33,12 +33,12 @@ class ImageLoaderProvider(val context: Context) {
context, context,
Util.getImageLoaderConcurrency() Util.getImageLoaderConcurrency()
) )
val isNewImageLoaderEnabled = get(FeatureStorage::class.java) val features: FeatureStorage = get()
.isFeatureEnabled(Feature.NEW_IMAGE_DOWNLOADER) val isNewImageLoaderEnabled = features.isFeatureEnabled(Feature.NEW_IMAGE_DOWNLOADER)
imageLoader = if (isNewImageLoaderEnabled) { imageLoader = if (isNewImageLoaderEnabled) {
SubsonicImageLoaderProxy( SubsonicImageLoaderProxy(
legacyImageLoader, legacyImageLoader,
get(SubsonicImageLoader::class.java) get()
) )
} else { } else {
legacyImageLoader legacyImageLoader

View File

@ -24,8 +24,9 @@ import android.graphics.drawable.Drawable
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.Checkable import android.widget.Checkable
import org.koin.java.KoinJavaComponent.get import org.koin.core.component.KoinComponent
import org.koin.java.KoinJavaComponent.inject import org.koin.core.component.get
import org.koin.core.component.inject
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
@ -42,7 +43,7 @@ import timber.log.Timber
/** /**
* Used to display songs and videos in a `ListView`. * Used to display songs and videos in a `ListView`.
*/ */
class SongView(context: Context) : UpdateView(context), Checkable { class SongView(context: Context) : UpdateView(context), Checkable, KoinComponent {
var entry: MusicDirectory.Entry? = null var entry: MusicDirectory.Entry? = null
private set private set
@ -55,10 +56,9 @@ class SongView(context: Context) : UpdateView(context), Checkable {
private var downloadFile: DownloadFile? = null private var downloadFile: DownloadFile? = null
private var playing = false private var playing = false
private var viewHolder: SongViewHolder? = null private var viewHolder: SongViewHolder? = null
private val features: FeatureStorage = get()
private val useFiveStarRating: Boolean = private val useFiveStarRating: Boolean = features.isFeatureEnabled(Feature.FIVE_STAR_RATING)
get(FeatureStorage::class.java).isFeatureEnabled(Feature.FIVE_STAR_RATING) private val mediaPlayerController: MediaPlayerController by inject()
private val mediaPlayerControllerLazy = inject(MediaPlayerController::class.java)
fun setLayout(song: MusicDirectory.Entry) { fun setLayout(song: MusicDirectory.Entry) {
@ -96,7 +96,7 @@ class SongView(context: Context) : UpdateView(context), Checkable {
updateBackground() updateBackground()
entry = song entry = song
downloadFile = mediaPlayerControllerLazy.value.getDownloadFileForSong(song) downloadFile = mediaPlayerController.getDownloadFileForSong(song)
val artist = StringBuilder(60) val artist = StringBuilder(60)
var bitRate: String? = null var bitRate: String? = null
@ -223,7 +223,7 @@ class SongView(context: Context) : UpdateView(context), Checkable {
public override fun update() { public override fun update() {
updateBackground() updateBackground()
downloadFile = mediaPlayerControllerLazy.value.getDownloadFileForSong(entry) downloadFile = mediaPlayerController.getDownloadFileForSong(entry)
updateDownloadStatus(downloadFile!!) updateDownloadStatus(downloadFile!!)
@ -254,7 +254,7 @@ class SongView(context: Context) : UpdateView(context), Checkable {
if (rating > 4) starDrawable else starHollowDrawable if (rating > 4) starDrawable else starHollowDrawable
) )
val playing = mediaPlayerControllerLazy.value.currentPlaying === downloadFile val playing = mediaPlayerController.currentPlaying === downloadFile
if (playing) { if (playing) {
if (!this.playing) { if (!this.playing) {

View File

@ -297,7 +297,6 @@
<string name="settings.show_track_number">Zobrazovat číslo skladby</string> <string name="settings.show_track_number">Zobrazovat číslo skladby</string>
<string name="settings.show_track_number_summary">Připojovat číslo skladby při zobrazování skladby</string> <string name="settings.show_track_number_summary">Připojovat číslo skladby při zobrazování skladby</string>
<string name="settings.test_connection_title">Test připojení</string> <string name="settings.test_connection_title">Test připojení</string>
<string name="settings.testing_connection">Testuji připojení&#8230;</string>
<string name="settings.testing_ok">Připojení je v pořádku</string> <string name="settings.testing_ok">Připojení je v pořádku</string>
<string name="settings.testing_unlicensed">Připojení je v pořádku. Server bez licence.</string> <string name="settings.testing_unlicensed">Připojení je v pořádku. Server bez licence.</string>
<string name="settings.theme_light">Světlý</string> <string name="settings.theme_light">Světlý</string>
@ -445,7 +444,6 @@
<string name="server_menu.move_down">Posunout níž</string> <string name="server_menu.move_down">Posunout níž</string>
<string name="server_editor.authentication">Ověření</string> <string name="server_editor.authentication">Ověření</string>
<string name="server_editor.advanced">Rozšířené možnosti</string> <string name="server_editor.advanced">Rozšířené možnosti</string>
<plurals name="select_album_n_songs"> <plurals name="select_album_n_songs">
<item quantity="one">1 skladba</item> <item quantity="one">1 skladba</item>
<item quantity="few">%d skladby</item> <item quantity="few">%d skladby</item>

View File

@ -296,7 +296,6 @@
<string name="settings.show_track_number">Titelnummer anzeigen</string> <string name="settings.show_track_number">Titelnummer anzeigen</string>
<string name="settings.show_track_number_summary">Titel mit Nummer anzeigen</string> <string name="settings.show_track_number_summary">Titel mit Nummer anzeigen</string>
<string name="settings.test_connection_title">Verbindung testen</string> <string name="settings.test_connection_title">Verbindung testen</string>
<string name="settings.testing_connection">Teste Verbindung&#8230;</string>
<string name="settings.testing_ok">Verbindung OK</string> <string name="settings.testing_ok">Verbindung OK</string>
<string name="settings.testing_unlicensed">Verbindung OK, Server nicht lizensiert.</string> <string name="settings.testing_unlicensed">Verbindung OK, Server nicht lizensiert.</string>
<string name="settings.theme_light">Hell</string> <string name="settings.theme_light">Hell</string>

View File

@ -309,7 +309,6 @@
<string name="settings.show_track_number">Mostrar número de pista</string> <string name="settings.show_track_number">Mostrar número de pista</string>
<string name="settings.show_track_number_summary">Incluir el número de pista cuando se muestre una canción</string> <string name="settings.show_track_number_summary">Incluir el número de pista cuando se muestre una canción</string>
<string name="settings.test_connection_title">Comprobar conexión</string> <string name="settings.test_connection_title">Comprobar conexión</string>
<string name="settings.testing_connection">Comprobado conexión&#8230;</string>
<string name="settings.testing_ok">La conexión es correcta</string> <string name="settings.testing_ok">La conexión es correcta</string>
<string name="settings.testing_unlicensed">La conexión es correcta. Servidor sin licencia.</string> <string name="settings.testing_unlicensed">La conexión es correcta. Servidor sin licencia.</string>
<string name="settings.theme_light">Claro</string> <string name="settings.theme_light">Claro</string>
@ -459,6 +458,7 @@
<string name="server_menu.move_down">Bajar</string> <string name="server_menu.move_down">Bajar</string>
<string name="server_editor.authentication">Autenticación</string> <string name="server_editor.authentication">Autenticación</string>
<string name="server_editor.advanced">Configuración avanzada</string> <string name="server_editor.advanced">Configuración avanzada</string>
<string name="server_editor.disabled_feature">Una o más funciones se han deshabilitado porque el servidor no las admite.\nPuedes ejecutar esta prueba nuevamente en cualquier momento.</string>
<plurals name="select_album_n_songs"> <plurals name="select_album_n_songs">
<item quantity="one">1 canción</item> <item quantity="one">1 canción</item>

View File

@ -297,7 +297,6 @@
<string name="settings.show_track_number">Afficher le numéro du titre</string> <string name="settings.show_track_number">Afficher le numéro du titre</string>
<string name="settings.show_track_number_summary">Inclure son numero lors de l\'affichage d\'un titre</string> <string name="settings.show_track_number_summary">Inclure son numero lors de l\'affichage d\'un titre</string>
<string name="settings.test_connection_title">Tester la connexion</string> <string name="settings.test_connection_title">Tester la connexion</string>
<string name="settings.testing_connection">Connexion en cours de test&#8230;</string>
<string name="settings.testing_ok">Connexion correcte</string> <string name="settings.testing_ok">Connexion correcte</string>
<string name="settings.testing_unlicensed">Connexion correcte. Serveur sans licence.</string> <string name="settings.testing_unlicensed">Connexion correcte. Serveur sans licence.</string>
<string name="settings.theme_light">Clair</string> <string name="settings.theme_light">Clair</string>
@ -447,6 +446,7 @@
<string name="server_menu.move_down">Déplacer vers le bas</string> <string name="server_menu.move_down">Déplacer vers le bas</string>
<string name="server_editor.authentication">Authentification</string> <string name="server_editor.authentication">Authentification</string>
<string name="server_editor.advanced">Paramètres avancés</string> <string name="server_editor.advanced">Paramètres avancés</string>
<string name="server_editor.disabled_feature">Une ou plusieurs fonctionnalités ont été désactivées car le serveur ne les prend pas en charge.\nVous pouvez réexécuter ce test à tout moment.</string>
<plurals name="select_album_n_songs"> <plurals name="select_album_n_songs">
<item quantity="one">1 titre</item> <item quantity="one">1 titre</item>

View File

@ -15,6 +15,13 @@
<string name="button_bar.chat">Csevegés (Chat)</string> <string name="button_bar.chat">Csevegés (Chat)</string>
<string name="button_bar.home">Ultrasonic főoldal</string> <string name="button_bar.home">Ultrasonic főoldal</string>
<string name="button_bar.now_playing">Lejátszó</string> <string name="button_bar.now_playing">Lejátszó</string>
<string name="buttons.play">Lejátszás</string>
<string name="buttons.pause">Szünet</string>
<string name="buttons.repeat">Ismétlés</string>
<string name="buttons.shuffle">Véletlen sorrendű</string>
<string name="buttons.stop">Állj</string>
<string name="buttons.next">Következő</string>
<string name="buttons.previous">Előző</string>
<string name="podcasts.label">Podcast</string> <string name="podcasts.label">Podcast</string>
<string name="podcasts_channels.empty">Nincsenek podcast-csatornák regisztrálva</string> <string name="podcasts_channels.empty">Nincsenek podcast-csatornák regisztrálva</string>
<string name="button_bar.podcasts">Podcast</string> <string name="button_bar.podcasts">Podcast</string>
@ -32,8 +39,11 @@
<string name="common.name">Név</string> <string name="common.name">Név</string>
<string name="common.ok">OK</string> <string name="common.ok">OK</string>
<string name="common.pin">Tárolás (Megőrzés az eszközön)</string> <string name="common.pin">Tárolás (Megőrzés az eszközön)</string>
<string name="common.pause">Szünet</string>
<string name="common.play">Lejátszás</string>
<string name="common.play_last">Lejátszás (Utolsóként)</string> <string name="common.play_last">Lejátszás (Utolsóként)</string>
<string name="common.play_next">Lejátszás (Következőként)</string> <string name="common.play_next">Lejátszás (Következőként)</string>
<string name="common.play_previous">Előző lejátszása</string>
<string name="common.play_now">Lejátszás</string> <string name="common.play_now">Lejátszás</string>
<string name="common.play_shuffled">Véletlen sorrendű lejátszás</string> <string name="common.play_shuffled">Véletlen sorrendű lejátszás</string>
<string name="common.public">Nyilvános</string> <string name="common.public">Nyilvános</string>
@ -256,6 +266,8 @@
<string name="settings.playback.resume_play_on_headphones_plug.summary">Az alkalmazás folytatja a szüneteltetett lejátszást a fejhallgató behelyezésekor a készülékbe.</string> <string name="settings.playback.resume_play_on_headphones_plug.summary">Az alkalmazás folytatja a szüneteltetett lejátszást a fejhallgató behelyezésekor a készülékbe.</string>
<string name="settings.screen_lit_summary">Képernyő ébrentartása a letöltés alatt, a magasabb letöltési sebesség érdekében.</string> <string name="settings.screen_lit_summary">Képernyő ébrentartása a letöltés alatt, a magasabb letöltési sebesség érdekében.</string>
<string name="settings.screen_lit_title">Képernyő ébrentartása</string> <string name="settings.screen_lit_title">Képernyő ébrentartása</string>
<string name="settings.scrobble_summary">Ne felejtsd el beállítani a Scrobble szolgáltatónál használt felhasználóneved és jelszavad a szervereden</string>
<string name="settings.scrobble_title">Scrobble engedélyezése</string>
<string name="settings.search_1">1</string> <string name="settings.search_1">1</string>
<string name="settings.search_10">10</string> <string name="settings.search_10">10</string>
<string name="settings.search_100">100</string> <string name="settings.search_100">100</string>
@ -297,11 +309,11 @@
<string name="settings.show_track_number">Sorszám megjelenítése</string> <string name="settings.show_track_number">Sorszám megjelenítése</string>
<string name="settings.show_track_number_summary">Dalok sorszámának megjelenítése.</string> <string name="settings.show_track_number_summary">Dalok sorszámának megjelenítése.</string>
<string name="settings.test_connection_title">Kapcsolat tesztelése</string> <string name="settings.test_connection_title">Kapcsolat tesztelése</string>
<string name="settings.testing_connection">Kapcsolat tesztelése&#8230;</string>
<string name="settings.testing_ok">Kapcsolat OK!</string> <string name="settings.testing_ok">Kapcsolat OK!</string>
<string name="settings.testing_unlicensed">Kapcsolat OK! A kiszolgálónak nincs licence!</string> <string name="settings.testing_unlicensed">Kapcsolat OK! A kiszolgálónak nincs licence!</string>
<string name="settings.theme_light">Világos</string> <string name="settings.theme_light">Világos</string>
<string name="settings.theme_dark">Sötét</string> <string name="settings.theme_dark">Sötét</string>
<string name="settings.theme_black">Fekete</string>
<string name="settings.theme_title">Téma</string> <string name="settings.theme_title">Téma</string>
<string name="settings.title.allow_self_signed_certificate">Engedélyezze az önaláírt HTTPS tanúsítványt</string> <string name="settings.title.allow_self_signed_certificate">Engedélyezze az önaláírt HTTPS tanúsítványt</string>
<string name="settings.title.enable_ldap_users_support">Az LDAP-felhasználók támogatásának engedélyezése</string> <string name="settings.title.enable_ldap_users_support">Az LDAP-felhasználók támogatásának engedélyezése</string>
@ -401,6 +413,8 @@
<string name="settings.playback.bluetooth_all">Minden Bluetooth eszköz</string> <string name="settings.playback.bluetooth_all">Minden Bluetooth eszköz</string>
<string name="settings.playback.bluetooth_a2dp">Csak audio (A2DP) eszközök</string> <string name="settings.playback.bluetooth_a2dp">Csak audio (A2DP) eszközök</string>
<string name="settings.playback.bluetooth_disabled">Kikapcsolva</string> <string name="settings.playback.bluetooth_disabled">Kikapcsolva</string>
<string name="settings.playback.single_button_bluetooth_device">Egy gombos Lejátszás/Szünet a Bluetooth eszközökön</string>
<string name="settings.playback.single_button_bluetooth_device_summary">Régebbi Bluetooth eszközök esetén segíthet, ha a Lejátszás/Szünet nem működik megfelelően</string>
<string name="settings.debug.title">Hibakeresési lehetőségek</string> <string name="settings.debug.title">Hibakeresési lehetőségek</string>
<string name="settings.debug.log_to_file">Hibakeresési napló írása fájlba</string> <string name="settings.debug.log_to_file">Hibakeresési napló írása fájlba</string>
<string name="settings.debug.log_path">A naplófájlok elérhetőek a következő helyen: %1$s/%2$s</string> <string name="settings.debug.log_path">A naplófájlok elérhetőek a következő helyen: %1$s/%2$s</string>
@ -444,7 +458,6 @@
<string name="server_menu.move_down">Lejjebb mozgat</string> <string name="server_menu.move_down">Lejjebb mozgat</string>
<string name="server_editor.authentication">Bejelentkezés</string> <string name="server_editor.authentication">Bejelentkezés</string>
<string name="server_editor.advanced">Haladó beállítások</string> <string name="server_editor.advanced">Haladó beállítások</string>
<plurals name="select_album_n_songs"> <plurals name="select_album_n_songs">
<item quantity="one">1 dal</item> <item quantity="one">1 dal</item>
<item quantity="other">%d dal</item> <item quantity="other">%d dal</item>

View File

@ -289,7 +289,6 @@
<string name="settings.show_track_number">Visualizza numero traccia</string> <string name="settings.show_track_number">Visualizza numero traccia</string>
<string name="settings.show_track_number_summary">Includi numero traccia quando visualizzi una canzone</string> <string name="settings.show_track_number_summary">Includi numero traccia quando visualizzi una canzone</string>
<string name="settings.test_connection_title">Prova Connessione</string> <string name="settings.test_connection_title">Prova Connessione</string>
<string name="settings.testing_connection">Collaudo connessione&#8230;</string>
<string name="settings.testing_ok">Connessione OK</string> <string name="settings.testing_ok">Connessione OK</string>
<string name="settings.testing_unlicensed">Connessione OK. Server senza licenza.</string> <string name="settings.testing_unlicensed">Connessione OK. Server senza licenza.</string>
<string name="settings.theme_light">Chiaro</string> <string name="settings.theme_light">Chiaro</string>

View File

@ -266,6 +266,8 @@
<string name="settings.playback.resume_play_on_headphones_plug.summary">Het afspelen wordt hervat zodra er een hoofdtelefoon wordt aangesloten.</string> <string name="settings.playback.resume_play_on_headphones_plug.summary">Het afspelen wordt hervat zodra er een hoofdtelefoon wordt aangesloten.</string>
<string name="settings.screen_lit_summary">Door het scherm aan te houden tijdens het downloaden, wordt de downloadsnelheid verhoogd.</string> <string name="settings.screen_lit_summary">Door het scherm aan te houden tijdens het downloaden, wordt de downloadsnelheid verhoogd.</string>
<string name="settings.screen_lit_title">Scherm aan houden</string> <string name="settings.screen_lit_title">Scherm aan houden</string>
<string name="settings.scrobble_summary">Let op: stel je gebruikersnaam en wachtwoord van je scrobble-dienst(en) in op je Subsonic-server</string>
<string name="settings.scrobble_title">Scrobbelen</string>
<string name="settings.search_1">1</string> <string name="settings.search_1">1</string>
<string name="settings.search_10">10</string> <string name="settings.search_10">10</string>
<string name="settings.search_100">100</string> <string name="settings.search_100">100</string>
@ -307,7 +309,6 @@
<string name="settings.show_track_number">Itemnummer tonen</string> <string name="settings.show_track_number">Itemnummer tonen</string>
<string name="settings.show_track_number_summary">Itemnummer tonen tijdens tonen van nummers</string> <string name="settings.show_track_number_summary">Itemnummer tonen tijdens tonen van nummers</string>
<string name="settings.test_connection_title">Verbinding testen</string> <string name="settings.test_connection_title">Verbinding testen</string>
<string name="settings.testing_connection">Bezig met testen van verbinding&#8230;</string>
<string name="settings.testing_ok">Verbinding is goed</string> <string name="settings.testing_ok">Verbinding is goed</string>
<string name="settings.testing_unlicensed">Verbinding is goed; geen serverlicentie.</string> <string name="settings.testing_unlicensed">Verbinding is goed; geen serverlicentie.</string>
<string name="settings.theme_light">Licht</string> <string name="settings.theme_light">Licht</string>
@ -457,6 +458,7 @@
<string name="server_menu.move_down">Omlaag</string> <string name="server_menu.move_down">Omlaag</string>
<string name="server_editor.authentication">Authenticatie</string> <string name="server_editor.authentication">Authenticatie</string>
<string name="server_editor.advanced">Geavanceerde instellingen</string> <string name="server_editor.advanced">Geavanceerde instellingen</string>
<string name="server_editor.disabled_feature">Eén of meerdere functies zijn uitgeschakeld omdat de server ze niet ondersteunt.\nVoer deze test later opnieuw uit.</string>
<plurals name="select_album_n_songs"> <plurals name="select_album_n_songs">
<item quantity="one">1 nummer</item> <item quantity="one">1 nummer</item>

View File

@ -295,7 +295,6 @@
<string name="settings.show_track_number">Wyświetlaj numer utworu</string> <string name="settings.show_track_number">Wyświetlaj numer utworu</string>
<string name="settings.show_track_number_summary">Dołącza numer utworu podczas wyświetlania utworu</string> <string name="settings.show_track_number_summary">Dołącza numer utworu podczas wyświetlania utworu</string>
<string name="settings.test_connection_title">Testuj połączenie</string> <string name="settings.test_connection_title">Testuj połączenie</string>
<string name="settings.testing_connection">Trwa testowanie połączenia&#8230;</string>
<string name="settings.testing_ok">Połączenie jest OK</string> <string name="settings.testing_ok">Połączenie jest OK</string>
<string name="settings.testing_unlicensed">Połączenie jest OK. Brak licencji na serwerze.</string> <string name="settings.testing_unlicensed">Połączenie jest OK. Brak licencji na serwerze.</string>
<string name="settings.theme_light">Jasny</string> <string name="settings.theme_light">Jasny</string>

View File

@ -297,7 +297,6 @@
<string name="settings.show_track_number">Mostrar o Número da Faixa</string> <string name="settings.show_track_number">Mostrar o Número da Faixa</string>
<string name="settings.show_track_number_summary">Incluir o número da faixa quando mostrando uma música</string> <string name="settings.show_track_number_summary">Incluir o número da faixa quando mostrando uma música</string>
<string name="settings.test_connection_title">Teste de Conexão</string> <string name="settings.test_connection_title">Teste de Conexão</string>
<string name="settings.testing_connection">Testando conexão&#8230;</string>
<string name="settings.testing_ok">Conexão OK</string> <string name="settings.testing_ok">Conexão OK</string>
<string name="settings.testing_unlicensed">Conexão OK. Servidor não licenciado.</string> <string name="settings.testing_unlicensed">Conexão OK. Servidor não licenciado.</string>
<string name="settings.theme_light">Claro</string> <string name="settings.theme_light">Claro</string>
@ -447,7 +446,6 @@
<string name="server_menu.move_down">Para baixo</string> <string name="server_menu.move_down">Para baixo</string>
<string name="server_editor.authentication">Autenticação</string> <string name="server_editor.authentication">Autenticação</string>
<string name="server_editor.advanced">Configurações avançadas</string> <string name="server_editor.advanced">Configurações avançadas</string>
<plurals name="select_album_n_songs"> <plurals name="select_album_n_songs">
<item quantity="one">%d música</item> <item quantity="one">%d música</item>
<item quantity="other">%d músicas</item> <item quantity="other">%d músicas</item>

View File

@ -295,7 +295,6 @@
<string name="settings.show_track_number">Mostrar o Número da Faixa</string> <string name="settings.show_track_number">Mostrar o Número da Faixa</string>
<string name="settings.show_track_number_summary">Incluir o número da faixa quando mostrando uma música</string> <string name="settings.show_track_number_summary">Incluir o número da faixa quando mostrando uma música</string>
<string name="settings.test_connection_title">Teste de Conexão</string> <string name="settings.test_connection_title">Teste de Conexão</string>
<string name="settings.testing_connection">Testando conexão&#8230;</string>
<string name="settings.testing_ok">Conexão OK</string> <string name="settings.testing_ok">Conexão OK</string>
<string name="settings.testing_unlicensed">Conexão OK. Servidor não licenciado.</string> <string name="settings.testing_unlicensed">Conexão OK. Servidor não licenciado.</string>
<string name="settings.theme_light">Claro</string> <string name="settings.theme_light">Claro</string>

View File

@ -290,7 +290,6 @@
<string name="settings.show_track_number">Показать номер трека</string> <string name="settings.show_track_number">Показать номер трека</string>
<string name="settings.show_track_number_summary">Включить номер дорожки при отображении песни</string> <string name="settings.show_track_number_summary">Включить номер дорожки при отображении песни</string>
<string name="settings.test_connection_title">Тестовое соединение</string> <string name="settings.test_connection_title">Тестовое соединение</string>
<string name="settings.testing_connection">Тестирование соединения&#8230;</string>
<string name="settings.testing_ok">Успешное соединение</string> <string name="settings.testing_ok">Успешное соединение</string>
<string name="settings.testing_unlicensed">Успешное соединение. Сервер нелицензионный.</string> <string name="settings.testing_unlicensed">Успешное соединение. Сервер нелицензионный.</string>
<string name="settings.theme_light">Светлая</string> <string name="settings.theme_light">Светлая</string>

View File

@ -220,7 +220,6 @@
<string name="settings.show_notification">显示通知</string> <string name="settings.show_notification">显示通知</string>
<string name="settings.show_notification_always">总是显示通知</string> <string name="settings.show_notification_always">总是显示通知</string>
<string name="settings.test_connection_title">测试连接</string> <string name="settings.test_connection_title">测试连接</string>
<string name="settings.testing_connection">测试连接&#8230;</string>
<string name="settings.testing_ok">连接正常</string> <string name="settings.testing_ok">连接正常</string>
<string name="settings.testing_unlicensed">连接正常, 服务器未授权。</string> <string name="settings.testing_unlicensed">连接正常, 服务器未授权。</string>
<string name="settings.theme_title">主题</string> <string name="settings.theme_title">主题</string>
@ -301,7 +300,6 @@
<string name="server_menu.move_down">下移</string> <string name="server_menu.move_down">下移</string>
<string name="server_editor.authentication">认证</string> <string name="server_editor.authentication">认证</string>
<string name="server_editor.advanced">高级设置</string> <string name="server_editor.advanced">高级设置</string>
<string name="api.subsonic.not_authenticated">用户名或密码错误</string> <string name="api.subsonic.not_authenticated">用户名或密码错误</string>
<string name="api.subsonic.param_missing">缺少必需的参数。</string> <string name="api.subsonic.param_missing">缺少必需的参数。</string>
<string name="api.subsonic.requested_data_was_not_found">未找到请求的数据。</string> <string name="api.subsonic.requested_data_was_not_found">未找到请求的数据。</string>

View File

@ -311,7 +311,6 @@
<string name="settings.show_track_number">Show Track Number</string> <string name="settings.show_track_number">Show Track Number</string>
<string name="settings.show_track_number_summary">Include track number when displaying a song</string> <string name="settings.show_track_number_summary">Include track number when displaying a song</string>
<string name="settings.test_connection_title">Test Connection</string> <string name="settings.test_connection_title">Test Connection</string>
<string name="settings.testing_connection">Testing connection&#8230;</string>
<string name="settings.testing_ok">Connection is OK</string> <string name="settings.testing_ok">Connection is OK</string>
<string name="settings.testing_unlicensed">Connection is OK. Server unlicensed.</string> <string name="settings.testing_unlicensed">Connection is OK. Server unlicensed.</string>
<string name="settings.theme_light">Light</string> <string name="settings.theme_light">Light</string>
@ -462,6 +461,7 @@
<string name="server_menu.move_down">Move down</string> <string name="server_menu.move_down">Move down</string>
<string name="server_editor.authentication">Authentication</string> <string name="server_editor.authentication">Authentication</string>
<string name="server_editor.advanced">Advanced settings</string> <string name="server_editor.advanced">Advanced settings</string>
<string name="server_editor.disabled_feature">One or more features were disabled because the server doesn\'t support them.\nYou can run this test again anytime.</string>
<plurals name="select_album_n_songs"> <plurals name="select_album_n_songs">
<item quantity="one">1 song</item> <item quantity="one">1 song</item>