fix deep link

This commit is contained in:
2026-08-08 08:52:18 +03:00
parent 64ee936aa6
commit 023395e09d
9 changed files with 74 additions and 71 deletions
+2 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId = namespace applicationId = namespace
minSdk = libs.versions.proj.min.get().toInt() minSdk = libs.versions.proj.min.get().toInt()
targetSdk = libs.versions.proj.target.get().toInt() targetSdk = libs.versions.proj.target.get().toInt()
versionCode = 48 versionCode = 49
versionName = "1.24.4" versionName = "1.25.0"
} }
} }
@@ -30,6 +30,7 @@
android:exported="true" android:exported="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleInstance"
android:supportsPictureInPicture="true"> android:supportsPictureInPicture="true">
<intent-filter> <intent-filter>
@@ -56,10 +57,6 @@
android:host="box.shadowsparky.ru" android:host="box.shadowsparky.ru"
android:scheme="https" /> android:scheme="https" />
</intent-filter> </intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity> </activity>
<activity-alias <activity-alias
@@ -95,10 +92,6 @@
android:host="box.shadowsparky.ru" android:host="box.shadowsparky.ru"
android:scheme="https" /> android:scheme="https" />
</intent-filter> </intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias> </activity-alias>
<receiver <receiver
@@ -4,6 +4,7 @@ import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@@ -29,19 +30,14 @@ class MainActivity : ComponentActivity() {
edgeToEdge(false) edgeToEdge(false)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
root = retainedComponent { componentContext -> root = retainedComponent { componentContext ->
val routeFromIntent = obtainRouteOrNull(intent?.data) koin.get<RootFactory>().create(componentContext, listOf(Route.default))
val route = if (routeFromIntent == null) {
listOf(Route.default)
} else {
listOf(Route.default, routeFromIntent)
}
koin.get<RootFactory>().create(componentContext, route)
} }
if (openSavedIfNeeded(intent) || openUrlIfNeeded(intent.data)) { if (openSavedIfNeeded(intent) || openUrlIfNeeded(intent.data)) {
intent = Intent(intent) intent = Intent(intent)
.setData(null) .setData(null)
.setAction(null) .setAction(null)
} }
log("onCreate $intent")
val isPiggyActivity = componentName?.className == PigReceiver.PIGGY_CLS val isPiggyActivity = componentName?.className == PigReceiver.PIGGY_CLS
setContent { setContent {
if (isPiggyActivity) { if (isPiggyActivity) {
@@ -54,10 +50,9 @@ class MainActivity : ComponentActivity() {
edgeToEdge(isSystemInDarkTheme()) edgeToEdge(isSystemInDarkTheme())
} }
lifecycleScope.launch { lifecycleScope.launch {
root.onFinish.collect { root.onFinish.collect { finishAndRemoveTask() }
finishAndRemoveTask()
}
} }
if (savedInstanceState == null) handleIntent(intent)
} }
private fun edgeToEdge(isDarkTheme: Boolean) { private fun edgeToEdge(isDarkTheme: Boolean) {
@@ -84,15 +79,42 @@ class MainActivity : ComponentActivity() {
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent) super.onNewIntent(intent)
if (!openSavedIfNeeded(intent)) { log("onNewIntent $intent")
val uri = intent.data ?: return setIntent(intent)
val route = obtainRouteOrNull(uri) handleIntent(intent)
if (route != null) { }
root.nav(route, true)
} else { private fun handleIntent(intent: Intent?) {
openUrlIfNeeded(uri) intent ?: return
} if (openSavedIfNeeded(intent)) {
clearIntent(intent)
return
} }
val uri = intent.data ?: return
val route = obtainRouteOrNull(uri)
if (route != null) {
val currentRoute = root.childStack.value.active.configuration
if (currentRoute != route) {
root.nav(route, true)
}
} else {
openUrlIfNeeded(uri)
}
clearIntent(intent)
}
private fun clearIntent(intent: Intent) {
intent.data = null
intent.action = null
}
override fun onDestroy() {
super.onDestroy()
log("onDestroy")
}
private fun log(msg: String) {
Log.d("MainActivityDebug", msg)
} }
private fun openUrlIfNeeded(uri: Uri?): Boolean { private fun openUrlIfNeeded(uri: Uri?): Boolean {
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:enabled="true"
android:icon="@drawable/baseline_saved_search_24"
android:shortcutId="saved_movies"
android:shortcutLongLabel="@string/saved_movies_long"
android:shortcutShortLabel="@string/saved_movies">
<intent
android:action="ru.shadowsparky.vbox.action.OPEN_SAVED_MOVIES"
android:targetClass="ru.shadowsparky.videobox.multiplatform.MainActivity"
android:targetPackage="ru.shadowsparky.videobox.multiplatform" />
</shortcut>
</shortcuts>
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts></shortcuts>
@@ -102,6 +102,7 @@ object UrlRoutes {
} }
), ),
// "/chat
rule( rule(
match = { it is Route.Chat }, match = { it is Route.Chat },
build = { build = {
@@ -110,7 +111,13 @@ object UrlRoutes {
emptyMap() emptyMap()
) )
}, },
parse = { _, _ -> Route.Chat() } parse = { segs, _ ->
if (segs.size == 1 && segs[0] == Seg.CHAT) {
Route.Chat()
} else {
null
}
}
), ),
// "/search" // "/search"
@@ -10,7 +10,6 @@ import com.arkivanov.essenty.lifecycle.coroutines.repeatOnLifecycle
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import org.koin.core.Koin import org.koin.core.Koin
@@ -27,26 +26,29 @@ import ru.shadowsparky.vbox.shared.presentation.nav.Route
class VBoxRootComponent( class VBoxRootComponent(
componentContext: ComponentContext, componentContext: ComponentContext,
tokenStorage: TokenStorage, tokenStorage: TokenStorage,
override val initStack: List<Route>, private val initRoute: Route,
private val healthCheck: HeathCheck, private val healthCheck: HeathCheck,
val imageLoaderProvider: ImageLoaderProvider, val imageLoaderProvider: ImageLoaderProvider,
updateComponentFactory: UpdateComponentFactory, updateComponentFactory: UpdateComponentFactory,
private val remoteEventListener: RemoteEventListener, private val remoteEventListener: RemoteEventListener,
override val initStack: List<Route> = listOf(initRoute),
override val serializer: KSerializer<Route> = Route.serializer(), override val serializer: KSerializer<Route> = Route.serializer(),
override val koin: Koin = ru.shadowsparky.koin override val koin: Koin = ru.shadowsparky.koin
) : RootComponent<Route>(componentContext), WebNavigationOwner { ) : RootComponent<Route>(componentContext), WebNavigationOwner {
val updateComponent = updateComponentFactory.create(childContext("UpdateComponentRoot")) val updateComponent = updateComponentFactory.create(childContext("UpdateComponentRoot"))
private val scope = createComponentScope() private val scope = createComponentScope()
val isLoggedIn = tokenStorage.token.map {
if (it != null) {
runCatching { healthCheck.check() }.getOrNull() ?: return@map false
true
} else {
false
}
}
init { init {
scope.launch {
repeatOnLifecycle {
tokenStorage.token.collect {
if (it != null) {
val invalid = runCatching { healthCheck.check() }.getOrNull() == null
if (invalid) { navReplace(initRoute) }
}
} // i love you
}
}
scope.launch { scope.launch {
repeatOnLifecycle { repeatOnLifecycle {
tokenStorage.token.flatMapLatest { tokenStorage.token.flatMapLatest {
@@ -103,7 +105,7 @@ class RootFactory(
return VBoxRootComponent( return VBoxRootComponent(
context, context,
tokenStorage, tokenStorage,
listOf(Route.Loading(next = initStack.toSet())), Route.Loading(next = initStack.toSet()),
heathCheck, heathCheck,
imageLoaderProvider, imageLoaderProvider,
updateComponentFactory, updateComponentFactory,
@@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
@@ -292,12 +293,15 @@ private fun OverviewDescription(
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(vertical = 8.dp) modifier = Modifier.padding(vertical = 8.dp)
) )
Text(
text = details.desc.trim(), SelectionContainer {
style = MaterialTheme.typography.bodyLarge, Text(
maxLines = if (showDetails.value) Int.MAX_VALUE else 3, text = details.desc.trim(),
overflow = TextOverflow.Ellipsis style = MaterialTheme.typography.bodyLarge,
) maxLines = if (showDetails.value) Int.MAX_VALUE else 3,
overflow = TextOverflow.Ellipsis
)
}
AnimatedVisibility( AnimatedVisibility(
visible = !showDetails.value, visible = !showDetails.value,
@@ -7,8 +7,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -86,13 +84,6 @@ fun mainInternal() {
r.onFinish.collect { r.navReplace(Route.Loading()) } r.onFinish.collect { r.navReplace(Route.Loading()) }
} }
val isLoggedIn by r.isLoggedIn.collectAsState(null)
LaunchedEffect(isLoggedIn) {
if (isLoggedIn == false) {
r.navReplace(Route.Loading())
}
}
Box(Modifier.fillMaxSize().padding(WindowInsets.safeDrawing.asPaddingValues())) { Box(Modifier.fillMaxSize().padding(WindowInsets.safeDrawing.asPaddingValues())) {
Nav(r) Nav(r)
} }