diff --git a/apps/vbox/client/androidApp/build.gradle.kts b/apps/vbox/client/androidApp/build.gradle.kts
index a3f45e7..1cb9b0e 100644
--- a/apps/vbox/client/androidApp/build.gradle.kts
+++ b/apps/vbox/client/androidApp/build.gradle.kts
@@ -12,8 +12,8 @@ android {
applicationId = namespace
minSdk = libs.versions.proj.min.get().toInt()
targetSdk = libs.versions.proj.target.get().toInt()
- versionCode = 48
- versionName = "1.24.4"
+ versionCode = 49
+ versionName = "1.25.0"
}
}
diff --git a/apps/vbox/client/androidApp/src/main/AndroidManifest.xml b/apps/vbox/client/androidApp/src/main/AndroidManifest.xml
index 7d62610..14ffe88 100644
--- a/apps/vbox/client/androidApp/src/main/AndroidManifest.xml
+++ b/apps/vbox/client/androidApp/src/main/AndroidManifest.xml
@@ -30,6 +30,7 @@
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
+ android:launchMode="singleInstance"
android:supportsPictureInPicture="true">
@@ -56,10 +57,6 @@
android:host="box.shadowsparky.ru"
android:scheme="https" />
-
-
-
-
- val routeFromIntent = obtainRouteOrNull(intent?.data)
- val route = if (routeFromIntent == null) {
- listOf(Route.default)
- } else {
- listOf(Route.default, routeFromIntent)
- }
- koin.get().create(componentContext, route)
+ koin.get().create(componentContext, listOf(Route.default))
}
if (openSavedIfNeeded(intent) || openUrlIfNeeded(intent.data)) {
intent = Intent(intent)
.setData(null)
.setAction(null)
}
+ log("onCreate $intent")
val isPiggyActivity = componentName?.className == PigReceiver.PIGGY_CLS
setContent {
if (isPiggyActivity) {
@@ -54,10 +50,9 @@ class MainActivity : ComponentActivity() {
edgeToEdge(isSystemInDarkTheme())
}
lifecycleScope.launch {
- root.onFinish.collect {
- finishAndRemoveTask()
- }
+ root.onFinish.collect { finishAndRemoveTask() }
}
+ if (savedInstanceState == null) handleIntent(intent)
}
private fun edgeToEdge(isDarkTheme: Boolean) {
@@ -84,15 +79,42 @@ class MainActivity : ComponentActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
- if (!openSavedIfNeeded(intent)) {
- val uri = intent.data ?: return
- val route = obtainRouteOrNull(uri)
- if (route != null) {
- root.nav(route, true)
- } else {
- openUrlIfNeeded(uri)
- }
+ log("onNewIntent $intent")
+ setIntent(intent)
+ handleIntent(intent)
+ }
+
+ private fun handleIntent(intent: Intent?) {
+ 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 {
diff --git a/apps/vbox/client/androidApp/src/main/res/xml-v25/shortcuts.xml b/apps/vbox/client/androidApp/src/main/res/xml-v25/shortcuts.xml
deleted file mode 100644
index 6e9707b..0000000
--- a/apps/vbox/client/androidApp/src/main/res/xml-v25/shortcuts.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
diff --git a/apps/vbox/client/androidApp/src/main/res/xml/shortcuts.xml b/apps/vbox/client/androidApp/src/main/res/xml/shortcuts.xml
deleted file mode 100644
index 167bf66..0000000
--- a/apps/vbox/client/androidApp/src/main/res/xml/shortcuts.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/UrlRoutes.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/UrlRoutes.kt
index 60a3d74..f06a703 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/UrlRoutes.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/UrlRoutes.kt
@@ -102,6 +102,7 @@ object UrlRoutes {
}
),
+ // "/chat
rule(
match = { it is Route.Chat },
build = {
@@ -110,7 +111,13 @@ object UrlRoutes {
emptyMap()
)
},
- parse = { _, _ -> Route.Chat() }
+ parse = { segs, _ ->
+ if (segs.size == 1 && segs[0] == Seg.CHAT) {
+ Route.Chat()
+ } else {
+ null
+ }
+ }
),
// "/search"
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/VBoxRootComponent.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/VBoxRootComponent.kt
index 61ec15e..54fd8c9 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/VBoxRootComponent.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/VBoxRootComponent.kt
@@ -10,7 +10,6 @@ import com.arkivanov.essenty.lifecycle.coroutines.repeatOnLifecycle
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.serialization.KSerializer
import org.koin.core.Koin
@@ -27,26 +26,29 @@ import ru.shadowsparky.vbox.shared.presentation.nav.Route
class VBoxRootComponent(
componentContext: ComponentContext,
tokenStorage: TokenStorage,
- override val initStack: List,
+ private val initRoute: Route,
private val healthCheck: HeathCheck,
val imageLoaderProvider: ImageLoaderProvider,
updateComponentFactory: UpdateComponentFactory,
private val remoteEventListener: RemoteEventListener,
+ override val initStack: List = listOf(initRoute),
override val serializer: KSerializer = Route.serializer(),
override val koin: Koin = ru.shadowsparky.koin
) : RootComponent(componentContext), WebNavigationOwner {
val updateComponent = updateComponentFactory.create(childContext("UpdateComponentRoot"))
private val scope = createComponentScope()
- val isLoggedIn = tokenStorage.token.map {
- if (it != null) {
- runCatching { healthCheck.check() }.getOrNull() ?: return@map false
- true
- } else {
- false
- }
- }
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 {
repeatOnLifecycle {
tokenStorage.token.flatMapLatest {
@@ -103,7 +105,7 @@ class RootFactory(
return VBoxRootComponent(
context,
tokenStorage,
- listOf(Route.Loading(next = initStack.toSet())),
+ Route.Loading(next = initStack.toSet()),
heathCheck,
imageLoaderProvider,
updateComponentFactory,
diff --git a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/OverviewScreen.kt b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/OverviewScreen.kt
index 5697e42..5e90e0b 100644
--- a/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/OverviewScreen.kt
+++ b/apps/vbox/client/shared/src/commonMain/kotlin/ru/shadowsparky/vbox/shared/presentation/overview/OverviewScreen.kt
@@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
@@ -292,12 +293,15 @@ private fun OverviewDescription(
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(vertical = 8.dp)
)
- Text(
- text = details.desc.trim(),
- style = MaterialTheme.typography.bodyLarge,
- maxLines = if (showDetails.value) Int.MAX_VALUE else 3,
- overflow = TextOverflow.Ellipsis
- )
+
+ SelectionContainer {
+ Text(
+ text = details.desc.trim(),
+ style = MaterialTheme.typography.bodyLarge,
+ maxLines = if (showDetails.value) Int.MAX_VALUE else 3,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
AnimatedVisibility(
visible = !showDetails.value,
diff --git a/apps/vbox/client/web/src/wasmJsMain/kotlin/ru/shadowsparky/vbox/Main.kt b/apps/vbox/client/web/src/wasmJsMain/kotlin/ru/shadowsparky/vbox/Main.kt
index c64bdcd..b83622f 100644
--- a/apps/vbox/client/web/src/wasmJsMain/kotlin/ru/shadowsparky/vbox/Main.kt
+++ b/apps/vbox/client/web/src/wasmJsMain/kotlin/ru/shadowsparky/vbox/Main.kt
@@ -7,8 +7,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -86,13 +84,6 @@ fun mainInternal() {
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())) {
Nav(r)
}