Merge branch 'develop' into AndroidAuto
This commit is contained in:
commit
be4ffc2c7e
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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í…</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>
|
||||||
|
|
|
@ -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…</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>
|
||||||
|
|
|
@ -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…</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>
|
||||||
|
|
|
@ -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…</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>
|
||||||
|
|
|
@ -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…</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>
|
||||||
|
|
|
@ -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…</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>
|
||||||
|
|
|
@ -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…</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>
|
||||||
|
|
|
@ -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…</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>
|
||||||
|
|
|
@ -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…</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>
|
||||||
|
|
|
@ -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…</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>
|
||||||
|
|
|
@ -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">Тестирование соединения…</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>
|
||||||
|
|
|
@ -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">测试连接…</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>
|
||||||
|
|
|
@ -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…</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>
|
||||||
|
|
Loading…
Reference in New Issue