Skip to content

Commit

Permalink
Merge pull request #55 from ikarenkov/list-navigation-improvements
Browse files Browse the repository at this point in the history
List navigation improvements
  • Loading branch information
ikarenkov committed Jun 30, 2024
2 parents 98b2599 + fd6c699 commit 844d0e1
Show file tree
Hide file tree
Showing 16 changed files with 676 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Static code analysis
run-name: Running static code analysis
name: PR checks
run-name: Running static code analysis and unit tests
on:
push:
branches:
Expand All @@ -9,6 +9,7 @@ on:
- "dev"
jobs:
static-analysis-check:
name: Static code analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -20,6 +21,9 @@ jobs:
distribution: "temurin"
cache: gradle

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3

- name: "Run static analysis"
run: ./gradlew detektAll mergeLintSarif
continue-on-error: false
Expand All @@ -36,3 +40,28 @@ jobs:
sarif_file: build/reports/lint-merged.sarif
category: lint

run-unit-tests:
name: Run unit tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: "Set up JDK 17"
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
cache: gradle

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3

- name: Run unit tests
run: ./gradlew testDebugUnitTest --continue

- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: always()
with:
report_paths: '**/build/test-results/**/TEST-*.xml'
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[versions]
leakcanaryAndroid = "2.14"
modo = "0.9.0-rc1"
modo = "0.9.0"
androidGradlePlugin = "8.4.0"
detektComposeVersion = "0.3.20"
detektVersion = "1.23.6"
junit = "4.13.2"
androidxComposeBomModo = "2024.03.00"
androidxComposeBomApp = "2024.05.00"
androidxComposeBomApp = "2024.06.00"
androidxActivityCompose = "1.8.2"
androidxLifecycle = "2.7.0"
androidxCore = "1.13.1"
Expand All @@ -24,7 +24,7 @@ androidx-compose-bom-modo = { group = "androidx.compose", name = "compose-bom",
androidx-compose-bom-app = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBomApp" }
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }
androidx-compose-foundation-android-beta = { group = "androidx.compose.foundation", name = "foundation-android", version = "1.7.0-beta01" }
androidx-compose-foundation-beta = { group = "androidx.compose.foundation", name = "foundation", version = "1.7.0-beta01" }
androidx-compose-foundation-beta = { group = "androidx.compose.foundation", name = "foundation", version = "1.7.0-beta04" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-animation = { group = "androidx.compose.animation", name = "animation" }
androidx-compose-material = { group = "androidx.compose.material", name = "material" }
Expand Down
3 changes: 3 additions & 0 deletions modo-compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ dependencies {

tasks.withType(Test::class) {
useJUnitPlatform()
reports {
junitXml.required = true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ typealias ReducerProvider<State, Action> = () -> NavigationReducer<State, Action
/**
* Container for simple using [ContainerScreen] with [Parcelize]
*/
@Stable
class NavModel<State : NavigationState, Action : NavigationAction<State>>(
initialState: State,
val screenKey: ScreenKey = generateScreenKey()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package com.github.terrakok.modo.list

import com.github.terrakok.modo.NavigationContainer
import com.github.terrakok.modo.ReducerAction
import com.github.terrakok.modo.Screen
import com.github.terrakok.modo.ScreenKey

fun interface ListNavigationAction : ReducerAction<ListNavigationState> {

class RemoveScreens private constructor(
private val reducer: ReducerAction<ListNavigationState>
) : ListNavigationAction {

constructor(removeCondition: (pos: Int, screen: Screen) -> Boolean) : this(
ReducerAction { oldState ->
ListNavigationState(
oldState.screens.filterIndexed { index, screen -> !removeCondition(index, screen) }
)
}
)

constructor(screenToRemove: Screen, vararg screensToRemove: Screen) : this(
ReducerAction { oldState ->
val screensToRemoveSet = screensToRemove.toMutableSet().apply { add(screenToRemove) }
ListNavigationState(
oldState.screens.filter { screen -> screen !in screensToRemoveSet }
)
}
)

constructor(screenKeyToRemove: ScreenKey) : this(
{ _, screen ->
screen.screenKey == screenKeyToRemove
}
)

// Unable to use vararg because of https://youtrack.jetbrains.com/issue/KT-33565/Allow-vararg-parameter-of-inline-class-type
constructor(screenKeysToRemove: Set<ScreenKey>) : this(
ReducerAction { oldState ->
ListNavigationState(
oldState.screens.filter { screen -> screen.screenKey !in screenKeysToRemove }
)
}
)

override fun reduce(oldState: ListNavigationState): ListNavigationState = reducer.reduce(oldState)

companion object {
inline operator fun <reified T : Screen> invoke() = RemoveScreens { _, screen -> screen is T }
}
}

class AddScreens private constructor(
private val reducer: ReducerAction<ListNavigationState>
) : ListNavigationAction {

constructor(pos: Int, screen: Screen, vararg screens: Screen) : this(
ReducerAction { oldState ->
val newScreensCount = screens.size + 1
ListNavigationState(
List(oldState.screens.size + newScreensCount) {
when (it) {
in 0 until pos -> oldState.screens[it]
pos -> screen
in pos + 1 until pos + newScreensCount -> screens[it - pos - 1]
else -> oldState.screens[it - newScreensCount]
}
}
)
}
)

constructor(screen: Screen, vararg screens: Screen, addToEnd: Boolean = false) : this(
ReducerAction { oldState ->
ListNavigationState(
if (addToEnd) {
List(oldState.screens.size + screens.size + 1) {
when (it) {
in 0 until oldState.screens.size -> oldState.screens[it]
oldState.screens.size -> screen
else -> screens[it - oldState.screens.size - 1]
}
}
} else {
List(1 + screens.size + oldState.screens.size) {
when (it) {
0 -> screen
in 1..screens.size -> screens[it - 1]
else -> oldState.screens[it - 1 - screens.size]
}
}
}
)
}
)

override fun reduce(oldState: ListNavigationState): ListNavigationState = reducer.reduce(oldState)
}

class SetScreens private constructor(
private val reducer: ReducerAction<ListNavigationState>
) : ListNavigationAction {

constructor(vararg screens: Screen) : this(
ReducerAction { _ ->
ListNavigationState(screens.toList())
}
)

constructor(screens: List<Screen>) : this(
ReducerAction { _ -> ListNavigationState(screens) }
)

override fun reduce(oldState: ListNavigationState): ListNavigationState = reducer.reduce(oldState)
}

}

fun NavigationContainer<ListNavigationState, ListNavigationAction>.dispatch(action: (ListNavigationState) -> ListNavigationState) =
dispatch(ListNavigationAction(action))

fun NavigationContainer<ListNavigationState, ListNavigationAction>.addScreens(pos: Int, screen: Screen, vararg screens: Screen) =
dispatch(ListNavigationAction.AddScreens(pos, screen, *screens))

fun NavigationContainer<ListNavigationState, ListNavigationAction>.addScreens(screen: Screen, vararg screens: Screen, addToEnd: Boolean = false) =
dispatch(ListNavigationAction.AddScreens(screen, *screens, addToEnd = addToEnd))

fun NavigationContainer<ListNavigationState, ListNavigationAction>.removeScreens(removeCondition: (pos: Int, screen: Screen) -> Boolean) =
dispatch(ListNavigationAction.RemoveScreens(removeCondition))

fun NavigationContainer<ListNavigationState, ListNavigationAction>.removeScreen(screenKeyToRemove: ScreenKey) =
dispatch(ListNavigationAction.RemoveScreens(screenKeyToRemove))

fun NavigationContainer<ListNavigationState, ListNavigationAction>.removeScreens(screenToRemove: Screen) =
dispatch(ListNavigationAction.RemoveScreens(screenToRemove))

inline fun <reified T : Screen> NavigationContainer<ListNavigationState, ListNavigationAction>.removeScreens() =
dispatch(ListNavigationAction.RemoveScreens<T>())

fun NavigationContainer<ListNavigationState, ListNavigationAction>.setScreens(vararg screens: Screen) =
dispatch(ListNavigationAction.SetScreens(*screens))

fun NavigationContainer<ListNavigationState, ListNavigationAction>.removeAllScreens() =
dispatch(ListNavigationAction.SetScreens())

Original file line number Diff line number Diff line change
@@ -1,60 +1,25 @@
package com.github.terrakok.modo.list

import com.github.terrakok.modo.ContainerScreen
import com.github.terrakok.modo.NavModel
import com.github.terrakok.modo.NavigationContainer
import com.github.terrakok.modo.NavigationState
import com.github.terrakok.modo.ReducerAction
import com.github.terrakok.modo.Screen
import com.github.terrakok.modo.ScreenKey
import kotlinx.parcelize.Parcelize

typealias ListNavModel = NavModel<ListNavigationState, ListNavigationAction>

fun ListNavModel(screens: List<Screen>): ListNavModel = NavModel(ListNavigationState(screens = screens))

interface ListNavigationContainer : NavigationContainer<ListNavigationState, ListNavigationAction>

abstract class ListNavigationContainerScreen(
navModel: ListNavModel
) : ListNavigationContainer, ContainerScreen<ListNavigationState, ListNavigationAction>(navModel)

@Parcelize
class ListNavigationState(
data class ListNavigationState(
val screens: List<Screen>
) : NavigationState {
override fun getChildScreens(): List<Screen> = screens
}

fun interface ListNavigationAction : ReducerAction<ListNavigationState> {

class Remove(private val removeCondition: (pos: Int, screen: Screen) -> Boolean) : ListNavigationAction {

constructor(screenToRemove: Screen) : this({ _, screen -> screen == screenToRemove })

constructor(screenKeyToRemove: ScreenKey) : this({ _, screen -> screen.screenKey == screenKeyToRemove })

override fun reduce(oldState: ListNavigationState): ListNavigationState = ListNavigationState(
oldState.screens.filterIndexed { index, screen -> !removeCondition(index, screen) }
)

companion object {
inline operator fun <reified T : Screen> invoke() = Remove { _, screen -> screen is T }
}
}

class Add private constructor(private val nullablePos: Int?, private val screen: Screen) : ListNavigationAction {

constructor(pos: Int, screen: Screen) : this(nullablePos = pos, screen = screen)
constructor(screen: Screen) : this(nullablePos = null, screen = screen)

override fun reduce(oldState: ListNavigationState): ListNavigationState = ListNavigationState(
oldState.screens.toMutableList().apply {
if (nullablePos == null) {
add(screen)
} else {
add(nullablePos, screen)
}
}
)
}

}

interface ListNavigationContainer : NavigationContainer<ListNavigationState, ListNavigationAction>

fun ListNavigationContainer.dispatch(action: (ListNavigationState) -> ListNavigationState) =
dispatch(ListNavigationAction(action))
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ val LocalStackNavigation: ProvidableCompositionLocal<StackNavContainer> = static
/**
* Basic screen container that represents stack of [Screen]'s.
*/
@Stable
abstract class StackScreen(
navigationModel: StackNavModel
) : ContainerScreen<StackState, StackAction>(navigationModel), StackNavContainer {
Expand Down
Loading

0 comments on commit 844d0e1

Please sign in to comment.