diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 59a7612..8a81c59 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,6 +28,14 @@
+
+
+
+
+
+
+
+
{
repeat = false
if (sawVisible) {
- nm.cancel(FOREGROUND_NOTIFICATION_ID)
+ nm.cancel(WallencExternalLaunch.ForegroundTaskPipelineNotification.NOTIFICATION_ID)
indeterminateDotsPhaseByTaskId.clear()
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
@@ -97,7 +102,7 @@ class TaskPipelineForegroundService : Service() {
if (ui.tasks.isNotEmpty()) {
sawVisible = true
nm.notify(
- FOREGROUND_NOTIFICATION_ID,
+ WallencExternalLaunch.ForegroundTaskPipelineNotification.NOTIFICATION_ID,
buildAccumulatedNotification(ui.tasks),
)
}
@@ -129,7 +134,7 @@ class TaskPipelineForegroundService : Service() {
private fun ensureChannel() {
val nm = getSystemService(NotificationManager::class.java)
val channel = NotificationChannel(
- CHANNEL_ID,
+ WallencExternalLaunch.ForegroundTaskPipelineNotification.CHANNEL_ID,
getString(R.string.task_notification_channel_name),
NotificationManager.IMPORTANCE_LOW,
)
@@ -137,7 +142,7 @@ class TaskPipelineForegroundService : Service() {
}
private fun buildPlaceholderNotification(): Notification =
- NotificationCompat.Builder(this, CHANNEL_ID)
+ NotificationCompat.Builder(this, WallencExternalLaunch.ForegroundTaskPipelineNotification.CHANNEL_ID)
.setContentTitle(getString(R.string.task_notification_title))
.setContentText(getString(R.string.task_notification_preparing))
.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))
.setContentText(collapsedSubtext)
.setSmallIcon(android.R.drawable.stat_sys_download)
@@ -191,19 +199,18 @@ class TaskPipelineForegroundService : Service() {
private fun openTaskPipelinePendingIntent(): PendingIntent =
PendingIntent.getActivity(
this,
- REQUEST_CODE_OPEN_TASK_PIPELINE,
- Intent(this, MainActivity::class.java).apply {
- putExtra(MainActivity.EXTRA_OPEN_TASK_PIPELINE, true)
- flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
- },
+ WallencExternalLaunch.PendingIntentRequestCodes.NOTIFICATION_TASK_PIPELINE_OPEN_TASKS,
+ WallencExternalLaunch.mainActivityViewIntentForTasks(this),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
private fun cancelAllTasksPendingIntent(): PendingIntent =
PendingIntent.getService(
this,
- 0,
- Intent(this, TaskPipelineForegroundService::class.java).setAction(ACTION_CANCEL_ALL_TASKS),
+ WallencExternalLaunch.PendingIntentRequestCodes.NOTIFICATION_TASK_PIPELINE_CANCEL_SERVICE,
+ Intent(this, TaskPipelineForegroundService::class.java).setAction(
+ WallencExternalLaunch.FOREGROUND_SERVICE_ACTION_CANCEL_ALL_TASKS,
+ ),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
@@ -334,14 +341,6 @@ class TaskPipelineForegroundService : Service() {
}
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
/** Must match [R.layout.notification_wallenc_tasks_big] row count. */
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..dffd973
--- /dev/null
+++ b/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,5 @@
+
+
+
+ #FF1C1B1F
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 545704f..32c62cc 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,2 +1,4 @@
-
+
+ #FFFFFFFF
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index e6bc520..25b0609 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,5 +1,11 @@
-
+
-
-
\ No newline at end of file
+
+
diff --git a/ui/src/main/java/com/github/nullptroma/wallenc/ui/WallencUi.kt b/ui/src/main/java/com/github/nullptroma/wallenc/ui/WallencUi.kt
index 97a0d49..f974f8a 100644
--- a/ui/src/main/java/com/github/nullptroma/wallenc/ui/WallencUi.kt
+++ b/ui/src/main/java/com/github/nullptroma/wallenc/ui/WallencUi.kt
@@ -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(enterTransition = {
+ composable(
+ 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(enterTransition = {
+ composable(
+ deepLinks = listOf(
+ navDeepLink { uriPattern = WallencDeepLinks.SETTINGS_URI_PATTERN },
+ ),
+ enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
}) {
SettingsScreen(Modifier.padding(innerPaddings), settingsViewModel)
}
- composable(enterTransition = {
+ composable(
+ deepLinks = listOf(
+ navDeepLink { uriPattern = WallencDeepLinks.TASKS_URI_PATTERN },
+ ),
+ enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
diff --git a/ui/src/main/java/com/github/nullptroma/wallenc/ui/navigation/WallencDeepLinks.kt b/ui/src/main/java/com/github/nullptroma/wallenc/ui/navigation/WallencDeepLinks.kt
new file mode 100644
index 0000000..7c5c1e1
--- /dev/null
+++ b/ui/src/main/java/com/github/nullptroma/wallenc/ui/navigation/WallencDeepLinks.kt
@@ -0,0 +1,29 @@
+package com.github.nullptroma.wallenc.ui.navigation
+
+import android.content.Intent
+
+/**
+ * URI для входа извне приложения (уведомления, виджеты, шорткаты, другие Activity).
+ *
+ * Должны совпадать с `` в манифесте
+ * и с [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