Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion sample-compose/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
alias(libs.plugins.kover)
Expand Down Expand Up @@ -137,6 +137,7 @@ dependencies {
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.serialization.json)

debugImplementation(libs.chucker)
releaseImplementation(libs.chucker.noOp)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package co.nimblehq.sample.compose.ui.screens

import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.snapshots.SnapshotStateList
import co.nimblehq.sample.compose.navigation.Navigator
import kotlin.reflect.KClass

class FakeNavigator : Navigator {
override val backStack: SnapshotStateList<Any> = mutableStateListOf()

override fun goTo(destination: Any) {
backStack.add(destination)
}

override fun goBack() {
backStack.removeLastOrNull()
}

override fun goBackToLast(destinationClass: KClass<*>) {
val index = backStack.indexOfLast {
destinationClass.isInstance(it)
}

if (index in backStack.indices) {
backStack.removeRange(index + 1, backStack.size)
}
}

fun addToBackStack(listOfPastDestinations: List<Any>) {
backStack.addAll(listOfPastDestinations)
}

fun currentScreen(): Any? = backStack.lastOrNull()

fun currentScreenClass(): KClass<*>? = backStack.lastOrNull()?.let { it::class }
}

This file was deleted.

10 changes: 0 additions & 10 deletions sample-compose/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,6 @@
<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" />

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,122 @@
package co.nimblehq.sample.compose.di.modules.main

import android.content.Intent
import android.net.Uri
import androidx.compose.ui.platform.LocalContext
import androidx.core.net.toUri
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import co.nimblehq.sample.compose.navigation.EntryProviderInstaller
import co.nimblehq.sample.compose.navigation.Navigator
import co.nimblehq.sample.compose.navigation.NavigatorImpl
import co.nimblehq.sample.compose.ui.common.PATH_BASE
import co.nimblehq.sample.compose.ui.common.PATH_SEARCH
import co.nimblehq.sample.compose.ui.screens.details.DetailsScreen
import co.nimblehq.sample.compose.ui.screens.details.DetailsViewModel
import co.nimblehq.sample.compose.ui.screens.list.ListScreen
import co.nimblehq.sample.compose.ui.screens.login.LoginOrRegisterScreen
import co.nimblehq.sample.compose.ui.screens.login.LoginScreen
import co.nimblehq.sample.compose.ui.screens.search.SearchScreen
import co.nimblehq.sample.compose.util.LocalResultEventBus
import co.nimblehq.sample.compose.util.ResultEffect
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.components.ActivityRetainedComponent
import dagger.hilt.android.scopes.ActivityRetainedScoped
import dagger.multibindings.IntoSet

@Module
@InstallIn(ActivityComponent::class)
class MainActivityModule
@InstallIn(ActivityRetainedComponent::class)
object MainActivityModule {

@Provides
@ActivityRetainedScoped
fun provideNavigator(): Navigator = NavigatorImpl(startDestination = ListScreen)

@Suppress("LongMethod")
@IntoSet
@Provides
fun provideEntryProviderInstaller(navigator: Navigator): EntryProviderInstaller =
{
entry<ListScreen> {
ListScreen(
viewModel = hiltViewModel(),
onClickSearch = { navigator.goTo(SearchScreen) },
onItemClick = { uiModel ->
navigator.goTo(DetailsScreen.Details(uiModel.id.toIntOrNull() ?: 1))
}
)
}

entry<SearchScreen> {
val context = LocalContext.current
SearchScreen(
onClickCreateDeeplink = { username ->
val intent = Intent(
Intent.ACTION_VIEW,
"$PATH_BASE/$PATH_SEARCH?${DetailsScreen.Search::username.name}=${Uri.encode(username)}".toUri()
)
context.startActivity(intent)
},
onClickBack = navigator::goBack
)
}

entry<DetailsScreen.Details> { key ->
val viewModel = hiltViewModel<DetailsViewModel, DetailsViewModel.Factory>(
// Note: We need a new ViewModel for every new DetailsScreen instance. Usually
// we would need to supply a `key` String that is unique to the
// instance, however, the ViewModelStoreNavEntryDecorator (supplied
// above) does this for us, using `NavEntry.contentKey` to uniquely
// identify the viewModel.
//
// tl;dr: Make sure you use rememberViewModelStoreNavEntryDecorator()
// if you want a new ViewModel for each new navigation key instance.
creationCallback = { factory -> factory.create(key) }
)
val eventBus = LocalResultEventBus.current

ResultEffect<String> { username ->
viewModel.changeUsername(username)
eventBus.removeResult<String>()
}

DetailsScreen(
viewModel = viewModel,
navigateToLoginOrRegister = { navigator.goTo(LoginOrRegisterScreen) },
onClickBack = navigator::goBack
)
}

entry<DetailsScreen.Search> { key ->
val viewModel = hiltViewModel<DetailsViewModel, DetailsViewModel.Factory>(
creationCallback = { factory -> factory.create(key) }
)

DetailsScreen(
viewModel = viewModel,
navigateToLoginOrRegister = { navigator.goTo(LoginOrRegisterScreen) },
onClickBack = navigator::goBack
)
}

entry<LoginOrRegisterScreen> {
LoginOrRegisterScreen(
navigateToLogin = { navigator.goTo(LoginScreen) },
navigateToRegister = { /* NO-OP */ },
onClickBack = navigator::goBack
)
}

entry<LoginScreen> {
val eventBus = LocalResultEventBus.current
LoginScreen(
navigateToDetails = { username ->
eventBus.sendResult<String>(result = username)
navigator.goBackToLast(DetailsScreen::class)
},
onClickBack = navigator::goBack
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package co.nimblehq.sample.compose.extensions

import android.os.Build
import androidx.activity.ComponentActivity
import androidx.activity.enableEdgeToEdge

fun ComponentActivity.setEdgeToEdgeConfig() {
enableEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Force the 3-button navigation bar to be transparent
// See: https://developer.android.com/develop/ui/views/layout/edge-to-edge#create-transparent
window.isNavigationBarContrastEnforced = false
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package co.nimblehq.sample.compose.navigation

import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.navigation3.runtime.EntryProviderScope
import kotlin.reflect.KClass

typealias EntryProviderInstaller = EntryProviderScope<Any>.() -> Unit

interface Navigator {

val backStack: SnapshotStateList<Any>

fun goTo(destination: Any)

fun goBack()

fun goBackToLast(destinationClass: KClass<*>)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package co.nimblehq.sample.compose.navigation

import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.snapshots.SnapshotStateList
import dagger.hilt.android.scopes.ActivityRetainedScoped
import kotlin.reflect.KClass

@ActivityRetainedScoped
class NavigatorImpl(startDestination: Any) : Navigator {
override val backStack: SnapshotStateList<Any> = mutableStateListOf(startDestination)

override fun goTo(destination: Any) {
backStack.add(destination)
}

override fun goBack() {
backStack.removeLastOrNull()
}

override fun goBackToLast(destinationClass: KClass<*>) {
val index = backStack.indexOfLast {
destinationClass.isInstance(it)
}

if (index in backStack.indices) {
backStack.removeRange(index + 1, backStack.size)
}
}
}

This file was deleted.

Loading