feat(app): deep links, notification launch, and night splash

Add wallenc:// URI patterns, manifest VIEW intent-filters, and NavDeepLink
with handleDeepLink from MainActivity. Move FGS notification PendingIntent
constants to WallencExternalLaunch. Theme splash/window background via
splash_screen_background in values and values-night.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-11 22:43:22 +03:00
parent 88a13080e5
commit 60627f11d6
9 changed files with 170 additions and 51 deletions

View File

@@ -1,5 +1,6 @@
package com.github.nullptroma.wallenc.ui
import android.content.Intent
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -26,7 +27,10 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.navDeepLink
import com.github.nullptroma.wallenc.ui.navigation.NavBarItemData
import com.github.nullptroma.wallenc.ui.navigation.WallencDeepLinks
import com.github.nullptroma.wallenc.ui.navigation.matchesWallencDeepLink
import com.github.nullptroma.wallenc.ui.navigation.rememberNavigationState
import com.github.nullptroma.wallenc.ui.screens.main.MainRoute
import com.github.nullptroma.wallenc.ui.screens.main.MainScreen
@@ -40,10 +44,16 @@ import com.github.nullptroma.wallenc.ui.theme.WallencTheme
@Composable
fun WallencUi(taskPipelineOpenRequestCount: Int = 0) {
fun WallencUi(
deepLinkPulse: Int = 0,
deepLinkIntent: Intent = Intent(),
) {
WallencTheme {
Surface {
WallencNavRoot(taskPipelineOpenRequestCount = taskPipelineOpenRequestCount)
WallencNavRoot(
deepLinkPulse = deepLinkPulse,
deepLinkIntent = deepLinkIntent,
)
}
}
}
@@ -52,7 +62,8 @@ fun WallencUi(taskPipelineOpenRequestCount: Int = 0) {
@Composable
fun WallencNavRoot(
viewModel: WallencViewModel = hiltViewModel(),
taskPipelineOpenRequestCount: Int = 0,
deepLinkPulse: Int = 0,
deepLinkIntent: Intent = Intent(),
) {
val navState = rememberNavigationState()
val mainNavState = rememberNavigationState()
@@ -62,10 +73,10 @@ fun WallencNavRoot(
val topLevelRoutes = viewModel.routes
LaunchedEffect(taskPipelineOpenRequestCount) {
if (taskPipelineOpenRequestCount <= 0) return@LaunchedEffect
val route = topLevelRoutes[TaskPipelineRoute::class.qualifiedName!!] ?: return@LaunchedEffect
navState.changeTop(route)
LaunchedEffect(deepLinkPulse) {
if (deepLinkPulse <= 0) return@LaunchedEffect
if (!deepLinkIntent.matchesWallencDeepLink()) return@LaunchedEffect
navState.navHostController.handleDeepLink(deepLinkIntent)
}
val topLevelNavBarItems = remember {
@@ -121,7 +132,11 @@ fun WallencNavRoot(
navState.navHostController,
startDestination = topLevelRoutes[MainRoute::class.qualifiedName]!!
) {
composable<MainRoute>(enterTransition = {
composable<MainRoute>(
deepLinks = listOf(
navDeepLink { uriPattern = WallencDeepLinks.MAIN_URI_PATTERN },
),
enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
@@ -132,14 +147,22 @@ fun WallencNavRoot(
viewModel = mainViewModel
)
}
composable<SettingsRoute>(enterTransition = {
composable<SettingsRoute>(
deepLinks = listOf(
navDeepLink { uriPattern = WallencDeepLinks.SETTINGS_URI_PATTERN },
),
enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
}) {
SettingsScreen(Modifier.padding(innerPaddings), settingsViewModel)
}
composable<TaskPipelineRoute>(enterTransition = {
composable<TaskPipelineRoute>(
deepLinks = listOf(
navDeepLink { uriPattern = WallencDeepLinks.TASKS_URI_PATTERN },
),
enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))

View File

@@ -0,0 +1,29 @@
package com.github.nullptroma.wallenc.ui.navigation
import android.content.Intent
/**
* URI для входа извне приложения (уведомления, виджеты, шорткаты, другие Activity).
*
* Должны совпадать с `<intent-filter><data android:scheme=… android:host=…/></intent-filter>` в манифесте
* и с [androidx.navigation.navDeepLink] на соответствующих `composable` в [androidx.navigation.compose.NavHost].
*
* Для публичных HTTPS-ссылок позже добавляют отдельные хосты и `android:autoVerify`.
*/
object WallencDeepLinks {
const val SCHEME = "wallenc"
object Host {
const val MAIN = "main"
const val TASKS = "tasks"
const val SETTINGS = "settings"
}
const val MAIN_URI_PATTERN = "$SCHEME://${Host.MAIN}"
const val TASKS_URI_PATTERN = "$SCHEME://${Host.TASKS}"
const val SETTINGS_URI_PATTERN = "$SCHEME://${Host.SETTINGS}"
}
/** `ACTION_VIEW` с URI нашей схемы — обрабатывается [androidx.navigation.NavController.handleDeepLink]. */
fun Intent.matchesWallencDeepLink(): Boolean =
action == Intent.ACTION_VIEW && data?.scheme == WallencDeepLinks.SCHEME