Merge pull request #50 from jellyfin/extract-connection-handling

Split up web app and connection handling, use view binding for setup layout
This commit is contained in:
Niels van Velzen
2020-08-24 22:08:50 +02:00
committed by GitHub
31 changed files with 303 additions and 240 deletions
+4
View File
@@ -31,6 +31,10 @@ android {
aaptOptions.cruncherEnabled = false // Disable png crunching
}
}
@Suppress("UnstableApiUsage")
buildFeatures {
viewBinding = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
@@ -7,24 +7,10 @@ import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.webkit.*
import android.widget.Button
import android.widget.EditText
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.view.doOnNextLayout
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import org.jellyfin.mobile.bridge.Commands.triggerInputManagerAction
import org.jellyfin.mobile.bridge.NativeInterface
@@ -32,6 +18,7 @@ import org.jellyfin.mobile.bridge.NativePlayer
import org.jellyfin.mobile.cast.Chromecast
import org.jellyfin.mobile.utils.*
import org.jellyfin.mobile.utils.Constants.INPUT_MANAGER_COMMAND_BACK
import org.jellyfin.mobile.webapp.ConnectionHelper
import timber.log.Timber
import java.io.Reader
@@ -40,6 +27,10 @@ class WebappActivity : AppCompatActivity(), WebViewController {
val appPreferences: AppPreferences by lazy { AppPreferences(this) }
val httpClient = OkHttpClient()
val chromecast = Chromecast()
private val connectionHelper = ConnectionHelper(this)
val rootView: FrameLayout by lazyView(R.id.root_view)
val webView: WebView by lazyView(R.id.web_view)
var serviceBinder: RemotePlayerService.ServiceBinder? = null
private set
@@ -54,15 +45,6 @@ class WebappActivity : AppCompatActivity(), WebViewController {
}
}
private var cachedInstanceUrl: HttpUrl? = null
private var connected = false
private val rootView: FrameLayout by lazyView(R.id.root_view)
private val webView: WebView by lazyView(R.id.web_view)
private val serverSetupLayout: View by lazy { layoutInflater.inflate(R.layout.connect_server, rootView, false) }
private val hostInput: EditText by lazy { serverSetupLayout.findViewById<EditText>(R.id.host_input) }
private val connectButton: Button by lazy { serverSetupLayout.findViewById<Button>(R.id.connect_button) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -74,8 +56,7 @@ class WebappActivity : AppCompatActivity(), WebViewController {
webView.initialize()
// Load content
cachedInstanceUrl = appPreferences.instanceUrl?.toHttpUrlOrNull()
loadOrShowSetup()
connectionHelper.initialize()
chromecast.initializePlugin(this)
}
@@ -91,16 +72,16 @@ class WebappActivity : AppCompatActivity(), WebViewController {
path.endsWith(Constants.INDEX_PATH) -> {
val patchedIndex = loadPatchedIndex(httpClient, url.toString())
if (patchedIndex != null) {
runOnUiThread { onConnectedToWebapp() }
runOnUiThread { connectionHelper.onConnectedToWebapp() }
patchedIndex
} else {
runOnUiThread { onErrorReceived() }
runOnUiThread { connectionHelper.onErrorReceived() }
emptyResponse
}
}
path.contains("native") -> loadAsset("native/${url.lastPathSegment}")
path.endsWith("web/selectserver.html") -> {
runOnUiThread { onSelectServer() }
runOnUiThread { connectionHelper.onSelectServer() }
emptyResponse
}
else -> null
@@ -111,7 +92,7 @@ class WebappActivity : AppCompatActivity(), WebViewController {
val errorMessage = errorResponse.data?.run { bufferedReader().use(Reader::readText) }
Timber.e("Received WebView HTTP %d error: %s", errorResponse.statusCode, errorMessage)
if (request.url.path?.endsWith(Constants.INDEX_PATH) != false)
runOnUiThread { onErrorReceived() }
runOnUiThread { connectionHelper.onErrorReceived() }
}
override fun onReceivedError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceError) {
@@ -127,78 +108,8 @@ class WebappActivity : AppCompatActivity(), WebViewController {
addJavascriptInterface(NativePlayer(this@WebappActivity), "NativePlayer")
}
private fun loadOrShowSetup() {
cachedInstanceUrl.let { url ->
if (url != null) {
webView.isVisible = true
webView.loadUrl(url.resolve(Constants.INDEX_PATH).toString())
} else {
webView.isVisible = false
showServerSetup()
}
}
}
private fun showServerSetup() {
rootView.addView(serverSetupLayout)
hostInput.setText(appPreferences.instanceUrl)
hostInput.setSelection(hostInput.length())
hostInput.setOnEditorActionListener { _, action, event ->
when {
action == EditorInfo.IME_ACTION_DONE || event.keyCode == KeyEvent.KEYCODE_ENTER -> {
connect()
true
}
else -> false
}
}
connectButton.setOnClickListener {
connect()
}
// Show keyboard
serverSetupLayout.doOnNextLayout {
hostInput.postDelayed(25) {
hostInput.requestFocus()
getSystemService<InputMethodManager>()?.showSoftInput(hostInput, InputMethodManager.SHOW_IMPLICIT)
}
}
}
private fun connect() {
hostInput.isEnabled = false
connectButton.isEnabled = false
lifecycleScope.launch {
val httpUrl = checkServerUrlAndConnection(hostInput.text.toString())
if (httpUrl != null) {
appPreferences.instanceUrl = httpUrl.toString()
cachedInstanceUrl = httpUrl
rootView.removeView(serverSetupLayout)
loadOrShowSetup()
}
hostInput.isEnabled = true
connectButton.isEnabled = true
}
}
private fun onConnectedToWebapp() {
connected = true
requestNoBatteryOptimizations()
}
private fun onSelectServer() {
cachedInstanceUrl = null
loadOrShowSetup()
}
private fun onErrorReceived() {
connected = false
appPreferences.instanceUrl = null
onSelectServer()
}
override fun loadUrl(url: String) {
if (connected) webView.loadUrl(url)
if (connectionHelper.connected) webView.loadUrl(url)
}
fun updateRemoteVolumeLevel(value: Int) {
@@ -207,13 +118,8 @@ class WebappActivity : AppCompatActivity(), WebViewController {
override fun onBackPressed() {
when {
!connected -> super.onBackPressed()
serverSetupLayout.isAttachedToWindow -> {
rootView.removeView(serverSetupLayout)
cachedInstanceUrl = appPreferences.instanceUrl?.toHttpUrlOrNull()
webView.isVisible = true
}
else -> triggerInputManagerAction(INPUT_MANAGER_COMMAND_BACK)
!connectionHelper.connected -> super.onBackPressed()
!connectionHelper.onBackPressed() -> triggerInputManagerAction(INPUT_MANAGER_COMMAND_BACK)
}
}
@@ -1,80 +0,0 @@
package org.jellyfin.mobile.utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import org.jellyfin.mobile.R
import org.jellyfin.mobile.WebappActivity
import org.jellyfin.mobile.utils.Constants.SERVER_INFO_PATH
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber
import java.io.IOException
suspend fun WebappActivity.checkServerUrlAndConnection(enteredUrl: String): HttpUrl? {
val normalizedUrl = enteredUrl.run {
if (lastOrNull() == '/') this
else "$this/"
}
val urls = when {
normalizedUrl.startsWith("http") -> listOf(normalizedUrl)
else -> listOf("https://$normalizedUrl", "http://$normalizedUrl")
}
var httpUrl: HttpUrl? = null
var serverInfoResponse: String? = null
loop@ for (url in urls) {
httpUrl = url.toHttpUrlOrNull()
if (httpUrl == null) {
toast(R.string.toast_error_invalid_format)
return null // Format is invalid, don't try any other variants
}
serverInfoResponse = fetchServerInfo(httpClient, httpUrl)
if (serverInfoResponse != null)
break@loop
}
if (httpUrl == null || serverInfoResponse == null) {
toast(getString(R.string.toast_error_cannot_connect_host, normalizedUrl))
return null
}
val isValidInstance = try {
val serverInfo = JSONObject(serverInfoResponse)
val version = serverInfo.getString("Version")
.split('.')
.mapNotNull(String::toIntOrNull)
when {
version.size != 3 -> false
version[0] == 10 && version[1] < 3 -> true // Valid old version
else -> serverInfo.getString("ProductName") == "Jellyfin Server"
}
} catch (e: JSONException) {
Timber.e(e, "Cannot get server info")
false
}
return if (isValidInstance) httpUrl else {
toast(getString(R.string.toast_error_cannot_connect_host, normalizedUrl))
null
}
}
suspend fun fetchServerInfo(httpClient: OkHttpClient, url: HttpUrl): String? {
val serverInfoUrl = url.resolve(SERVER_INFO_PATH) ?: return null
val request = httpClient.newCall(Request.Builder().url(serverInfoUrl).build())
return withContext(Dispatchers.IO) {
try {
request.execute().use { it.body?.string() }
} catch (e: IOException) {
Timber.e(e, "Cannot connect to server")
null
}
}
}
@@ -0,0 +1,210 @@
package org.jellyfin.mobile.webapp
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.webkit.WebView
import android.widget.Button
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.core.content.getSystemService
import androidx.core.view.doOnNextLayout
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import org.jellyfin.mobile.AppPreferences
import org.jellyfin.mobile.R
import org.jellyfin.mobile.WebappActivity
import org.jellyfin.mobile.databinding.ConnectServerBinding
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.Constants.SERVER_INFO_PATH
import org.jellyfin.mobile.utils.requestNoBatteryOptimizations
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber
import java.io.IOException
class ConnectionHelper(private val activity: WebappActivity) {
private val appPreferences: AppPreferences get() = activity.appPreferences
private val rootView: FrameLayout get() = activity.rootView
private val webView: WebView get() = activity.webView
private var cachedInstanceUrl: HttpUrl? = null
var connected = false
private set
private val connectServerBinding: ConnectServerBinding by lazy {
ConnectServerBinding.inflate(activity.layoutInflater, rootView, false)
}
private val serverSetupLayout: View get() = connectServerBinding.root
private val hostInput: EditText get() = connectServerBinding.hostInput
private val connectionErrorText: TextView get() = connectServerBinding.connectionErrorText
private val connectButton: Button get() = connectServerBinding.connectButton
fun initialize() {
cachedInstanceUrl = appPreferences.instanceUrl?.toHttpUrlOrNull()
loadOrShowSetup()
}
fun onConnectedToWebapp() {
connected = true
activity.requestNoBatteryOptimizations()
}
fun onSelectServer() {
cachedInstanceUrl = null
loadOrShowSetup()
}
fun onErrorReceived() {
connected = false
showConnectionError()
onSelectServer()
}
fun onBackPressed(): Boolean {
if (serverSetupLayout.isAttachedToWindow) {
rootView.removeView(serverSetupLayout)
cachedInstanceUrl = appPreferences.instanceUrl?.toHttpUrlOrNull()
webView.isVisible = true
return true
}
return false
}
private fun loadOrShowSetup() {
cachedInstanceUrl.let { url ->
if (url != null) {
webView.isVisible = true
webView.loadUrl(url.resolve(Constants.INDEX_PATH).toString())
} else {
webView.isVisible = false
showServerSetup()
}
}
}
private fun showServerSetup() {
rootView.addView(serverSetupLayout)
hostInput.setText(appPreferences.instanceUrl)
hostInput.setSelection(hostInput.length())
hostInput.setOnEditorActionListener { _, action, event ->
when {
action == EditorInfo.IME_ACTION_DONE || event.keyCode == KeyEvent.KEYCODE_ENTER -> {
connect()
true
}
else -> false
}
}
connectButton.setOnClickListener {
connect()
}
// Show keyboard
serverSetupLayout.doOnNextLayout {
hostInput.postDelayed(25) {
hostInput.requestFocus()
activity.getSystemService<InputMethodManager>()?.showSoftInput(hostInput, InputMethodManager.SHOW_IMPLICIT)
}
}
}
private fun connect() {
hostInput.isEnabled = false
connectButton.isEnabled = false
clearConnectionError()
activity.lifecycleScope.launch {
val httpUrl = checkServerUrlAndConnection(hostInput.text.toString())
if (httpUrl != null) {
appPreferences.instanceUrl = httpUrl.toString()
cachedInstanceUrl = httpUrl
rootView.removeView(serverSetupLayout)
loadOrShowSetup()
}
hostInput.isEnabled = true
connectButton.isEnabled = true
}
}
private fun showConnectionError(@StringRes errorString: Int = R.string.connection_error_cannot_connect) {
connectionErrorText.setText(errorString)
connectionErrorText.isVisible = true
}
private fun clearConnectionError() {
connectionErrorText.isVisible = false
}
private suspend fun checkServerUrlAndConnection(enteredUrl: String): HttpUrl? {
val normalizedUrl = enteredUrl.run {
if (lastOrNull() == '/') this
else "$this/"
}
val urls = when {
normalizedUrl.startsWith("http") -> listOf(normalizedUrl)
else -> listOf("https://$normalizedUrl", "http://$normalizedUrl")
}
var httpUrl: HttpUrl? = null
var serverInfoResponse: String? = null
loop@ for (url in urls) {
httpUrl = url.toHttpUrlOrNull()
if (httpUrl == null) {
showConnectionError(R.string.connection_error_invalid_format)
return null // Format is invalid, don't try any other variants
}
serverInfoResponse = fetchServerInfo(activity.httpClient, httpUrl)
if (serverInfoResponse != null)
break@loop
}
if (httpUrl == null || serverInfoResponse == null) {
showConnectionError()
return null
}
val isValidInstance = try {
val serverInfo = JSONObject(serverInfoResponse)
val version = serverInfo.getString("Version")
.split('.')
.mapNotNull(String::toIntOrNull)
when {
version.size != 3 -> false
version[0] == 10 && version[1] < 3 -> true // Valid old version
else -> serverInfo.getString("ProductName") == "Jellyfin Server"
}
} catch (e: JSONException) {
Timber.e(e, "Cannot get server info")
false
}
return if (isValidInstance) httpUrl else null
}
private suspend fun fetchServerInfo(httpClient: OkHttpClient, url: HttpUrl): String? {
val serverInfoUrl = url.resolve(SERVER_INFO_PATH) ?: return null
val request = httpClient.newCall(Request.Builder().url(serverInfoUrl).build())
return withContext(Dispatchers.IO) {
try {
request.execute().use { it.body?.string() }
} catch (e: IOException) {
Timber.e(e, "Cannot connect to server")
null
}
}
}
}
+2 -1
View File
@@ -2,7 +2,8 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:viewBindingIgnore="true">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view"
@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:viewBindingIgnore="true">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
@@ -18,4 +20,4 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
+4 -2
View File
@@ -1,12 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:windowBackground">
android:background="?android:windowBackground"
tools:viewBindingIgnore="true">
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</FrameLayout>
+19 -2
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/connect_server_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -60,6 +61,22 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/connect_title" />
<TextView
android:id="@+id/connection_error_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:fontFamily="sans-serif-medium"
android:textColor="@color/error_text_color"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/host_input"
tools:text="@string/connection_error_cannot_connect"
tools:visibility="visible" />
<Button
android:id="@+id/connect_button"
style="@style/Widget.AppCompat.Button.Colored"
@@ -70,5 +87,5 @@
android:textColor="@android:color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/host_input" />
</androidx.constraintlayout.widget.ConstraintLayout>
app:layout_constraintTop_toBottomOf="@id/connection_error_text" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -5,7 +5,8 @@
android:id="@+id/player_controls"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/playback_controls_background">
android:background="@color/playback_controls_background"
tools:viewBindingIgnore="true">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/track_title"
+2 -2
View File
@@ -10,10 +10,10 @@
<string name="battery_optimizations_message">Por favor desactiva las optimizaciones de batería para la reproducción de medios mientras la pantalla este apagada.</string>
<string name="toast_error_cannot_connect_host">No se pudo establecer la conexión a %s.
\nPor favor revisa el nombre de host y las configuaciones de tu red.</string>
<string name="toast_error_invalid_format">Host tiene un formato inválido</string>
<string name="connection_error_invalid_format">Host tiene un formato inválido</string>
<string name="toast_error_missing_scheme">Faltó el scheme (http o https)</string>
<string name="connect_button_text">Conectar</string>
<string name="host_input_hint">Host</string>
<string name="connect_to_server_title">Conectar a un Servidor</string>
<string name="app_name">Jellyfin</string>
</resources>
</resources>
+2 -2
View File
@@ -10,7 +10,7 @@
<string name="battery_optimizations_title">Vypnout systémovou správu baterie</string>
<string name="toast_error_cannot_connect_host">Připojení k %s se nezdařilo.
\nZkontrolujte název hostitele a připojení k síti.</string>
<string name="toast_error_invalid_format">Formát hostitele je neplatný</string>
<string name="connection_error_invalid_format">Formát hostitele je neplatný</string>
<string name="toast_error_missing_scheme">Chybí protokol (https nebo http)</string>
<string name="connect_button_text">Připojit</string>
<string name="host_input_hint">Hostitel</string>
@@ -27,4 +27,4 @@
<string name="playback_info_audio_streams">Audio proudy</string>
<string name="playback_info_video_streams">Video proudy</string>
<string name="playback_info_transcoding">Překódování: %b</string>
</resources>
</resources>
+2 -2
View File
@@ -14,7 +14,7 @@
<string name="connect_to_server_title">Serververbindung herstellen</string>
<string name="toast_error_cannot_connect_host">Verbindung zu %s konnte nicht hergestellt werden.
\nPrüfe deine Netzwerkverbindung.</string>
<string name="toast_error_invalid_format">Adressformat ungültig</string>
<string name="connection_error_invalid_format">Adressformat ungültig</string>
<string name="toast_error_missing_scheme">Protokoll fehlt (https oder http)</string>
<string name="pref_music_notification_always_dismissible_title">Musik Player Benachrichtigung immer verwerfbar</string>
<string name="pref_enable_exoplayer_summary">Aktiviere den experimentellen ExoPlayer Video Player, welcher mehr Videoformate und -Codecs unterstützt und besser ins Betriebssystem integriert ist</string>
@@ -27,4 +27,4 @@
<string name="playback_info_video_streams">Video-Streams</string>
<string name="playback_info_and_x_more">…und %d weitere</string>
<string name="playback_info_transcoding">Transkodiert: %b</string>
</resources>
</resources>
+2 -2
View File
@@ -12,7 +12,7 @@
<string name="app_name">Jellyfin</string>
<string name="toast_error_cannot_connect_host">No se puede establecer la conexión a %s.
\nCompruebe el nombre de host y su conexión de red.</string>
<string name="toast_error_invalid_format">El host tiene un formato no válido</string>
<string name="connection_error_invalid_format">El host tiene un formato no válido</string>
<string name="toast_error_missing_scheme">Esquema faltante (https o http)</string>
<string name="connect_button_text">Conectar</string>
<string name="host_input_hint">Host</string>
@@ -23,4 +23,4 @@
<string name="pref_category_music_player">Reproductor de Música</string>
<string name="activity_name_settings">Configuración de Jellyfin</string>
<string name="menu_item_none">Ninguno</string>
</resources>
</resources>
+2 -2
View File
@@ -10,8 +10,8 @@
<string name="battery_optimizations_message">Por favor deshabilita la optimización de la batería para la reproducción de medios mientras la pantalla está apagada.</string>
<string name="toast_error_cannot_connect_host">No se puede establecer la conexión a %s.
\nCompruebe el nombre de host y su conexión de red.</string>
<string name="toast_error_invalid_format">El host tiene un formato no válido</string>
<string name="connection_error_invalid_format">El host tiene un formato no válido</string>
<string name="toast_error_missing_scheme">Esquema faltante (https o http)</string>
<string name="connect_button_text">Conectado</string>
<string name="connect_to_server_title">Conectarse al Servidor</string>
</resources>
</resources>
+2 -2
View File
@@ -12,11 +12,11 @@
<string name="pref_music_notification_always_dismissible_title">Aseta musiikkisoittimen ilmoitukset piilotetuiksi</string>
<string name="pref_category_music_player">Musiikkisoitin</string>
<string name="activity_name_settings">Jellyfin asetukset</string>
<string name="toast_error_invalid_format">Isäntäpalvelimen muoto on virheellinen</string>
<string name="connection_error_invalid_format">Isäntäpalvelimen muoto on virheellinen</string>
<string name="host_input_hint">Isäntäpalvelin</string>
<string name="toast_error_cannot_connect_host">Yhteyttä %s ei pystytä muodostamaan.
\nTarkista isäntäpalvelimen nimi ja verkkoyhteytesi.</string>
<string name="connect_button_text">Yhdistä</string>
<string name="connect_to_server_title">Yhdistä palvelimeen</string>
<string name="app_name">Jellyfin</string>
</resources>
</resources>
+2 -2
View File
@@ -10,7 +10,7 @@
<string name="battery_optimizations_title">Désactiver les optimisations de la batterie</string>
<string name="toast_error_cannot_connect_host">La connexion à %s ne peut être établie.
\nVeuillez vérifier le nom d\'hôte et votre connexion au réseau.</string>
<string name="toast_error_invalid_format">L\'hôte a un format invalide</string>
<string name="connection_error_invalid_format">L\'hôte a un format invalide</string>
<string name="toast_error_missing_scheme">Schéma manquant (https ou http)</string>
<string name="connect_button_text">Se connecter</string>
<string name="host_input_hint">Hôte</string>
@@ -23,4 +23,4 @@
<string name="menu_item_none">Aucun</string>
<string name="pref_category_music_player">Lecteur de musique</string>
<string name="pref_music_notification_always_dismissible_title">Rendre rejetable la notification du lecteur audio</string>
</resources>
</resources>
+2 -2
View File
@@ -8,7 +8,7 @@
<string name="network_title">Engedélyezett hálózat típusok</string>
<string name="battery_optimizations_message">Ahhoz, hogy a lejátszás kikapcsolt képernyő mellett is működhessen, kapcsold ki az akkumulátor optimalizálást.</string>
<string name="battery_optimizations_title">Akkumulátor optimalizálás letiltása</string>
<string name="toast_error_invalid_format">A hoszt formátuma helytelen</string>
<string name="connection_error_invalid_format">A hoszt formátuma helytelen</string>
<string name="toast_error_missing_scheme">Hiányzó séma (https vagy http)</string>
<string name="connect_button_text">Kapcsolódás</string>
<string name="host_input_hint">Hoszt</string>
@@ -27,4 +27,4 @@
<string name="playback_info_audio_streams">Audio sávok</string>
<string name="playback_info_video_streams">Video sávok</string>
<string name="playback_info_transcoding">Transzkódolás: %b</string>
</resources>
</resources>
+2 -2
View File
@@ -21,10 +21,10 @@
<string name="battery_optimizations_title">Nonaktifkan Pengoptimalan Baterai</string>
<string name="toast_error_cannot_connect_host">Koneksi ke %s tidak dapat dibuat.
\nSilakan periksa nama host dan koneksi jaringan Anda.</string>
<string name="toast_error_invalid_format">Host memiliki format yang tidak valid</string>
<string name="connection_error_invalid_format">Host memiliki format yang tidak valid</string>
<string name="downloading">Mengunduh</string>
<string name="playback_info_and_x_more">… Dan %d lainnya</string>
<string name="playback_info_audio_streams">Aliran audio</string>
<string name="playback_info_video_streams">Aliran video</string>
<string name="playback_info_transcoding">Transcoding : %b</string>
</resources>
</resources>
+2 -2
View File
@@ -16,10 +16,10 @@
<string name="activity_name_settings">Impostazioni Jellyfin</string>
<string name="toast_error_cannot_connect_host">La connessione verso %s non può essere stabilita.
\nControllare il nome Host e la connessione di rete.</string>
<string name="toast_error_invalid_format">L\'Host non ha un formato valido</string>
<string name="connection_error_invalid_format">L\'Host non ha un formato valido</string>
<string name="toast_error_missing_scheme">Schema mancante (https o http)</string>
<string name="connect_button_text">Connetti</string>
<string name="host_input_hint">Host</string>
<string name="connect_to_server_title">Connetti al Server</string>
<string name="app_name">Jellyfin</string>
</resources>
</resources>
+2 -2
View File
@@ -9,11 +9,11 @@
<string name="battery_optimizations_title">배터리 최적화 비활성화</string>
<string name="toast_error_cannot_connect_host">%s으로의 연결이 성립되지 않았습니다.
\nhostname과 네트워크 연결 상태를 확인해주십시오.</string>
<string name="toast_error_invalid_format">호스트가 올바르지 않은 포맷을 사용합니다</string>
<string name="connection_error_invalid_format">호스트가 올바르지 않은 포맷을 사용합니다</string>
<string name="toast_error_missing_scheme">스킴이 존재하지 않음 (https 혹은 http)</string>
<string name="connect_button_text">연결</string>
<string name="host_input_hint">호스트</string>
<string name="connect_to_server_title">서버에 연결</string>
<string name="app_name">젤리핀</string>
<string name="battery_optimizations_message">화면이 꺼졌을 때의 미디어 재생을 위해 배터리 최적화 비활성화 해주십시오.</string>
</resources>
</resources>
+2 -2
View File
@@ -10,7 +10,7 @@
<string name="battery_optimizations_title">Deaktiver batterioptimaliseringer</string>
<string name="toast_error_cannot_connect_host">Tilkobling til %s kunne ikke etableres.
\nVennligst sjekk tjenernavnet og nettverksforbindelsen din.</string>
<string name="toast_error_invalid_format">Tjeneren har et ugyldig format</string>
<string name="connection_error_invalid_format">Tjeneren har et ugyldig format</string>
<string name="toast_error_missing_scheme">Mangler skjema (https eller http)</string>
<string name="connect_button_text">Koble til</string>
<string name="host_input_hint">Tjener</string>
@@ -19,4 +19,4 @@
<string name="playback_info_audio_streams">Lyd strømninh</string>
<string name="playback_info_video_streams">Video strømning</string>
<string name="menu_item_none">Ingen</string>
</resources>
</resources>
+2 -2
View File
@@ -9,7 +9,7 @@
<string name="battery_optimizations_message">Zet batterijoptimalisatie uit om media te spelen wanneer het scherm uit is.</string>
<string name="battery_optimizations_title">Batterijoptimalisaties uitschakelen</string>
<string name="app_name">Jellyfin</string>
<string name="toast_error_invalid_format">Host heeft een ongeldig formaat</string>
<string name="connection_error_invalid_format">Host heeft een ongeldig formaat</string>
<string name="host_input_hint">Host</string>
<string name="toast_error_missing_scheme">Geen schema opgegeven (https of http)</string>
<string name="toast_error_cannot_connect_host">Verbinding met %s kan niet worden opgezet.
@@ -23,4 +23,4 @@
<string name="pref_category_music_player">Muziek speler</string>
<string name="activity_name_settings">Jellyfin Instellingen</string>
<string name="menu_item_none">Geen</string>
</resources>
</resources>
+2 -2
View File
@@ -8,7 +8,7 @@
<string name="network_title">Tipuri de rețea permise</string>
<string name="battery_optimizations_message">Vă rugăm să dezactivați optimizările bateriei pentru redarea media cu ecranul stins.</string>
<string name="battery_optimizations_title">Dezactivați optimizările bateriei</string>
<string name="toast_error_invalid_format">Gazda are un format invalid</string>
<string name="connection_error_invalid_format">Gazda are un format invalid</string>
<string name="toast_error_missing_scheme">Lipsește protocolul (https sau http)</string>
<string name="connect_button_text">Conectare</string>
<string name="host_input_hint">Gazdă</string>
@@ -16,4 +16,4 @@
<string name="app_name">Jellyfin</string>
<string name="toast_error_cannot_connect_host">Conectarea la %s nu poate realizată.
\nVerificați numele gazdei și conexiunea de rețea.</string>
</resources>
</resources>
+2 -2
View File
@@ -16,7 +16,7 @@
<string name="menu_item_none">Нет</string>
<string name="toast_error_cannot_connect_host">Соединение с %s не установлено.
\nПроверьте имя сервера и ваше подключение к сети.</string>
<string name="toast_error_invalid_format">Неправильный формат имени сервера</string>
<string name="connection_error_invalid_format">Неправильный формат имени сервера</string>
<string name="connect_button_text">Соединение</string>
<string name="host_input_hint">Сервер</string>
</resources>
</resources>
+2 -2
View File
@@ -10,7 +10,7 @@
<string name="battery_optimizations_title">Zakázať optimalizáciu batérie</string>
<string name="toast_error_cannot_connect_host">Pripojenie k %s sa nepodarilo.
\nSkontrolujte názov hostiteľa a pripojenie k sieti.</string>
<string name="toast_error_invalid_format">Formát hostiteľa je neplatný</string>
<string name="connection_error_invalid_format">Formát hostiteľa je neplatný</string>
<string name="toast_error_missing_scheme">Chýba protokol (https alebo http)</string>
<string name="connect_button_text">Pripojiť</string>
<string name="host_input_hint">Hostiteľ</string>
@@ -27,4 +27,4 @@
<string name="playback_info_audio_streams">Audio streamy</string>
<string name="playback_info_video_streams">Video streamy</string>
<string name="playback_info_transcoding">Transkódovanie: %b</string>
</resources>
</resources>
+2 -2
View File
@@ -10,7 +10,7 @@
<string name="battery_optimizations_title">Onemogoči optimizacijo akumulatorja</string>
<string name="toast_error_cannot_connect_host">Povezave s %s ni mogoče vzpostaviti.
\nPreverite naslov in internetno povezavo.</string>
<string name="toast_error_invalid_format">Napačen format naslova</string>
<string name="connection_error_invalid_format">Napačen format naslova</string>
<string name="toast_error_missing_scheme">Manjka protokol (https ali http)</string>
<string name="connect_button_text">Poveži</string>
<string name="host_input_hint">Naslov</string>
@@ -23,4 +23,4 @@
<string name="pref_category_music_player">Predvajalnik glasbe</string>
<string name="activity_name_settings">Nastavitve Jellyfin</string>
<string name="menu_item_none">Nič</string>
</resources>
</resources>
+2 -2
View File
@@ -17,7 +17,7 @@
<string name="battery_optimizations_title">பேட்டரி உகப்பாக்கங்களை முடக்கு</string>
<string name="toast_error_cannot_connect_host">%s உடன் இணைப்பை நிறுவ முடியாது.
\nஹோஸ்ட்பெயர் மற்றும் உங்கள் பிணைய இணைப்பை சரிபார்க்கவும்.</string>
<string name="toast_error_invalid_format">ஹோஸ்ட் தவறான வடிவத்தைக் கொண்டுள்ளது</string>
<string name="connection_error_invalid_format">ஹோஸ்ட் தவறான வடிவத்தைக் கொண்டுள்ளது</string>
<string name="toast_error_missing_scheme">விடுபட்ட திட்டம் (https அல்லது http)</string>
<string name="connect_button_text">இணை</string>
<string name="host_input_hint">ஹோஸ்ட்</string>
@@ -27,4 +27,4 @@
<string name="playback_info_audio_streams">ஆடியோ ஸ்ட்ரீம்கள்</string>
<string name="playback_info_video_streams">வீடியோ ஸ்ட்ரீம்கள்</string>
<string name="playback_info_transcoding">டிரான்ஸ்கோடிங்: %b</string>
</resources>
</resources>
+2 -2
View File
@@ -10,10 +10,10 @@
<string name="battery_optimizations_message">Відключіть оптимізацію акумулятора для відтворення медіа, коли екран вимкнено.</string>
<string name="toast_error_cannot_connect_host">Підключення до %s неможливо встановити.
\nПеревірте ім\'я сервера та мережеве з\'єднання.</string>
<string name="toast_error_invalid_format">Невірний формат сервера</string>
<string name="connection_error_invalid_format">Невірний формат сервера</string>
<string name="toast_error_missing_scheme">Відсутня схема (https або http)</string>
<string name="connect_button_text">Підключитись</string>
<string name="host_input_hint">Сервер</string>
<string name="connect_to_server_title">Підключення до сервера</string>
<string name="app_name">Jellyfin</string>
</resources>
</resources>
+2 -2
View File
@@ -11,9 +11,9 @@
<string name="app_name">Jellyfin</string>
<string name="toast_error_cannot_connect_host">无法建立到 %s 的连接。
\n请检查主机名和你的网络连接。</string>
<string name="toast_error_invalid_format">主机格式无效</string>
<string name="connection_error_invalid_format">主机格式无效</string>
<string name="toast_error_missing_scheme">缺少协议类型 (https 或 http)</string>
<string name="connect_button_text">连接</string>
<string name="host_input_hint">主机</string>
<string name="connect_to_server_title">连接到服务器</string>
</resources>
</resources>
+2 -1
View File
@@ -11,6 +11,7 @@
<!-- App colors -->
<color name="theme_background">#101010</color>
<color name="logo_text_color">#fafafa</color>
<color name="error_text_color">#b00020</color>
<color name="playback_controls_background">#60000000</color>
<color name="playback_info_background">#cc000000</color>
</resources>
</resources>
+3 -4
View File
@@ -3,11 +3,10 @@
<string name="connect_to_server_title">Connect to Server</string>
<string name="host_input_hint">Host</string>
<string name="connection_error_invalid_format">Host has an invalid format</string>
<string name="connection_error_cannot_connect">Connection cannot be established.\nPlease check the hostname and your network connection.</string>
<string name="connect_button_text">Connect</string>
<string name="toast_error_invalid_format">Host has an invalid format</string>
<string name="toast_error_cannot_connect_host">Connection to %s cannot be established.\nPlease check the hostname and your network connection.</string>
<string name="battery_optimizations_title">Disable Battery Optimizations</string>
<string name="battery_optimizations_message">Please disable battery optimizations for media playback while the screen is off.</string>
<string name="network_title">Allowed Network Types</string>
@@ -29,4 +28,4 @@
<string name="pref_category_video_player">Native video player</string>
<string name="pref_enable_exoplayer_title">Enable video player integration</string>
<string name="pref_enable_exoplayer_summary">Enable the experimental ExoPlayer video player which supports more video formats and codecs, and is more integrated into the OS</string>
</resources>
</resources>