diff --git a/deeplink-utils/.gitignore b/deeplink-utils/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/deeplink-utils/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/deeplink-utils/build.gradle b/deeplink-utils/build.gradle
new file mode 100644
index 0000000..b7f6ef2
--- /dev/null
+++ b/deeplink-utils/build.gradle
@@ -0,0 +1,15 @@
+apply from: "../android-configs/lib-config.gradle"
+
+dependencies {
+ def coroutinesVersion = '1.6.4'
+
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
+
+ constraints {
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") {
+ version {
+ require(coroutinesVersion)
+ }
+ }
+ }
+}
diff --git a/deeplink-utils/readme.md b/deeplink-utils/readme.md
new file mode 100644
index 0000000..57e9fcd
--- /dev/null
+++ b/deeplink-utils/readme.md
@@ -0,0 +1,52 @@
+
+# Сущность для централизованнной обработки deeplink'ов
+
+### Поддерживает
+- Добавление диплинка в очередь
+- Очистка очереди и отмена всех активных операций
+- Асинхронная обработка диплинка
+- Цепочка обработчиков для диплинка
+- Поиск обработчика в иерархии экранов
+
+### Использование
+
+Корневая навигация
+
+```kotlin
+DeepLinkHandler
+ .observeDeeplink(
+ observerOrder = 0,
+ navigateAction = { deeplink ->
+ binding.bottomNavigation.selectedItemId = deeplink.getBottomNavigationItem().menuId
+ })
+ .launchIn(viewLifecycleOwner.lifecycleScope)
+```
+
+
+Навигация на втором уровне
+
+```kotlin
+DeepLinkHandler
+ .observeDeeplink(
+ observerOrder = 1,
+ canHandle = { it.getBottomNavigationItem() == args.tabType },
+ isFinalObserver = { !it.isListDeeplink() },
+ navigateAction = { it.navigateToListScreen() }
+ )
+ .launchIn(viewLifecycleOwner.lifecycleScope)
+```
+
+Навигация на третьем уровне
+
+```kotlin
+DeepLinkHandler
+ .observeDeeplink(
+ observerOrder = 2,
+ canHandle = { it.getBottomNavigationItem() == args.tabType && it.isListDeeplink() },
+ navigateCondition = { it.getItemId() != null },
+ navigateAction = { it.navigateToDetailsScreen() }
+ )
+ .launchIn(viewLifecycleOwner.lifecycleScope)
+```
+
+Пример проекта можно посмотреть в: https://github.com/duwna/Deeplinks-test
\ No newline at end of file
diff --git a/deeplink-utils/src/main/AndroidManifest.xml b/deeplink-utils/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..623cf90
--- /dev/null
+++ b/deeplink-utils/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/deeplink-utils/src/main/java/ru/touchin/roboswag/deeplink_utils/DeeplinkHandler.kt b/deeplink-utils/src/main/java/ru/touchin/roboswag/deeplink_utils/DeeplinkHandler.kt
new file mode 100644
index 0000000..031f2c5
--- /dev/null
+++ b/deeplink-utils/src/main/java/ru/touchin/roboswag/deeplink_utils/DeeplinkHandler.kt
@@ -0,0 +1,76 @@
+package ru.touchin.roboswag.deeplink_utils
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Support observing deepliks with different conditions with queue and cancellation.
+ * Must be used as Singleton for the App.
+ * */
+class DeeplinkHandler {
+
+ /**
+ * @param observerOrder
+ * Entity index that can handle deeplink
+ * E.g. 0 - can be root bottom navigation
+ * 1 - first screen that handles chain of deeplink screens ("home" screen)
+ * 2 - second screen ("list" screen)
+ * */
+ class CurrentDeeplink(
+ val observerOrder: Int,
+ val deeplink: DeeplinkModel
+ )
+
+ private val currentDeeplink = MutableStateFlow(null)
+ private val deeplinkQueue = ArrayDeque()
+
+ fun handleDeeplink(url: String) {
+ val deeplink = DeeplinkModel(url)
+ deeplinkQueue.add(deeplink)
+
+ if (currentDeeplink.value == null) {
+ currentDeeplink.value = CurrentDeeplink(observerOrder = 0, deeplink = deeplink)
+ }
+ }
+
+ fun observeDeeplink(
+ observerOrder: Int = 0,
+ canHandle: (DeeplinkModel) -> Boolean = { true },
+ isFinalObserver: (DeeplinkModel) -> Boolean = { true },
+ navigateCondition: (DeeplinkModel) -> Boolean = { true },
+ navigateAction: suspend (DeeplinkModel) -> Unit = { }
+ ): Flow =
+ currentDeeplink
+ .filterNotNull()
+ .filter { it.observerOrder == observerOrder }
+ .map { it.deeplink }
+ .filter { canHandle(it) }
+ .onEach { if (navigateCondition(it) && currentDeeplink.value != null) navigateAction(it) }
+ .onEach { if (isFinalObserver(it)) markDeeplinkHandled() else it.sendToNextObserver(observerOrder) }
+
+ fun cancel(deeplink: DeeplinkModel) {
+ deeplinkQueue.remove(deeplink)
+ if (currentDeeplink.value?.deeplink == deeplink) currentDeeplink.value = null
+ }
+
+ fun cancelAll() {
+ deeplinkQueue.clear()
+ currentDeeplink.value = null
+ }
+
+ private fun markDeeplinkHandled() {
+ deeplinkQueue.removeFirstOrNull()
+
+ currentDeeplink.value = deeplinkQueue.firstOrNull()?.let {
+ CurrentDeeplink(observerOrder = 0, deeplink = it)
+ }
+ }
+
+ private fun DeeplinkModel.sendToNextObserver(order: Int) {
+ currentDeeplink.value = CurrentDeeplink(observerOrder = order + 1, deeplink = this)
+ }
+}
diff --git a/deeplink-utils/src/main/java/ru/touchin/roboswag/deeplink_utils/DeeplinkModel.kt b/deeplink-utils/src/main/java/ru/touchin/roboswag/deeplink_utils/DeeplinkModel.kt
new file mode 100644
index 0000000..179e6c4
--- /dev/null
+++ b/deeplink-utils/src/main/java/ru/touchin/roboswag/deeplink_utils/DeeplinkModel.kt
@@ -0,0 +1,17 @@
+package ru.touchin.roboswag.deeplink_utils
+
+import java.net.URL
+
+data class DeeplinkModel(
+ val url: String
+) {
+
+ val parsedUrl: URL? by lazy(LazyThreadSafetyMode.NONE) {
+ runCatching { URL(url) }.getOrNull()
+ }
+
+ val pathParts: List?
+ get() = parsedUrl?.path
+ ?.split("/")
+ ?.filter { it.isNotEmpty() }
+}