Skip to content

Commit 7ab0ce7

Browse files
authored
Merge pull request #881 from arkivanov/pages-setItems
Added PagesNavigator#setItems extension function
2 parents bbf7f24 + 781f04b commit 7ab0ce7

File tree

6 files changed

+217
-75
lines changed

6 files changed

+217
-75
lines changed

decompose/api/android/decompose.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,8 @@ public final class com/arkivanov/decompose/router/pages/PagesNavigatorExtKt {
360360
public static synthetic fun selectNext$default (Lcom/arkivanov/decompose/router/pages/PagesNavigator;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
361361
public static final fun selectPrev (Lcom/arkivanov/decompose/router/pages/PagesNavigator;ZLkotlin/jvm/functions/Function2;)V
362362
public static synthetic fun selectPrev$default (Lcom/arkivanov/decompose/router/pages/PagesNavigator;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
363+
public static final fun setItems (Lcom/arkivanov/decompose/router/pages/PagesNavigator;Lkotlin/jvm/functions/Function1;)V
364+
public static final fun setItems (Lcom/arkivanov/decompose/router/pages/PagesNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
363365
}
364366

365367
public final class com/arkivanov/decompose/router/pages/PagesWebNavigationKt {

decompose/api/decompose.klib.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,8 @@ final inline fun (com.arkivanov.decompose.router.stack/StackNavigator<*>).com.ar
636636
final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.items/ItemsNavigator<#A>).com.arkivanov.decompose.router.items/setActiveItems(crossinline kotlin/Function1<kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>, kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>>) // com.arkivanov.decompose.router.items/setActiveItems|setActiveItems@com.arkivanov.decompose.router.items.ItemsNavigator<0:0>(kotlin.Function1<kotlin.collections.Map<0:0,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>,kotlin.collections.Map<0:0,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>>){0§<kotlin.Any>}[0]
637637
final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.items/ItemsNavigator<#A>).com.arkivanov.decompose.router.items/setActiveItems(crossinline kotlin/Function1<kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>, kotlin.collections/Map<#A, com.arkivanov.decompose.router.items/Items.ActiveLifecycleState>>, crossinline kotlin/Function2<com.arkivanov.decompose.router.items/Items<#A>, com.arkivanov.decompose.router.items/Items<#A>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.items/setActiveItems|setActiveItems@com.arkivanov.decompose.router.items.ItemsNavigator<0:0>(kotlin.Function1<kotlin.collections.Map<0:0,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>,kotlin.collections.Map<0:0,com.arkivanov.decompose.router.items.Items.ActiveLifecycleState>>;kotlin.Function2<com.arkivanov.decompose.router.items.Items<0:0>,com.arkivanov.decompose.router.items.Items<0:0>,kotlin.Unit>){0§<kotlin.Any>}[0]
638638
final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.items/ItemsNavigator<#A>).com.arkivanov.decompose.router.items/setItems(crossinline kotlin/Function1<kotlin.collections/List<#A>, kotlin.collections/List<#A>>, crossinline kotlin/Function2<com.arkivanov.decompose.router.items/Items<#A>, com.arkivanov.decompose.router.items/Items<#A>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.items/setItems|setItems@com.arkivanov.decompose.router.items.ItemsNavigator<0:0>(kotlin.Function1<kotlin.collections.List<0:0>,kotlin.collections.List<0:0>>;kotlin.Function2<com.arkivanov.decompose.router.items.Items<0:0>,com.arkivanov.decompose.router.items.Items<0:0>,kotlin.Unit>){0§<kotlin.Any>}[0]
639+
final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.pages/PagesNavigator<#A>).com.arkivanov.decompose.router.pages/setItems(crossinline kotlin/Function1<kotlin.collections/List<#A>, kotlin.collections/List<#A>>) // com.arkivanov.decompose.router.pages/setItems|setItems@com.arkivanov.decompose.router.pages.PagesNavigator<0:0>(kotlin.Function1<kotlin.collections.List<0:0>,kotlin.collections.List<0:0>>){0§<kotlin.Any>}[0]
640+
final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.pages/PagesNavigator<#A>).com.arkivanov.decompose.router.pages/setItems(crossinline kotlin/Function1<kotlin.collections/List<#A>, kotlin.collections/List<#A>>, crossinline kotlin/Function2<com.arkivanov.decompose.router.pages/Pages<#A>, com.arkivanov.decompose.router.pages/Pages<#A>, kotlin/Unit>) // com.arkivanov.decompose.router.pages/setItems|setItems@com.arkivanov.decompose.router.pages.PagesNavigator<0:0>(kotlin.Function1<kotlin.collections.List<0:0>,kotlin.collections.List<0:0>>;kotlin.Function2<com.arkivanov.decompose.router.pages.Pages<0:0>,com.arkivanov.decompose.router.pages.Pages<0:0>,kotlin.Unit>){0§<kotlin.Any>}[0]
639641
final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.slot/SlotNavigator<#A>).com.arkivanov.decompose.router.slot/activate(#A, crossinline kotlin/Function0<kotlin/Unit> = ...) // com.arkivanov.decompose.router.slot/activate|activate@com.arkivanov.decompose.router.slot.SlotNavigator<0:0>(0:0;kotlin.Function0<kotlin.Unit>){0§<kotlin.Any>}[0]
640642
final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNavigator<#A>).com.arkivanov.decompose.router.stack/bringToFront(#A, crossinline kotlin/Function0<kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/bringToFront|bringToFront@com.arkivanov.decompose.router.stack.StackNavigator<0:0>(0:0;kotlin.Function0<kotlin.Unit>){0§<kotlin.Any>}[0]
641643
final inline fun <#A: kotlin/Any> (com.arkivanov.decompose.router.stack/StackNavigator<#A>).com.arkivanov.decompose.router.stack/pop(crossinline kotlin/Function1<kotlin/Boolean, kotlin/Unit> = ...) // com.arkivanov.decompose.router.stack/pop|pop@com.arkivanov.decompose.router.stack.StackNavigator<0:0>(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){0§<kotlin.Any>}[0]

decompose/api/jvm/decompose.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,8 @@ public final class com/arkivanov/decompose/router/pages/PagesNavigatorExtKt {
340340
public static synthetic fun selectNext$default (Lcom/arkivanov/decompose/router/pages/PagesNavigator;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
341341
public static final fun selectPrev (Lcom/arkivanov/decompose/router/pages/PagesNavigator;ZLkotlin/jvm/functions/Function2;)V
342342
public static synthetic fun selectPrev$default (Lcom/arkivanov/decompose/router/pages/PagesNavigator;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
343+
public static final fun setItems (Lcom/arkivanov/decompose/router/pages/PagesNavigator;Lkotlin/jvm/functions/Function1;)V
344+
public static final fun setItems (Lcom/arkivanov/decompose/router/pages/PagesNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
343345
}
344346

345347
public final class com/arkivanov/decompose/router/pages/PagesWebNavigationKt {

decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/pages/PagesNavigatorExt.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.arkivanov.decompose.router.pages
22

3+
import com.arkivanov.decompose.ExperimentalDecomposeApi
4+
35
/**
46
* A convenience method for [PagesNavigator.navigate].
57
*/
@@ -103,6 +105,45 @@ fun <C : Any> PagesNavigator<C>.clear(onComplete: (newPages: Pages<C>, oldPages:
103105
)
104106
}
105107

108+
/**
109+
* Replaces the components with the provided list. The [Pages.selectedIndex] parameter
110+
* is automatically coerced within the new list's range.
111+
*
112+
* @param items a transformer function from the current item list to a new one.
113+
* See [Pages.items].
114+
* @param onComplete called when the navigation is finished (either synchronously or asynchronously).
115+
*/
116+
@ExperimentalDecomposeApi
117+
inline fun <C : Any> PagesNavigator<C>.setItems(
118+
crossinline items: (List<C>) -> List<C>,
119+
crossinline onComplete: (newPages: Pages<C>, oldPages: Pages<C>) -> Unit,
120+
) {
121+
navigate(
122+
transformer = {
123+
val newItems = items(it.items)
124+
it.copy(
125+
items = newItems,
126+
selectedIndex = if (newItems.isNotEmpty()) it.selectedIndex.coerceIn(newItems.indices) else -1,
127+
)
128+
},
129+
onComplete = { newPages, oldPages -> onComplete(newPages, oldPages) },
130+
)
131+
}
132+
133+
/**
134+
* Replaces the components with the provided list. The [Pages.selectedIndex] parameter
135+
* is automatically coerced within the new list's range.
136+
*
137+
* @param items a transformer function from the current item list to a new one.
138+
* See [Pages.items].
139+
*/
140+
@ExperimentalDecomposeApi
141+
inline fun <C : Any> PagesNavigator<C>.setItems(
142+
crossinline items: (List<C>) -> List<C>,
143+
) {
144+
setItems(items = items, onComplete = { _, _ -> })
145+
}
146+
106147
private inline fun <C : Any> Pages<C>.coerceSelectedIndex(circular: Boolean = false, update: (Int) -> Int): Pages<C> =
107148
if (items.isNotEmpty()) {
108149
var newIndex = update(selectedIndex)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.arkivanov.decompose.router.pages
2+
3+
import com.arkivanov.decompose.TestComponentContext
4+
import com.arkivanov.decompose.value.getValue
5+
import kotlin.test.Test
6+
7+
@Suppress("TestFunctionName")
8+
class ChildPagesSetItemsTest : BaseChildPagesTest() {
9+
10+
private val ctx = TestComponentContext()
11+
12+
@Test
13+
fun WHEN_setItems_with_new_items_THEN_items_added() {
14+
val pages by ctx.childPages(initialPages = Pages(items = listOf(0, 1, 2), selectedIndex = 2))
15+
16+
navigation.setItems { it + listOf(3, 4) }
17+
18+
pages.assertPages(selectedIndex = 2, size = 5)
19+
}
20+
21+
@Test
22+
fun WHEN_setItems_with_items_removed_from_end_THEN_items_removed_and_selected_index_coerced() {
23+
val pages by ctx.childPages(initialPages = Pages(items = listOf(0, 1, 2, 3, 4), selectedIndex = 4))
24+
25+
navigation.setItems { it.dropLast(2) }
26+
27+
pages.assertPages(selectedIndex = 2, size = 3)
28+
}
29+
30+
@Test
31+
fun WHEN_setItems_with_items_removed_from_start_THEN_items_removed() {
32+
val pages by ctx.childPages(initialPages = Pages(items = listOf(0, 1, 2, 3, 4), selectedIndex = 0))
33+
34+
navigation.setItems { it.drop(2) }
35+
36+
pages.assertPages(ids = listOf(2, 3, 4), selectedIndex = 0)
37+
}
38+
39+
@Test
40+
fun WHEN_setItems_with_all_items_removed_THEN_items_removed() {
41+
val pages by ctx.childPages(initialPages = Pages(items = listOf(0, 1, 2), selectedIndex = 0))
42+
43+
navigation.setItems { emptyList() }
44+
45+
pages.assertPages(ids = emptyList(), selectedIndex = -1)
46+
}
47+
}

docs/navigation/pages/navigation.md

Lines changed: 123 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -30,106 +30,154 @@ There are `PagesNavigator` [extension functions](https://github.com/arkivanov/De
3030

3131
Selects the next component. If the currently selected component is the last one, then depending on the [circular] parameter, either nothing happens or the first component is selected.
3232

33-
```title="Before"
34-
1: [A, B*, C]
35-
2: [A, B, C*]
36-
3: [A, B, C*]
37-
```
38-
39-
```
40-
1: navigation.selectNext()
41-
2: navigation.selectNext(circular = false)
42-
3: navigation.selectNext(circular = true)
43-
```
44-
45-
```title="After"
46-
1: [A, B, C*]
47-
2: [A, B, C*]
48-
3: [A*, B, C]
49-
```
33+
!!! note "Illustration"
34+
35+
```title="Before"
36+
1: [A, B*, C]
37+
2: [A, B, C*]
38+
3: [A, B, C*]
39+
```
40+
41+
```
42+
1: navigation.selectNext()
43+
2: navigation.selectNext(circular = false)
44+
3: navigation.selectNext(circular = true)
45+
```
46+
47+
```title="After"
48+
1: [A, B, C*]
49+
2: [A, B, C*]
50+
3: [A*, B, C]
51+
```
5052

5153
### selectPrev
5254

5355
elects the previous component. If the currently selected component is the first one, then depending on the [circular] parameter, either nothing happens or the last component is selected.
5456

55-
```title="Before"
56-
1: [A, B*, C]
57-
2: [A*, B, C]
58-
3: [A*, B, C]
59-
```
60-
61-
```
62-
1: navigation.selectPrev()
63-
2: navigation.selectPrev(circular = false)
64-
3: navigation.selectPrev(circular = true)
65-
```
66-
67-
```title="After"
68-
1: [A*, B, C]
69-
2: [A*, B, C]
70-
3: [A, B, C*]
71-
```
57+
!!! note "Illustration"
58+
59+
```title="Before"
60+
1: [A, B*, C]
61+
2: [A*, B, C]
62+
3: [A*, B, C]
63+
```
64+
65+
```
66+
1: navigation.selectPrev()
67+
2: navigation.selectPrev(circular = false)
68+
3: navigation.selectPrev(circular = true)
69+
```
70+
71+
```title="After"
72+
1: [A*, B, C]
73+
2: [A*, B, C]
74+
3: [A, B, C*]
75+
```
7276

7377
### selectFirst
7478

7579
Selects the first component.
7680

77-
```title="Before"
78-
[A, B*, C]
79-
```
80-
81-
```
82-
navigation.selectFirst()
83-
```
81+
!!! note "Illustration"
8482

85-
```title="After"
86-
[A*, B, C]
87-
```
83+
```title="Before"
84+
[A, B*, C]
85+
```
86+
87+
```
88+
navigation.selectFirst()
89+
```
90+
91+
```title="After"
92+
[A*, B, C]
93+
```
8894

8995
### selectLast
9096

9197
Selects the last component.
9298

93-
```title="Before"
94-
[A, B*, C]
95-
```
96-
97-
```
98-
navigation.selectLast()
99-
```
99+
!!! note "Illustration"
100100

101-
```title="After"
102-
[A, B, C*]
103-
```
101+
```title="Before"
102+
[A, B*, C]
103+
```
104+
105+
```
106+
navigation.selectLast()
107+
```
108+
109+
```title="After"
110+
[A, B, C*]
111+
```
104112

105113
### select(index)
106114

107115
Selects the component at the specified [index]. Throws [IllegalArgumentException] if the index is out of bounds.
108116

109-
```title="Before"
110-
[A*, B, C]
111-
```
112-
113-
```
114-
navigation.select(2)
115-
```
117+
!!! note "Illustration"
116118

117-
```title="After"
118-
[A, B, C*]
119-
```
119+
```title="Before"
120+
[A*, B, C]
121+
```
122+
123+
```
124+
navigation.select(2)
125+
```
126+
127+
```title="After"
128+
[A, B, C*]
129+
```
120130

121131
### clear
122132

123133
Clears the current [Pages] state, i.e. removes all components.
124134

125-
```title="Before"
126-
[A, B*, C]
127-
```
128-
129-
```
130-
navigation.clear()
131-
```
132-
133-
```title="After"
134-
[]
135-
```
135+
!!! note "Illustration"
136+
137+
```title="Before"
138+
[A, B*, C]
139+
```
140+
141+
```
142+
navigation.clear()
143+
```
144+
145+
```title="After"
146+
[]
147+
```
148+
149+
### setItems
150+
151+
!!! note
152+
153+
Available since version `3.4.0-alpha02`.
154+
155+
Replaces the components with the provided list. The `selectedIndex` parameter is automatically coerced within the new list's range.
156+
157+
!!! note "Illustration 1"
158+
159+
```title="Before"
160+
[A, B, C*]
161+
```
162+
163+
```
164+
navigation.setItems { it + listOf(D) }
165+
```
166+
167+
```title="After"
168+
[A, B, C*, D]
169+
```
170+
171+
!!! note "Illustration 2"
172+
173+
```title="Before"
174+
[A, B, C*]
175+
```
176+
177+
```
178+
navigation.setItems { it - C }
179+
```
180+
181+
```title="After"
182+
[A, B*]
183+
```

0 commit comments

Comments
 (0)