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()
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'

View File

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

View File

@ -10,7 +10,7 @@ ext.versions = [
androidxcore : "1.5.0",
ktlint : "0.37.1",
ktlintGradle : "9.2.1",
detekt : "1.17.0",
detekt : "1.17.1",
jacoco : "0.8.7",
preferences : "1.1.1",
media : "1.3.1",
@ -20,16 +20,16 @@ ext.versions = [
androidSupportDesign : "1.3.0",
constraintLayout : "2.0.4",
multidex : "2.0.1",
room : "2.2.6",
kotlin : "1.4.32",
kotlinxCoroutines : "1.4.3-native-mt",
room : "2.3.0",
kotlin : "1.5.10",
kotlinxCoroutines : "1.5.0-native-mt",
viewModelKtx : "2.2.0",
retrofit : "2.6.4",
jackson : "2.9.5",
okhttp : "3.12.13",
twitterSerial : "0.1.6",
koin : "2.2.2",
koin : "3.0.2",
picasso : "2.71828",
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
}
kotlinOptions {
jvmTarget = "1.8"
}
compileOptions {
// Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8

View File

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

View File

@ -162,7 +162,8 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur
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;
swipeVelocity = swipeDistance;

View File

@ -23,7 +23,7 @@ import kotlin.Lazy;
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
{

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()
{
@Override
public void onClick(DialogInterface dialog, int i)
{
dialog.dismiss();
}
}).show();
new AlertDialog.Builder(context)
.setIcon(icon)
.setTitle(titleId)
.setMessage(message)
.setPositiveButton(R.string.common_ok, (dialog, i) -> dialog.dismiss())
.show();
}

View File

@ -28,8 +28,10 @@ import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager
import com.google.android.material.navigation.NavigationView
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.data.ActiveServerProvider
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.fragment.OnBackPressedHandler
@ -368,10 +370,18 @@ class NavigationActivity : AppCompatActivity() {
}
private fun setMenuForServerSetting() {
val visibility = !isOffline()
chatMenuItem?.isVisible = visibility
bookmarksMenuItem?.isVisible = visibility
sharesMenuItem?.isVisible = visibility
podcastsMenuItem?.isVisible = visibility
if (isOffline()) {
chatMenuItem?.isVisible = false
bookmarksMenuItem?.isVisible = false
sharesMenuItem?.isVisible = false
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
*/
@Database(entities = [ServerSetting::class], version = 2)
@Database(entities = [ServerSetting::class], version = 3)
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 = "ldapSupport") var ldapSupport: Boolean,
@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 (
-1, 0, "", "", "", "", false, false, false, null, null

View File

@ -2,11 +2,12 @@ package org.moire.ultrasonic.di
import androidx.room.Room
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.dsl.module
import org.moire.ultrasonic.data.AppDatabase
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.util.Util
@ -25,6 +26,7 @@ val appPermanentStorage = module {
"ultrasonic-database"
)
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.fallbackToDestructiveMigrationOnDowngrade()
.build()
}

View File

@ -6,7 +6,6 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.MusicDirectory
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
* TODO: Check refresh is working
*/
@KoinApiExtension
class AlbumListFragment : GenericListFragment<MusicDirectory.Entry, AlbumRowAdapter>() {
/**

View File

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

View File

@ -3,7 +3,6 @@ package org.moire.ultrasonic.fragment
import android.os.Bundle
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.Artist
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
*/
@KoinApiExtension
class ArtistListFragment : GenericListFragment<Artist, ArtistRowAdapter>() {
/**

View File

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

View File

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

View File

@ -11,23 +11,28 @@ import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textfield.TextInputLayout
import java.io.IOException
import java.net.MalformedURLException
import java.net.URL
import java.util.Locale
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.R
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
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.ServerSetting
import org.moire.ultrasonic.service.ApiCallResponseChecker
import org.moire.ultrasonic.service.MusicServiceFactory
import org.moire.ultrasonic.service.SubsonicRESTException
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.ErrorDialog
import org.moire.ultrasonic.util.ModalBackgroundTask
import org.moire.ultrasonic.util.Util
import retrofit2.Response
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
*/
private fun testConnection() {
val task: ModalBackgroundTask<Boolean> = object : ModalBackgroundTask<Boolean>(
val task: ModalBackgroundTask<String> = object : ModalBackgroundTask<String>(
activity,
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)
override fun doInBackground(): Boolean {
updateProgress(R.string.settings_testing_connection)
override fun doInBackground(): String {
currentServerSetting!!.chatSupport = null
currentServerSetting!!.bookmarkSupport = null
currentServerSetting!!.shareSupport = null
currentServerSetting!!.podcastSupport = null
updateProgress(getProgress())
val configuration = SubsonicClientConfiguration(
currentServerSetting!!.url,
currentServerSetting!!.userName,
@ -330,17 +362,62 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
pingResponse = subsonicApiClient.api.ping().execute()
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()
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) {
if (licenseValid) {
Util.toast(activity, R.string.settings_testing_ok)
} else {
Util.toast(activity, R.string.settings_testing_unlicensed)
override fun done(responseString: String) {
var dialogText = responseString
if (arrayOf(
currentServerSetting!!.chatSupport,
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) {
@ -359,6 +436,18 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
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
*/

View File

@ -13,8 +13,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import org.koin.android.ext.android.inject
import org.koin.android.viewmodel.ext.android.viewModel
import org.koin.core.component.KoinApiExtension
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider
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 TA: The Adapter to use (must extend GenericRowAdapter)
*/
@KoinApiExtension
abstract class GenericListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> : Fragment() {
internal val activeServerProvider: ActiveServerProvider by inject()
internal val serverSettingsModel: ServerSettingsModel by viewModel()

View File

@ -15,7 +15,6 @@ import java.net.UnknownHostException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinApiExtension
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
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
*/
@KoinApiExtension
open class GenericListModel(application: Application) :
AndroidViewModel(application), KoinComponent {

View File

@ -15,7 +15,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
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.data.ActiveServerProvider
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.launch
import org.koin.android.ext.android.inject
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
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.
* TODO: Refactor this fragment and model to extend the GenericListFragment
*/
@KoinApiExtension
class TrackCollectionFragment : Fragment() {
private var refreshAlbumListView: SwipeRefreshLayout? = null

View File

@ -13,7 +13,6 @@ import androidx.lifecycle.MutableLiveData
import java.util.LinkedList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.MusicDirectory
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
* TODO: Refactor this model to extend the GenericListModel
*/
@KoinApiExtension
class TrackCollectionModel(application: Application) : GenericListModel(application) {
private val allSongsId = "-1"

View File

@ -18,7 +18,8 @@ import timber.log.Timber
class AudioFocusHandler(private val context: Context) {
// TODO: This is a circular reference, try to remove it
// 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 {
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager

View File

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

View File

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

View File

@ -25,7 +25,6 @@ import java.net.URLEncoder
import java.util.Locale
import kotlin.math.abs
import kotlin.math.max
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.audiofx.EqualizerController
import org.moire.ultrasonic.audiofx.VisualizerController
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
*/
@KoinApiExtension
class LocalMediaPlayer(
private val audioFocusHandler: AudioFocusHandler,
private val context: Context

View File

@ -7,9 +7,9 @@
package org.moire.ultrasonic.service
import android.content.Intent
import org.koin.core.component.KoinApiExtension
import org.koin.java.KoinJavaComponent.get
import org.koin.java.KoinJavaComponent.inject
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.component.inject
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.data.ActiveServerProvider
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
* to control the Media Player implementation.
*/
@KoinApiExtension
@Suppress("TooManyFunctions")
class MediaPlayerController(
private val downloadQueueSerializer: DownloadQueueSerializer,
@ -38,7 +37,7 @@ class MediaPlayerController(
private val downloader: Downloader,
private val shufflePlayBuffer: ShufflePlayBuffer,
private val localMediaPlayer: LocalMediaPlayer
) {
) : KoinComponent {
private var created = false
var suggestedPlaylistName: String? = null
@ -46,8 +45,8 @@ class MediaPlayerController(
var showVisualization = false
private var autoPlayStart = false
private val jukeboxMediaPlayer = inject(JukeboxMediaPlayer::class.java).value
private val activeServerProvider = inject(ActiveServerProvider::class.java).value
private val jukeboxMediaPlayer: JukeboxMediaPlayer by inject()
private val activeServerProvider: ActiveServerProvider by inject()
fun onCreate() {
if (created) return
@ -462,7 +461,8 @@ class MediaPlayerController(
@Suppress("TooGenericExceptionCaught") // The interface throws only generic exceptions
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
val song = localMediaPlayer.currentPlaying!!.song
song.userRating = rating

View File

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

View File

@ -22,7 +22,8 @@ import java.util.Locale
import java.util.Random
import java.util.concurrent.TimeUnit
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.domain.Artist
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..
// Simplify them :)
@Suppress("TooManyFunctions")
class OfflineMusicService : MusicService {
private val activeServerProvider = inject(
ActiveServerProvider::class.java
)
class OfflineMusicService : MusicService, KoinComponent {
private val activeServerProvider: ActiveServerProvider by inject()
override fun getIndexes(musicFolderId: String?, refresh: Boolean): Indexes {
val artists: MutableList<Artist> = ArrayList()
@ -67,8 +66,8 @@ class OfflineMusicService : MusicService {
val ignoredArticlesString = "The El La Los Las Le Les"
val ignoredArticles = COMPILE.split(ignoredArticlesString)
artists.sortWith { lhsArtist, rhsArtist ->
var lhs = lhsArtist.name!!.toLowerCase(Locale.ROOT)
var rhs = rhsArtist.name!!.toLowerCase(Locale.ROOT)
var lhs = lhsArtist.name!!.lowercase(Locale.ROOT)
var rhs = rhsArtist.name!!.lowercase(Locale.ROOT)
val lhs1 = lhs[0]
val rhs1 = rhs[0]
if (Character.isDigit(lhs1) && !Character.isDigit(rhs1)) {
@ -79,13 +78,13 @@ class OfflineMusicService : MusicService {
}
for (article in ignoredArticles) {
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) {
lhs = lhs.substring(article.length + 1)
}
index = rhs.indexOf(
String.format(Locale.ROOT, "%s ", article.toLowerCase(Locale.ROOT))
String.format(Locale.ROOT, "%s ", article.lowercase(Locale.ROOT))
)
if (index == 0) {
rhs = rhs.substring(article.length + 1)
@ -253,7 +252,7 @@ class OfflineMusicService : MusicService {
@Throws(Exception::class)
override fun createPlaylist(id: String, name: String, entries: List<MusicDirectory.Entry>) {
val playlistFile =
FileUtil.getPlaylistFile(activeServerProvider.value.getActiveServer().name, name)
FileUtil.getPlaylistFile(activeServerProvider.getActiveServer().name, name)
val fw = FileWriter(playlistFile)
val bw = BufferedWriter(fw)
try {
@ -668,10 +667,10 @@ class OfflineMusicService : MusicService {
}
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 nameParts = COMPILE.split(
name!!.toLowerCase(Locale.ROOT)
name!!.lowercase(Locale.ROOT)
)
var closeness = 0
for (queryPart in queryParts) {

View File

@ -5,7 +5,6 @@ import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import java.util.Collections
import java.util.LinkedList
import org.koin.core.component.KoinApiExtension
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
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
*/
@Suppress("LongParameterList")
@KoinApiExtension
class DownloadHandler(
val mediaPlayerController: MediaPlayerController,
val networkAndStorageChecker: NetworkAndStorageChecker

View File

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

View File

@ -24,8 +24,9 @@ import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.LayoutInflater
import android.widget.Checkable
import org.koin.java.KoinJavaComponent.get
import org.koin.java.KoinJavaComponent.inject
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.component.inject
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.MusicDirectory
@ -42,7 +43,7 @@ import timber.log.Timber
/**
* 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
private set
@ -55,10 +56,9 @@ class SongView(context: Context) : UpdateView(context), Checkable {
private var downloadFile: DownloadFile? = null
private var playing = false
private var viewHolder: SongViewHolder? = null
private val useFiveStarRating: Boolean =
get(FeatureStorage::class.java).isFeatureEnabled(Feature.FIVE_STAR_RATING)
private val mediaPlayerControllerLazy = inject(MediaPlayerController::class.java)
private val features: FeatureStorage = get()
private val useFiveStarRating: Boolean = features.isFeatureEnabled(Feature.FIVE_STAR_RATING)
private val mediaPlayerController: MediaPlayerController by inject()
fun setLayout(song: MusicDirectory.Entry) {
@ -96,7 +96,7 @@ class SongView(context: Context) : UpdateView(context), Checkable {
updateBackground()
entry = song
downloadFile = mediaPlayerControllerLazy.value.getDownloadFileForSong(song)
downloadFile = mediaPlayerController.getDownloadFileForSong(song)
val artist = StringBuilder(60)
var bitRate: String? = null
@ -223,7 +223,7 @@ class SongView(context: Context) : UpdateView(context), Checkable {
public override fun update() {
updateBackground()
downloadFile = mediaPlayerControllerLazy.value.getDownloadFileForSong(entry)
downloadFile = mediaPlayerController.getDownloadFileForSong(entry)
updateDownloadStatus(downloadFile!!)
@ -254,7 +254,7 @@ class SongView(context: Context) : UpdateView(context), Checkable {
if (rating > 4) starDrawable else starHollowDrawable
)
val playing = mediaPlayerControllerLazy.value.currentPlaying === downloadFile
val playing = mediaPlayerController.currentPlaying === downloadFile
if (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_summary">Připojovat číslo skladby při zobrazování skladby</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_unlicensed">Připojení je v pořádku. Server bez licence.</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_editor.authentication">Ověření</string>
<string name="server_editor.advanced">Rozšířené možnosti</string>
<plurals name="select_album_n_songs">
<item quantity="one">1 skladba</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_summary">Titel mit Nummer anzeigen</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_unlicensed">Verbindung OK, Server nicht lizensiert.</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_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.testing_connection">Comprobado conexión&#8230;</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.theme_light">Claro</string>
@ -459,6 +458,7 @@
<string name="server_menu.move_down">Bajar</string>
<string name="server_editor.authentication">Autenticación</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">
<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_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.testing_connection">Connexion en cours de test&#8230;</string>
<string name="settings.testing_ok">Connexion correcte</string>
<string name="settings.testing_unlicensed">Connexion correcte. Serveur sans licence.</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_editor.authentication">Authentification</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">
<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.home">Ultrasonic főoldal</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_channels.empty">Nincsenek podcast-csatornák regisztrálva</string>
<string name="button_bar.podcasts">Podcast</string>
@ -32,8 +39,11 @@
<string name="common.name">Név</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.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_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_shuffled">Véletlen sorrendű lejátszás</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.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.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_10">10</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_summary">Dalok sorszámának megjeleníté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_unlicensed">Kapcsolat OK! A kiszolgálónak nincs licence!</string>
<string name="settings.theme_light">Világos</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.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>
@ -401,6 +413,8 @@
<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_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.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>
@ -444,7 +458,6 @@
<string name="server_menu.move_down">Lejjebb mozgat</string>
<string name="server_editor.authentication">Bejelentkezés</string>
<string name="server_editor.advanced">Haladó beállítások</string>
<plurals name="select_album_n_songs">
<item quantity="one">1 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_summary">Includi numero traccia quando visualizzi una canzone</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_unlicensed">Connessione OK. Server senza licenza.</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.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.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_10">10</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_summary">Itemnummer tonen tijdens tonen van nummers</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_unlicensed">Verbinding is goed; geen serverlicentie.</string>
<string name="settings.theme_light">Licht</string>
@ -457,6 +458,7 @@
<string name="server_menu.move_down">Omlaag</string>
<string name="server_editor.authentication">Authenticatie</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">
<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_summary">Dołącza numer utworu podczas wyświetlania utworu</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_unlicensed">Połączenie jest OK. Brak licencji na serwerze.</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_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.testing_connection">Testando conexão&#8230;</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.theme_light">Claro</string>
@ -447,7 +446,6 @@
<string name="server_menu.move_down">Para baixo</string>
<string name="server_editor.authentication">Autenticação</string>
<string name="server_editor.advanced">Configurações avançadas</string>
<plurals name="select_album_n_songs">
<item quantity="one">%d música</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_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.testing_connection">Testando conexão&#8230;</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.theme_light">Claro</string>

View File

@ -290,7 +290,6 @@
<string name="settings.show_track_number">Показать номер трека</string>
<string name="settings.show_track_number_summary">Включить номер дорожки при отображении песни</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_unlicensed">Успешное соединение. Сервер нелицензионный.</string>
<string name="settings.theme_light">Светлая</string>

View File

@ -220,7 +220,6 @@
<string name="settings.show_notification">显示通知</string>
<string name="settings.show_notification_always">总是显示通知</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_unlicensed">连接正常, 服务器未授权。</string>
<string name="settings.theme_title">主题</string>
@ -301,7 +300,6 @@
<string name="server_menu.move_down">下移</string>
<string name="server_editor.authentication">认证</string>
<string name="server_editor.advanced">高级设置</string>
<string name="api.subsonic.not_authenticated">用户名或密码错误</string>
<string name="api.subsonic.param_missing">缺少必需的参数。</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_summary">Include track number when displaying a song</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_unlicensed">Connection is OK. Server unlicensed.</string>
<string name="settings.theme_light">Light</string>
@ -462,6 +461,7 @@
<string name="server_menu.move_down">Move down</string>
<string name="server_editor.authentication">Authentication</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">
<item quantity="one">1 song</item>