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:
@@ -28,6 +28,14 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="wallenc" android:host="main" />
|
||||||
|
<data android:scheme="wallenc" android:host="tasks" />
|
||||||
|
<data android:scheme="wallenc" android:host="settings" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
package com.github.nullptroma.wallenc.app
|
package com.github.nullptroma.wallenc.app
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.github.nullptroma.wallenc.app.auth.YandexSignInService
|
import com.github.nullptroma.wallenc.app.auth.YandexSignInService
|
||||||
import com.github.nullptroma.wallenc.ui.WallencUi
|
import com.github.nullptroma.wallenc.ui.WallencUi
|
||||||
|
import com.github.nullptroma.wallenc.ui.navigation.matchesWallencDeepLink
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -24,7 +28,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var yandexSignInService: YandexSignInService
|
lateinit var yandexSignInService: YandexSignInService
|
||||||
|
|
||||||
private val openTaskPipelineFromNotification = mutableIntStateOf(0)
|
private val deepLinkPulse = mutableIntStateOf(0)
|
||||||
|
private var deepLinkIntentForNav: Intent by mutableStateOf(Intent())
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -34,11 +39,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
|
|
||||||
consumeOpenTaskPipelineIntent(intent)
|
deepLinkIntentForNav = intent
|
||||||
|
if (intent.matchesWallencDeepLink()) {
|
||||||
|
deepLinkPulse.intValue++
|
||||||
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
val pulse by deepLinkPulse
|
||||||
WallencUi(
|
WallencUi(
|
||||||
taskPipelineOpenRequestCount = openTaskPipelineFromNotification.intValue,
|
deepLinkPulse = pulse,
|
||||||
|
deepLinkIntent = deepLinkIntentForNav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,13 +56,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
setIntent(intent)
|
setIntent(intent)
|
||||||
consumeOpenTaskPipelineIntent(intent)
|
deepLinkIntentForNav = intent
|
||||||
}
|
if (intent.matchesWallencDeepLink()) {
|
||||||
|
deepLinkPulse.intValue++
|
||||||
private fun consumeOpenTaskPipelineIntent(intent: Intent?) {
|
}
|
||||||
if (intent?.getBooleanExtra(EXTRA_OPEN_TASK_PIPELINE, false) != true) return
|
|
||||||
intent.removeExtra(EXTRA_OPEN_TASK_PIPELINE)
|
|
||||||
openTaskPipelineFromNotification.intValue += 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@@ -75,9 +82,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_OPEN_TASK_PIPELINE =
|
|
||||||
"com.github.nullptroma.wallenc.app.EXTRA_OPEN_TASK_PIPELINE"
|
|
||||||
|
|
||||||
private const val NOTIFICATION_PERMISSION_REQUEST_CODE = 100
|
private const val NOTIFICATION_PERMISSION_REQUEST_CODE = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.github.nullptroma.wallenc.app.navigation
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import com.github.nullptroma.wallenc.app.MainActivity
|
||||||
|
import com.github.nullptroma.wallenc.ui.navigation.WallencDeepLinks
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запуск [MainActivity] и взаимодействие с foreground-сервисом из **вне** Compose: [android.app.PendingIntent],
|
||||||
|
* стабильные action, id канала/нотификации.
|
||||||
|
*
|
||||||
|
* URI см. [WallencDeepLinks] в `:ui` — тот же контракт, что у [androidx.navigation.navDeepLink] и манифеста.
|
||||||
|
*/
|
||||||
|
object WallencExternalLaunch {
|
||||||
|
|
||||||
|
/** Коды [android.app.PendingIntent]: у каждого сценария свой requestCode. */
|
||||||
|
object PendingIntentRequestCodes {
|
||||||
|
const val NOTIFICATION_TASK_PIPELINE_CANCEL_SERVICE = 0
|
||||||
|
const val NOTIFICATION_TASK_PIPELINE_OPEN_TASKS = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Intent.getAction] для [com.github.nullptroma.wallenc.app.tasks.TaskPipelineForegroundService]
|
||||||
|
* из кнопки уведомления ([PendingIntent.getService]).
|
||||||
|
*/
|
||||||
|
const val FOREGROUND_SERVICE_ACTION_CANCEL_ALL_TASKS =
|
||||||
|
"com.github.nullptroma.wallenc.action.CANCEL_ALL_TASKS"
|
||||||
|
|
||||||
|
object ForegroundTaskPipelineNotification {
|
||||||
|
const val CHANNEL_ID = "wallenc_task_pipeline"
|
||||||
|
const val NOTIFICATION_ID = 1001
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mainActivityViewIntent(context: Context, uri: Uri): Intent =
|
||||||
|
Intent(Intent.ACTION_VIEW, uri).apply {
|
||||||
|
setClass(context, MainActivity::class.java)
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mainActivityViewIntentForTasks(context: Context): Intent =
|
||||||
|
mainActivityViewIntent(context, Uri.parse(WallencDeepLinks.TASKS_URI_PATTERN))
|
||||||
|
}
|
||||||
@@ -11,8 +11,8 @@ import android.os.IBinder
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.github.nullptroma.wallenc.app.MainActivity
|
|
||||||
import com.github.nullptroma.wallenc.app.R
|
import com.github.nullptroma.wallenc.app.R
|
||||||
|
import com.github.nullptroma.wallenc.app.navigation.WallencExternalLaunch
|
||||||
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
|
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
|
||||||
import com.github.nullptroma.wallenc.domain.tasks.TaskForegroundItem
|
import com.github.nullptroma.wallenc.domain.tasks.TaskForegroundItem
|
||||||
import com.github.nullptroma.wallenc.domain.tasks.TaskForegroundUiState
|
import com.github.nullptroma.wallenc.domain.tasks.TaskForegroundUiState
|
||||||
@@ -58,7 +58,9 @@ class TaskPipelineForegroundService : Service() {
|
|||||||
override fun onBind(intent: Intent?): IBinder? = null
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
if (intent?.action == ACTION_CANCEL_ALL_TASKS && ::orchestrator.isInitialized) {
|
if (intent?.action == WallencExternalLaunch.FOREGROUND_SERVICE_ACTION_CANCEL_ALL_TASKS &&
|
||||||
|
::orchestrator.isInitialized
|
||||||
|
) {
|
||||||
orchestrator.cancelAll()
|
orchestrator.cancelAll()
|
||||||
}
|
}
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
@@ -68,7 +70,10 @@ class TaskPipelineForegroundService : Service() {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
ensureChannel()
|
ensureChannel()
|
||||||
startForeground(FOREGROUND_NOTIFICATION_ID, buildPlaceholderNotification())
|
startForeground(
|
||||||
|
WallencExternalLaunch.ForegroundTaskPipelineNotification.NOTIFICATION_ID,
|
||||||
|
buildPlaceholderNotification(),
|
||||||
|
)
|
||||||
|
|
||||||
val nm = getSystemService(NotificationManager::class.java)
|
val nm = getSystemService(NotificationManager::class.java)
|
||||||
|
|
||||||
@@ -85,7 +90,7 @@ class TaskPipelineForegroundService : Service() {
|
|||||||
TaskForegroundUiState.Hidden -> {
|
TaskForegroundUiState.Hidden -> {
|
||||||
repeat = false
|
repeat = false
|
||||||
if (sawVisible) {
|
if (sawVisible) {
|
||||||
nm.cancel(FOREGROUND_NOTIFICATION_ID)
|
nm.cancel(WallencExternalLaunch.ForegroundTaskPipelineNotification.NOTIFICATION_ID)
|
||||||
indeterminateDotsPhaseByTaskId.clear()
|
indeterminateDotsPhaseByTaskId.clear()
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
stopSelf()
|
stopSelf()
|
||||||
@@ -97,7 +102,7 @@ class TaskPipelineForegroundService : Service() {
|
|||||||
if (ui.tasks.isNotEmpty()) {
|
if (ui.tasks.isNotEmpty()) {
|
||||||
sawVisible = true
|
sawVisible = true
|
||||||
nm.notify(
|
nm.notify(
|
||||||
FOREGROUND_NOTIFICATION_ID,
|
WallencExternalLaunch.ForegroundTaskPipelineNotification.NOTIFICATION_ID,
|
||||||
buildAccumulatedNotification(ui.tasks),
|
buildAccumulatedNotification(ui.tasks),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -129,7 +134,7 @@ class TaskPipelineForegroundService : Service() {
|
|||||||
private fun ensureChannel() {
|
private fun ensureChannel() {
|
||||||
val nm = getSystemService(NotificationManager::class.java)
|
val nm = getSystemService(NotificationManager::class.java)
|
||||||
val channel = NotificationChannel(
|
val channel = NotificationChannel(
|
||||||
CHANNEL_ID,
|
WallencExternalLaunch.ForegroundTaskPipelineNotification.CHANNEL_ID,
|
||||||
getString(R.string.task_notification_channel_name),
|
getString(R.string.task_notification_channel_name),
|
||||||
NotificationManager.IMPORTANCE_LOW,
|
NotificationManager.IMPORTANCE_LOW,
|
||||||
)
|
)
|
||||||
@@ -137,7 +142,7 @@ class TaskPipelineForegroundService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildPlaceholderNotification(): Notification =
|
private fun buildPlaceholderNotification(): Notification =
|
||||||
NotificationCompat.Builder(this, CHANNEL_ID)
|
NotificationCompat.Builder(this, WallencExternalLaunch.ForegroundTaskPipelineNotification.CHANNEL_ID)
|
||||||
.setContentTitle(getString(R.string.task_notification_title))
|
.setContentTitle(getString(R.string.task_notification_title))
|
||||||
.setContentText(getString(R.string.task_notification_preparing))
|
.setContentText(getString(R.string.task_notification_preparing))
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
@@ -171,7 +176,10 @@ class TaskPipelineForegroundService : Service() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotificationCompat.Builder(this, CHANNEL_ID)
|
return NotificationCompat.Builder(
|
||||||
|
this,
|
||||||
|
WallencExternalLaunch.ForegroundTaskPipelineNotification.CHANNEL_ID,
|
||||||
|
)
|
||||||
.setContentTitle(getString(R.string.task_notification_title))
|
.setContentTitle(getString(R.string.task_notification_title))
|
||||||
.setContentText(collapsedSubtext)
|
.setContentText(collapsedSubtext)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
@@ -191,19 +199,18 @@ class TaskPipelineForegroundService : Service() {
|
|||||||
private fun openTaskPipelinePendingIntent(): PendingIntent =
|
private fun openTaskPipelinePendingIntent(): PendingIntent =
|
||||||
PendingIntent.getActivity(
|
PendingIntent.getActivity(
|
||||||
this,
|
this,
|
||||||
REQUEST_CODE_OPEN_TASK_PIPELINE,
|
WallencExternalLaunch.PendingIntentRequestCodes.NOTIFICATION_TASK_PIPELINE_OPEN_TASKS,
|
||||||
Intent(this, MainActivity::class.java).apply {
|
WallencExternalLaunch.mainActivityViewIntentForTasks(this),
|
||||||
putExtra(MainActivity.EXTRA_OPEN_TASK_PIPELINE, true)
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
|
||||||
},
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun cancelAllTasksPendingIntent(): PendingIntent =
|
private fun cancelAllTasksPendingIntent(): PendingIntent =
|
||||||
PendingIntent.getService(
|
PendingIntent.getService(
|
||||||
this,
|
this,
|
||||||
0,
|
WallencExternalLaunch.PendingIntentRequestCodes.NOTIFICATION_TASK_PIPELINE_CANCEL_SERVICE,
|
||||||
Intent(this, TaskPipelineForegroundService::class.java).setAction(ACTION_CANCEL_ALL_TASKS),
|
Intent(this, TaskPipelineForegroundService::class.java).setAction(
|
||||||
|
WallencExternalLaunch.FOREGROUND_SERVICE_ACTION_CANCEL_ALL_TASKS,
|
||||||
|
),
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -334,14 +341,6 @@ class TaskPipelineForegroundService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ACTION_CANCEL_ALL_TASKS =
|
|
||||||
"com.github.nullptroma.wallenc.action.CANCEL_ALL_TASKS"
|
|
||||||
|
|
||||||
private const val REQUEST_CODE_OPEN_TASK_PIPELINE = 2
|
|
||||||
|
|
||||||
private const val CHANNEL_ID = "wallenc_task_pipeline"
|
|
||||||
private const val FOREGROUND_NOTIFICATION_ID = 1001
|
|
||||||
|
|
||||||
private const val NOTIFICATION_QUEUE_STEP_MS = 500L
|
private const val NOTIFICATION_QUEUE_STEP_MS = 500L
|
||||||
|
|
||||||
/** Must match [R.layout.notification_wallenc_tasks_big] row count. */
|
/** Must match [R.layout.notification_wallenc_tasks_big] row count. */
|
||||||
|
|||||||
5
app/src/main/res/values-night/colors.xml
Normal file
5
app/src/main/res/values-night/colors.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Тёмная тема: фон splash и окна до отрисовки Compose -->
|
||||||
|
<color name="splash_screen_background">#FF1C1B1F</color>
|
||||||
|
</resources>
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources />
|
<resources>
|
||||||
|
<color name="splash_screen_background">#FFFFFFFF</color>
|
||||||
|
</resources>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<style name="Theme.Wallenc" parent="android:Theme.Material.Light.NoActionBar" />
|
<style name="Theme.Wallenc" parent="android:Theme.Material.Light.NoActionBar">
|
||||||
</resources>
|
<!-- До первого кадра Compose и системный splash (12+) -->
|
||||||
|
<item name="android:windowBackground">@color/splash_screen_background</item>
|
||||||
|
<item name="android:windowSplashScreenBackground" tools:targetApi="31">
|
||||||
|
@color/splash_screen_background
|
||||||
|
</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.github.nullptroma.wallenc.ui
|
package com.github.nullptroma.wallenc.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
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.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
import androidx.navigation.navDeepLink
|
||||||
import com.github.nullptroma.wallenc.ui.navigation.NavBarItemData
|
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.navigation.rememberNavigationState
|
||||||
import com.github.nullptroma.wallenc.ui.screens.main.MainRoute
|
import com.github.nullptroma.wallenc.ui.screens.main.MainRoute
|
||||||
import com.github.nullptroma.wallenc.ui.screens.main.MainScreen
|
import com.github.nullptroma.wallenc.ui.screens.main.MainScreen
|
||||||
@@ -40,10 +44,16 @@ import com.github.nullptroma.wallenc.ui.theme.WallencTheme
|
|||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WallencUi(taskPipelineOpenRequestCount: Int = 0) {
|
fun WallencUi(
|
||||||
|
deepLinkPulse: Int = 0,
|
||||||
|
deepLinkIntent: Intent = Intent(),
|
||||||
|
) {
|
||||||
WallencTheme {
|
WallencTheme {
|
||||||
Surface {
|
Surface {
|
||||||
WallencNavRoot(taskPipelineOpenRequestCount = taskPipelineOpenRequestCount)
|
WallencNavRoot(
|
||||||
|
deepLinkPulse = deepLinkPulse,
|
||||||
|
deepLinkIntent = deepLinkIntent,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,7 +62,8 @@ fun WallencUi(taskPipelineOpenRequestCount: Int = 0) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun WallencNavRoot(
|
fun WallencNavRoot(
|
||||||
viewModel: WallencViewModel = hiltViewModel(),
|
viewModel: WallencViewModel = hiltViewModel(),
|
||||||
taskPipelineOpenRequestCount: Int = 0,
|
deepLinkPulse: Int = 0,
|
||||||
|
deepLinkIntent: Intent = Intent(),
|
||||||
) {
|
) {
|
||||||
val navState = rememberNavigationState()
|
val navState = rememberNavigationState()
|
||||||
val mainNavState = rememberNavigationState()
|
val mainNavState = rememberNavigationState()
|
||||||
@@ -62,10 +73,10 @@ fun WallencNavRoot(
|
|||||||
|
|
||||||
val topLevelRoutes = viewModel.routes
|
val topLevelRoutes = viewModel.routes
|
||||||
|
|
||||||
LaunchedEffect(taskPipelineOpenRequestCount) {
|
LaunchedEffect(deepLinkPulse) {
|
||||||
if (taskPipelineOpenRequestCount <= 0) return@LaunchedEffect
|
if (deepLinkPulse <= 0) return@LaunchedEffect
|
||||||
val route = topLevelRoutes[TaskPipelineRoute::class.qualifiedName!!] ?: return@LaunchedEffect
|
if (!deepLinkIntent.matchesWallencDeepLink()) return@LaunchedEffect
|
||||||
navState.changeTop(route)
|
navState.navHostController.handleDeepLink(deepLinkIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
val topLevelNavBarItems = remember {
|
val topLevelNavBarItems = remember {
|
||||||
@@ -121,7 +132,11 @@ fun WallencNavRoot(
|
|||||||
navState.navHostController,
|
navState.navHostController,
|
||||||
startDestination = topLevelRoutes[MainRoute::class.qualifiedName]!!
|
startDestination = topLevelRoutes[MainRoute::class.qualifiedName]!!
|
||||||
) {
|
) {
|
||||||
composable<MainRoute>(enterTransition = {
|
composable<MainRoute>(
|
||||||
|
deepLinks = listOf(
|
||||||
|
navDeepLink { uriPattern = WallencDeepLinks.MAIN_URI_PATTERN },
|
||||||
|
),
|
||||||
|
enterTransition = {
|
||||||
fadeIn(tween(200))
|
fadeIn(tween(200))
|
||||||
}, exitTransition = {
|
}, exitTransition = {
|
||||||
fadeOut(tween(200))
|
fadeOut(tween(200))
|
||||||
@@ -132,14 +147,22 @@ fun WallencNavRoot(
|
|||||||
viewModel = mainViewModel
|
viewModel = mainViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<SettingsRoute>(enterTransition = {
|
composable<SettingsRoute>(
|
||||||
|
deepLinks = listOf(
|
||||||
|
navDeepLink { uriPattern = WallencDeepLinks.SETTINGS_URI_PATTERN },
|
||||||
|
),
|
||||||
|
enterTransition = {
|
||||||
fadeIn(tween(200))
|
fadeIn(tween(200))
|
||||||
}, exitTransition = {
|
}, exitTransition = {
|
||||||
fadeOut(tween(200))
|
fadeOut(tween(200))
|
||||||
}) {
|
}) {
|
||||||
SettingsScreen(Modifier.padding(innerPaddings), settingsViewModel)
|
SettingsScreen(Modifier.padding(innerPaddings), settingsViewModel)
|
||||||
}
|
}
|
||||||
composable<TaskPipelineRoute>(enterTransition = {
|
composable<TaskPipelineRoute>(
|
||||||
|
deepLinks = listOf(
|
||||||
|
navDeepLink { uriPattern = WallencDeepLinks.TASKS_URI_PATTERN },
|
||||||
|
),
|
||||||
|
enterTransition = {
|
||||||
fadeIn(tween(200))
|
fadeIn(tween(200))
|
||||||
}, exitTransition = {
|
}, exitTransition = {
|
||||||
fadeOut(tween(200))
|
fadeOut(tween(200))
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user