diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef36..500c1dbc 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 6d6940a1..04d6496b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -44,8 +44,8 @@ android {
applicationId = "com.nuvio.tv"
minSdk = 24
targetSdk = 36
- versionCode = 48
- versionName = "0.5.5-beta"
+ versionCode = 49
+ versionName = "0.5.6-beta"
buildConfigField("String", "PARENTAL_GUIDE_API_URL", "\"${localProperties.getProperty("PARENTAL_GUIDE_API_URL", "")}\"")
buildConfigField("String", "INTRODB_API_URL", "\"${localProperties.getProperty("INTRODB_API_URL", "")}\"")
diff --git a/app/src/main/java/com/nuvio/tv/MainActivity.kt b/app/src/main/java/com/nuvio/tv/MainActivity.kt
index 3de3a3c8..6e793a96 100644
--- a/app/src/main/java/com/nuvio/tv/MainActivity.kt
+++ b/app/src/main/java/com/nuvio/tv/MainActivity.kt
@@ -56,6 +56,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -218,7 +219,7 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
window?.setBackgroundDrawable(null)
setContent {
- var hasSelectedProfileThisSession by remember { mutableStateOf(false) }
+ var hasSelectedProfileThisSession by rememberSaveable { mutableStateOf(false) }
var onboardingCompletedThisSession by remember { mutableStateOf(false) }
var onboardingProfileSyncInProgress by remember { mutableStateOf(false) }
val hasSeenAuthQrOnFirstLaunch by appOnboardingDataStore
@@ -572,6 +573,7 @@ private fun LegacySidebarScaffold(
val openDrawerWidth = 196.dp
val focusManager = LocalFocusManager.current
+ val isRtl = androidx.compose.ui.platform.LocalLayoutDirection.current == androidx.compose.ui.unit.LayoutDirection.Rtl
val contentFocusRequester = remember { FocusRequester() }
var pendingContentFocusTransfer by remember { mutableStateOf(false) }
var pendingSidebarFocusRequest by remember { mutableStateOf(false) }
@@ -624,7 +626,8 @@ private fun LegacySidebarScaffold(
.padding(12.dp)
.selectableGroup()
.onPreviewKeyEvent { keyEvent ->
- if (keyEvent.key == Key.DirectionRight && keyEvent.type == KeyEventType.KeyDown) {
+ val closeKey = if (isRtl) Key.DirectionLeft else Key.DirectionRight
+ if (keyEvent.key == closeKey && keyEvent.type == KeyEventType.KeyDown) {
drawerState.setValue(DrawerValue.Closed)
pendingContentFocusTransfer = false
true
@@ -740,13 +743,14 @@ private fun LegacySidebarScaffold(
.fillMaxSize()
.padding(start = contentStartPadding)
.onKeyEvent { keyEvent ->
+ val openKey = if (isRtl) Key.DirectionRight else Key.DirectionLeft
if (
showSidebar &&
drawerState.currentValue == DrawerValue.Closed &&
keyEvent.type == KeyEventType.KeyDown &&
- keyEvent.key == Key.DirectionLeft
+ keyEvent.key == openKey
) {
- if (focusManager.moveFocus(FocusDirection.Left)) {
+ if (focusManager.moveFocus(if (isRtl) FocusDirection.Right else FocusDirection.Left)) {
true
} else {
pendingSidebarFocusRequest = true
@@ -874,6 +878,7 @@ private fun ModernSidebarScaffold(
val openSidebarWidth = 262.dp
val focusManager = LocalFocusManager.current
+ val isRtl = androidx.compose.ui.platform.LocalLayoutDirection.current == androidx.compose.ui.unit.LayoutDirection.Rtl
val contentFocusRequester = remember { FocusRequester() }
val drawerItemFocusRequesters = remember(drawerItems) {
drawerItems.associate { item -> item.route to FocusRequester() }
@@ -1091,8 +1096,9 @@ private fun ModernSidebarScaffold(
else -> Unit
}
}
- if (keyEvent.key == Key.DirectionLeft) {
- if (focusManager.moveFocus(FocusDirection.Left)) {
+ val openKey = if (isRtl) Key.DirectionRight else Key.DirectionLeft
+ if (keyEvent.key == openKey) {
+ if (focusManager.moveFocus(if (isRtl) FocusDirection.Right else FocusDirection.Left)) {
true
} else {
isSidebarExpanded = true
@@ -1155,10 +1161,15 @@ private fun ModernSidebarScaffold(
focusedDrawerIndex == drawerItems.lastIndex
}
- Key.DirectionRight -> {
- pendingContentFocusTransfer = false
- sidebarCollapsePending = true
- true
+ Key.DirectionRight, Key.DirectionLeft -> {
+ val collapseKey = if (isRtl) Key.DirectionLeft else Key.DirectionRight
+ if (keyEvent.key == collapseKey) {
+ pendingContentFocusTransfer = false
+ sidebarCollapsePending = true
+ true
+ } else {
+ false
+ }
}
else -> false
diff --git a/app/src/main/java/com/nuvio/tv/core/di/NetworkModule.kt b/app/src/main/java/com/nuvio/tv/core/di/NetworkModule.kt
index a072de5c..e89457cd 100644
--- a/app/src/main/java/com/nuvio/tv/core/di/NetworkModule.kt
+++ b/app/src/main/java/com/nuvio/tv/core/di/NetworkModule.kt
@@ -31,10 +31,15 @@ import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import com.nuvio.tv.core.network.IPv4FirstDns
import java.io.File
+import java.security.SecureRandom
+import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicLong
import javax.inject.Named
import javax.inject.Singleton
+import javax.net.ssl.SSLContext
+import javax.net.ssl.TrustManager
+import javax.net.ssl.X509TrustManager
private object TraktHttpTrace {
private val requestCounter = AtomicLong(0L)
@@ -53,16 +58,28 @@ object NetworkModule {
@Provides
@Singleton
- fun provideOkHttpClient(@ApplicationContext context: Context): OkHttpClient = OkHttpClient.Builder()
- .dns(IPv4FirstDns())
- .cache(Cache(File(context.cacheDir, "http_cache"), 50L * 1024 * 1024)) // 50 MB disk cache
- .connectTimeout(30, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS)
- .addInterceptor(HttpLoggingInterceptor().apply {
- level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BASIC
- else HttpLoggingInterceptor.Level.NONE
- })
- .build()
+ fun provideOkHttpClient(@ApplicationContext context: Context): OkHttpClient {
+ val trustAllManager = object : X509TrustManager {
+ override fun checkClientTrusted(chain: Array?, authType: String?) = Unit
+ override fun checkServerTrusted(chain: Array?, authType: String?) = Unit
+ override fun getAcceptedIssuers(): Array = emptyArray()
+ }
+ val sslContext = SSLContext.getInstance("TLS").apply {
+ init(null, arrayOf(trustAllManager), SecureRandom())
+ }
+ return OkHttpClient.Builder()
+ .dns(IPv4FirstDns())
+ .sslSocketFactory(sslContext.socketFactory, trustAllManager)
+ .hostnameVerifier { _, _ -> true }
+ .cache(Cache(File(context.cacheDir, "http_cache"), 50L * 1024 * 1024)) // 50 MB disk cache
+ .connectTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .addInterceptor(HttpLoggingInterceptor().apply {
+ level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BASIC
+ else HttpLoggingInterceptor.Level.NONE
+ })
+ .build()
+ }
@Provides
@Singleton
diff --git a/app/src/main/java/com/nuvio/tv/core/server/AddonConfigServer.kt b/app/src/main/java/com/nuvio/tv/core/server/AddonConfigServer.kt
index 464e30ec..ea9aacf1 100644
--- a/app/src/main/java/com/nuvio/tv/core/server/AddonConfigServer.kt
+++ b/app/src/main/java/com/nuvio/tv/core/server/AddonConfigServer.kt
@@ -1,5 +1,6 @@
package com.nuvio.tv.core.server
+import android.content.Context
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import fi.iki.elonen.NanoHTTPD
@@ -8,6 +9,7 @@ import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
class AddonConfigServer(
+ private val context: Context,
private val currentPageStateProvider: () -> PageState,
private val onChangeProposed: (PendingAddonChange) -> Unit,
private val logoProvider: (() -> ByteArray?)? = null,
@@ -101,7 +103,7 @@ class AddonConfigServer(
}
private fun serveWebPage(): Response {
- return newFixedLengthResponse(Response.Status.OK, "text/html; charset=utf-8", AddonWebPage.getHtml())
+ return newFixedLengthResponse(Response.Status.OK, "text/html; charset=utf-8", AddonWebPage.getHtml(context))
}
private fun serveLogo(): Response {
@@ -197,6 +199,7 @@ class AddonConfigServer(
companion object {
fun startOnAvailablePort(
+ context: Context,
currentPageStateProvider: () -> PageState,
onChangeProposed: (PendingAddonChange) -> Unit,
logoProvider: (() -> ByteArray?)? = null,
@@ -205,7 +208,7 @@ class AddonConfigServer(
): AddonConfigServer? {
for (port in startPort until startPort + maxAttempts) {
try {
- val server = AddonConfigServer(currentPageStateProvider, onChangeProposed, logoProvider, port)
+ val server = AddonConfigServer(context, currentPageStateProvider, onChangeProposed, logoProvider, port)
server.start(SOCKET_READ_TIMEOUT, false)
return server
} catch (e: Exception) {
diff --git a/app/src/main/java/com/nuvio/tv/core/server/AddonWebPage.kt b/app/src/main/java/com/nuvio/tv/core/server/AddonWebPage.kt
index 50785c4b..17a31203 100644
--- a/app/src/main/java/com/nuvio/tv/core/server/AddonWebPage.kt
+++ b/app/src/main/java/com/nuvio/tv/core/server/AddonWebPage.kt
@@ -1,14 +1,27 @@
package com.nuvio.tv.core.server
+import android.content.Context
+import android.content.res.Configuration
+import com.nuvio.tv.R
+import java.util.Locale
+
object AddonWebPage {
- fun getHtml(): String = """
+ fun getHtml(baseContext: Context): String {
+ val tag = baseContext.getSharedPreferences("app_locale", Context.MODE_PRIVATE)
+ .getString("locale_tag", null)
+ val context = if (!tag.isNullOrEmpty()) {
+ val config = Configuration(baseContext.resources.configuration)
+ config.setLocale(Locale.forLanguageTag(tag))
+ baseContext.createConfigurationContext(config)
+ } else baseContext
+ return """
-NuvioTV - Manage from Phone
+${context.getString(R.string.app_name)} - ${context.getString(R.string.web_manage_addons_title)}