diff --git a/filepicker/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerFragment.kt b/filepicker/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerFragment.kt
index ac21c3f..27166dd 100644
--- a/filepicker/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerFragment.kt
+++ b/filepicker/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerFragment.kt
@@ -306,6 +306,10 @@ open class ArkFilePickerFragment :
FilePickerSideEffect.CannotPinFile -> {
activity?.toast(R.string.ark_file_picker_pin_folder_only)
}
+
+ FilePickerSideEffect.NestedRootProhibited -> {
+ activity?.toast(R.string.ark_file_nested_root_inside)
+ }
}
diff --git a/filepicker/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerViewModel.kt b/filepicker/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerViewModel.kt
index 19d4b1b..cafe146 100644
--- a/filepicker/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerViewModel.kt
+++ b/filepicker/src/main/java/dev/arkbuilders/components/filepicker/ArkFilePickerViewModel.kt
@@ -14,6 +14,7 @@ import org.orbitmvi.orbit.viewmodel.container
import dev.arkbuilders.arklib.data.folders.FoldersRepo
import dev.arkbuilders.arklib.utils.DeviceStorageUtils
import dev.arkbuilders.arklib.utils.listChildren
+import dev.arkbuilders.components.utils.hasNestedOrParentalRoot
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.nio.file.Path
@@ -44,6 +45,7 @@ internal sealed class FilePickerSideEffect {
data object AlreadyFavorite : FilePickerSideEffect()
data object PinAsFirstRoot : FilePickerSideEffect()
data object CannotPinFile : FilePickerSideEffect()
+ data object NestedRootProhibited : FilePickerSideEffect()
}
@@ -190,6 +192,14 @@ internal class ArkFilePickerViewModel(
val roots = rootsWithFavorites.keys
val root = roots.find { root -> file.startsWith(root) }
val favorites = rootsWithFavorites[root]?.flatten()
+
+ val hasNestedRoot = file.hasNestedOrParentalRoot(roots)
+
+ if (hasNestedRoot) {
+ postSideEffect(FilePickerSideEffect.NestedRootProhibited)
+ return@intent
+ }
+
val haveRoot = haveRoot()
root?.let {
diff --git a/filepicker/src/main/res/values/strings.xml b/filepicker/src/main/res/values/strings.xml
index e7f016e..20116db 100644
--- a/filepicker/src/main/res/values/strings.xml
+++ b/filepicker/src/main/res/values/strings.xml
@@ -16,6 +16,7 @@
Already be a Favorite folder!
Already be a Root folder!
Only folder can be pinned.
+ There\'s already nested root(s) inside.
Pin
- %d item
diff --git a/utils/src/main/java/dev/arkbuilders/components/utils/PathExt.kt b/utils/src/main/java/dev/arkbuilders/components/utils/PathExt.kt
new file mode 100644
index 0000000..df0d9a9
--- /dev/null
+++ b/utils/src/main/java/dev/arkbuilders/components/utils/PathExt.kt
@@ -0,0 +1,10 @@
+package dev.arkbuilders.components.utils
+
+import java.nio.file.Path
+
+fun Path.hasNestedOrParentalRoot(roots: Iterable): Boolean {
+ val hasNestedRoot = roots.any { path ->
+ this.startsWith(path) || path.startsWith(this)
+ }
+ return hasNestedRoot
+}
\ No newline at end of file
diff --git a/utils/src/test/java/dev/arkbuilders/components/utils/PathExtTest.kt b/utils/src/test/java/dev/arkbuilders/components/utils/PathExtTest.kt
new file mode 100644
index 0000000..09a74a3
--- /dev/null
+++ b/utils/src/test/java/dev/arkbuilders/components/utils/PathExtTest.kt
@@ -0,0 +1,132 @@
+package dev.arkbuilders.components.utils
+
+import junit.framework.TestCase.assertEquals
+import org.junit.Test
+import java.nio.file.Path
+import java.nio.file.Paths
+
+class PathExtTest {
+
+ // --- Tests with Set for roots parameter ---
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when there is an exact match in roots`() {
+ val path = Paths.get("/parent/child")
+ val roots = setOf(Paths.get("/parent/child"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when there is a direct parent in roots`() {
+ val path = Paths.get("/parent/child")
+ val roots = setOf(Paths.get("/parent"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when there is a direct child in roots`() {
+ val path = Paths.get("/parent")
+ val roots = setOf(Paths.get("/parent/child"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when there is a nested parent in roots`() {
+ val path = Paths.get("/parent/child/grandchild")
+ val roots = setOf(Paths.get("/parent"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when there is a nested child in roots`() {
+ val path = Paths.get("/parent")
+ val roots = setOf(Paths.get("/parent/child/grandchild"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return false when there are no nested or parental roots`() {
+ val path = Paths.get("/parent/child")
+ val roots = setOf(Paths.get("/unrelated"), Paths.get("/another"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), false)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return false when roots set is empty`() {
+ val path = Paths.get("/parent/child")
+ val roots = emptySet()
+ assertEquals(path.hasNestedOrParentalRoot(roots), false)
+ }
+
+ // --- Tests with List for roots parameter ---
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when an exact match exists in roots (List)`() {
+ val path = Paths.get("/parent/child")
+ val roots = listOf(Paths.get("/parent/child"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when a direct parent exists in roots (List)`() {
+ val path = Paths.get("/parent/child")
+ val roots = listOf(Paths.get("/parent"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when a direct child exists in roots (List)`() {
+ val path = Paths.get("/parent")
+ val roots = listOf(Paths.get("/parent/child"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when a nested parent exists in roots (List)`() {
+ val path = Paths.get("/parent/child/grandchild")
+ val roots = listOf(Paths.get("/parent"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return false when there are no nested or parental roots (List)`() {
+ val path = Paths.get("/parent/child")
+ val roots = listOf(Paths.get("/unrelated"), Paths.get("/another"))
+ assertEquals(path.hasNestedOrParentalRoot(roots), false)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return false when roots is empty`() {
+ val path = Paths.get("/parent/child")
+ val roots = emptyList()
+ assertEquals(path.hasNestedOrParentalRoot(roots), false)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when duplicates of an exact match exist in roots (List)`() {
+ val path = Paths.get("/parent/child")
+ val roots = listOf(Paths.get("/parent/child"), Paths.get("/parent/child")) // Duplicate exact match
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when duplicates of a direct parent exist in roots (List)`() {
+ val path = Paths.get("/parent/child")
+ val roots = listOf(Paths.get("/parent"), Paths.get("/parent")) // Duplicate direct parent
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return true when duplicates of a nested parent exist in roots (List)`() {
+ val path = Paths.get("/parent/child/grandchild")
+ val roots = listOf(Paths.get("/parent"), Paths.get("/parent")) // Duplicate nested parent
+ assertEquals(path.hasNestedOrParentalRoot(roots), true)
+ }
+
+ @Test
+ fun `hasNestedOrParentalRoot should return false when duplicates of unrelated paths exist in roots (List)`() {
+ val path = Paths.get("/parent/child")
+ val roots = listOf(Paths.get("/unrelated"), Paths.get("/unrelated")) // Duplicate unrelated paths
+ assertEquals(path.hasNestedOrParentalRoot(roots), false)
+ }
+}