Skip to content

Commit

Permalink
Merge pull request #569 from nimblehq/release/3.27.0
Browse files Browse the repository at this point in the history
[Release] 3.27.0
  • Loading branch information
ryan-conway authored Jan 9, 2024
2 parents c183baa + 91c7ac8 commit 8970fa7
Show file tree
Hide file tree
Showing 40 changed files with 496 additions and 269 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package co.nimblehq.sample.compose.test

import co.nimblehq.sample.compose.domain.model.Model

object MockUtil {

val models = listOf(
Model(
id = 1,
username = "name1",
),
Model(
id = 2,
username = "name2",
),
Model(
id = 3,
username = "name3",
),
)
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
package co.nimblehq.sample.compose.ui.screens.home
package co.nimblehq.sample.compose.ui.screens.main.home

import androidx.activity.compose.setContent
import androidx.compose.ui.test.*
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.rule.GrantPermissionRule
import co.nimblehq.sample.compose.domain.model.Model
import co.nimblehq.sample.compose.domain.usecase.*
import co.nimblehq.sample.compose.domain.usecase.GetModelsUseCase
import co.nimblehq.sample.compose.domain.usecase.IsFirstTimeLaunchPreferencesUseCase
import co.nimblehq.sample.compose.domain.usecase.UpdateFirstTimeLaunchPreferencesUseCase
import co.nimblehq.sample.compose.test.MockUtil
import co.nimblehq.sample.compose.test.TestDispatchersProvider
import co.nimblehq.sample.compose.ui.AppDestination
import co.nimblehq.sample.compose.ui.base.BaseDestination
import co.nimblehq.sample.compose.ui.screens.MainActivity
import co.nimblehq.sample.compose.ui.screens.main.MainDestination
import co.nimblehq.sample.compose.ui.theme.ComposeTheme
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import org.junit.*
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class HomeScreenTest {

Expand All @@ -36,13 +43,11 @@ class HomeScreenTest {
private val mockUpdateFirstTimeLaunchPreferencesUseCase: UpdateFirstTimeLaunchPreferencesUseCase = mockk()

private lateinit var viewModel: HomeViewModel
private var expectedAppDestination: AppDestination? = null
private var expectedDestination: BaseDestination? = null

@Before
fun setUp() {
every { mockGetModelsUseCase() } returns flowOf(
listOf(Model(1), Model(2), Model(3))
)
every { mockGetModelsUseCase() } returns flowOf(MockUtil.models)
every { mockIsFirstTimeLaunchPreferencesUseCase() } returns flowOf(false)

viewModel = HomeViewModel(
Expand All @@ -69,7 +74,7 @@ class HomeScreenTest {
fun when_clicking_on_a_list_item__it_navigates_to_Second_screen() = initComposable {
onNodeWithText("1").performClick()

assertEquals(expectedAppDestination, AppDestination.Second)
assertEquals(expectedDestination, MainDestination.Second)
}

private fun initComposable(
Expand All @@ -79,7 +84,7 @@ class HomeScreenTest {
ComposeTheme {
HomeScreen(
viewModel = viewModel,
navigator = { destination -> expectedAppDestination = destination }
navigator = { destination -> expectedDestination = destination }
)
}
}
Expand Down
19 changes: 19 additions & 0 deletions sample-compose/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="https" />
<data android:host="android.nimblehq.co" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="android" />
</intent-filter>
</activity>
</application>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package co.nimblehq.sample.compose.extensions

import androidx.lifecycle.SavedStateHandle

fun <T> SavedStateHandle.getThenRemove(key: String): T? {
return if (contains(key)) {
val value = get<T>(key)
remove<T>(key)
value
} else null
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,10 @@
package co.nimblehq.sample.compose.ui

import androidx.navigation.NamedNavArgument
import androidx.navigation.NavType
import androidx.navigation.navArgument
import co.nimblehq.sample.compose.ui.models.UiModel
import co.nimblehq.sample.compose.ui.base.BaseDestination

const val KeyId = "id"
const val KeyModel = "model"
sealed class AppDestination {

sealed class AppDestination(val route: String = "") {
object RootNavGraph : BaseDestination("rootNavGraph")

open val arguments: List<NamedNavArgument> = emptyList()

open var destination: String = route

open var parcelableArgument: Pair<String, Any?> = "" to null

object Up : AppDestination()

object Home : AppDestination("home")

object Second : AppDestination("second/{$KeyId}") {

override val arguments = listOf(
navArgument(KeyId) { type = NavType.StringType }
)

fun createRoute(id: String) = apply {
destination = "second/$id"
}
}

object Third : AppDestination("third") {
fun addParcel(value: UiModel) = apply {
parcelableArgument = KeyModel to value
}
}
object MainNavGraph : BaseDestination("mainNavGraph")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package co.nimblehq.sample.compose.ui

import androidx.compose.runtime.Composable
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navDeepLink
import co.nimblehq.sample.compose.ui.base.BaseDestination
import co.nimblehq.sample.compose.ui.screens.main.mainNavGraph

@Composable
fun AppNavGraph(
navController: NavHostController,
) {
NavHost(
navController = navController,
route = AppDestination.RootNavGraph.route,
startDestination = AppDestination.MainNavGraph.destination
) {
mainNavGraph(navController = navController)
}
}

fun NavGraphBuilder.composable(
destination: BaseDestination,
content: @Composable (NavBackStackEntry) -> Unit,
) {
composable(
route = destination.route,
arguments = destination.arguments,
deepLinks = destination.deepLinks.map {
navDeepLink {
uriPattern = it
}
},
content = content
)
}

/**
* Navigate to provided [BaseDestination] with a Pair of key value String and Data [parcel]
* Caution to use this method. This method use savedStateHandle to store the Parcelable data.
* When previousBackstackEntry is popped out from navigation stack, savedStateHandle will return null and cannot retrieve data.
* eg.Login -> Home, the Login screen will be popped from the back-stack on logging in successfully.
*/
fun NavHostController.navigate(destination: BaseDestination, parcel: Pair<String, Any?>? = null) {
when (destination) {
is BaseDestination.Up -> {
destination.results.forEach { (key, value) ->
previousBackStackEntry?.savedStateHandle?.set(key, value)
}
navigateUp()
}
else -> {
parcel?.let { (key, value) ->
currentBackStackEntry?.savedStateHandle?.set(key, value)
}
navigate(route = destination.destination)
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package co.nimblehq.sample.compose.ui.base

import androidx.navigation.NamedNavArgument

const val KeyResultOk = "keyResultOk"

abstract class BaseDestination(val route: String = "") {

open val arguments: List<NamedNavArgument> = emptyList()

open val deepLinks: List<String> = listOf(
"https://android.nimblehq.co/$route",
"android://$route",
)

open var destination: String = route

open var parcelableArgument: Pair<String, Any?> = "" to null

data class Up(val results: HashMap<String, Any> = hashMapOf()) : BaseDestination() {

fun addResult(key: String, value: Any) = apply {
results[key] = value
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package co.nimblehq.sample.compose.ui.base

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.nimblehq.sample.compose.ui.AppDestination
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.coroutines.CoroutineContext
Expand All @@ -19,7 +18,7 @@ abstract class BaseViewModel : ViewModel() {
protected val _error = MutableSharedFlow<Throwable>()
val error = _error.asSharedFlow()

protected val _navigator = MutableSharedFlow<AppDestination>()
protected val _navigator = MutableSharedFlow<BaseDestination>()
val navigator = _navigator.asSharedFlow()

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package co.nimblehq.sample.compose.ui.screens
package co.nimblehq.sample.compose.ui.common

import androidx.annotation.StringRes
import androidx.compose.material.Text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package co.nimblehq.sample.compose.ui.screens
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import co.nimblehq.sample.compose.ui.AppNavigation
import androidx.navigation.compose.rememberNavController
import co.nimblehq.sample.compose.ui.AppNavGraph
import co.nimblehq.sample.compose.ui.theme.ComposeTheme
import dagger.hilt.android.AndroidEntryPoint

Expand All @@ -14,7 +15,7 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
ComposeTheme {
AppNavigation()
AppNavGraph(navController = rememberNavController())
}
}
}
Expand Down
Loading

0 comments on commit 8970fa7

Please sign in to comment.