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)}