Skip to content

Commit

Permalink
Merge pull request #2 from tooploox/task/navigation
Browse files Browse the repository at this point in the history
Navigation solution
  • Loading branch information
bpedryc authored Nov 23, 2023
2 parents db0c6e4 + 0af4dc8 commit 4f4f82b
Show file tree
Hide file tree
Showing 30 changed files with 648 additions and 80 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ dependencies {
coreLibraryDesugaring(libs.android.desugaring)
implementation(libs.koin.android)
testImplementation(libs.junit)
implementation(libs.compose.navigation)
implementation(libs.koin.compose)
}
11 changes: 2 additions & 9 deletions app/src/main/kotlin/co/touchlab/kampkit/android/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,17 @@ package co.touchlab.kampkit.android
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import co.touchlab.kampkit.android.ui.BreedsScreen
import co.touchlab.kampkit.android.ui.MainNavCoordinator
import co.touchlab.kampkit.android.ui.theme.KaMPKitTheme
import co.touchlab.kampkit.core.injectLogger
import co.touchlab.kampkit.ui.breeds.BreedsViewModel
import co.touchlab.kermit.Logger
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.component.KoinComponent

class MainActivity : ComponentActivity(), KoinComponent {

private val log: Logger by injectLogger("MainActivity")
private val viewModel: BreedsViewModel by viewModel()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
KaMPKitTheme {
BreedsScreen(viewModel, log)
MainNavCoordinator()
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion app/src/main/kotlin/co/touchlab/kampkit/android/MainApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.content.SharedPreferences
import android.util.Log
import co.touchlab.kampkit.core.AppInfo
import co.touchlab.kampkit.core.initKoin
import co.touchlab.kampkit.ui.breedDetails.BreedDetailsViewModel
import co.touchlab.kampkit.ui.breeds.BreedsViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.parameter.parametersOf
Expand All @@ -18,7 +19,14 @@ class MainApp : Application() {
initKoin(
module {
single<Context> { this@MainApp }
viewModel { BreedsViewModel(get(), get { parametersOf("BreedsViewModel") }) }
viewModel {
BreedsViewModel(get(), get { parametersOf("BreedsViewModel") })
}
viewModel { params ->
BreedDetailsViewModel(
params.get(), get(), get { parametersOf("BreedDetailsViewModel") }
)
}
single<SharedPreferences> {
get<Context>().getSharedPreferences("KAMPSTARTER_SETTINGS", MODE_PRIVATE)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package co.touchlab.kampkit.android.ui

import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.TweenSpec
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kampkit.android.R
import co.touchlab.kampkit.domain.breed.Breed
import co.touchlab.kampkit.ui.breedDetails.BreedDetailsViewModel
import co.touchlab.kampkit.ui.breedDetails.BreedDetailsViewState

@Composable
fun BreedDetailsScreen(viewModel: BreedDetailsViewModel) {
val state by viewModel.detailsState.collectAsStateWithLifecycle()
val error = state.error
Box(Modifier.fillMaxSize()) {
when {
state.isLoading -> Loading()
error != null -> Error(error)
else -> DetailsContents(
state = state,
onFavoriteClick = viewModel::onFavoriteClick
)
}
}
}

@Composable
private fun BoxScope.DetailsContents(
state: BreedDetailsViewState,
onFavoriteClick: () -> Unit
) {
Row(Modifier.align(Alignment.Center)) {
Text(state.breed?.name ?: "")
Spacer(Modifier.width(4.dp))
state.breed?.let { breed ->
FavoriteIcon(
breed = breed,
onClick = onFavoriteClick
)
}
}
}

@Composable
private fun BoxScope.Error(error: String) {
Text(
text = error,
color = Color.Red,
modifier = Modifier.align(Alignment.Center)
)
}

@Composable
fun BoxScope.Loading() {
CircularProgressIndicator(Modifier.align(Alignment.Center))
}

@Composable
fun FavoriteIcon(
breed: Breed,
onClick: () -> Unit
) {
Crossfade(
targetState = !breed.favorite,
animationSpec = TweenSpec(
durationMillis = 500,
easing = FastOutSlowInEasing
),
modifier = Modifier.clickable { onClick() }
) { fav ->
if (fav) {
Image(
painter = painterResource(id = R.drawable.ic_favorite_border_24px),
contentDescription = stringResource(R.string.favorite_breed, breed.name)
)
} else {
Image(
painter = painterResource(id = R.drawable.ic_favorite_24px),
contentDescription = stringResource(R.string.unfavorite_breed, breed.name)
)
}
}
}
63 changes: 32 additions & 31 deletions app/src/main/kotlin/co/touchlab/kampkit/android/ui/BreedsScreen.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package co.touchlab.kampkit.android.ui

import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.TweenSpec
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -23,13 +20,15 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kampkit.android.R
import co.touchlab.kampkit.domain.breed.Breed
import co.touchlab.kampkit.ui.breeds.BreedsNavRequest
import co.touchlab.kampkit.ui.breeds.BreedsViewModel
import co.touchlab.kampkit.ui.breeds.BreedsViewState
import co.touchlab.kermit.Logger
Expand All @@ -39,16 +38,26 @@ import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
@Composable
fun BreedsScreen(
viewModel: BreedsViewModel,
onBreedDetailsNavRequest: (breedId: Long) -> Unit,
log: Logger
) {
val breedsState by viewModel.breedsState.collectAsStateWithLifecycle()

breedsState.breedsNavRequest?.let { navRequest ->
LaunchedEffect(navRequest) {
if (navRequest is BreedsNavRequest.ToDetails) {
onBreedDetailsNavRequest(navRequest.breedId)
viewModel.onBreedDetailsNavRequestCompleted()
}
}
}

BreedsScreenContent(
dogsState = breedsState,
onRefresh = { viewModel.refreshBreeds() },
onSuccess = { data -> log.v { "View updating with ${data.size} breeds" } },
onError = { exception -> log.e { "Displaying error: $exception" } },
onFavorite = { viewModel.updateBreedFavorite(it.id) }
onBreedClick = { viewModel.onBreedClick(it) },
)
}

Expand All @@ -58,7 +67,7 @@ fun BreedsScreenContent(
onRefresh: () -> Unit = {},
onSuccess: (List<Breed>) -> Unit = {},
onError: (String) -> Unit = {},
onFavorite: (Breed) -> Unit = {}
onBreedClick: (breedId: Long) -> Unit = {},
) {
Surface(
color = MaterialTheme.colors.background,
Expand All @@ -76,7 +85,7 @@ fun BreedsScreenContent(
LaunchedEffect(breeds) {
onSuccess(breeds)
}
Success(successData = breeds, favoriteBreed = onFavorite)
Success(successData = breeds, onBreedClick = onBreedClick)
}
}

Expand All @@ -91,7 +100,7 @@ fun BreedsScreenContent(
}

@Composable
fun Empty() {
private fun Empty() {
Column(
modifier = Modifier
.fillMaxSize()
Expand All @@ -104,32 +113,32 @@ fun Empty() {
}

@Composable
fun Error(error: String) {
private fun Error(error: String) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = error)
Text(text = error, color = Color.Red)
}
}

@Composable
fun Success(
private fun Success(
successData: List<Breed>,
favoriteBreed: (Breed) -> Unit
onBreedClick: (breedId: Long) -> Unit
) {
DogList(breeds = successData, favoriteBreed)
DogList(breeds = successData, onBreedClick)
}

@Composable
fun DogList(breeds: List<Breed>, onItemClick: (Breed) -> Unit) {
fun DogList(breeds: List<Breed>, onItemClick: (breedId: Long) -> Unit) {
LazyColumn {
items(breeds) { breed ->
DogRow(breed) {
onItemClick(it)
onItemClick(breed.id)
}
Divider()
}
Expand All @@ -150,24 +159,16 @@ fun DogRow(breed: Breed, onClick: (Breed) -> Unit) {

@Composable
fun FavoriteIcon(breed: Breed) {
Crossfade(
targetState = !breed.favorite,
animationSpec = TweenSpec(
durationMillis = 500,
easing = FastOutSlowInEasing
if (breed.favorite) {
Image(
painter = painterResource(id = R.drawable.ic_favorite_24px),
contentDescription = stringResource(R.string.unfavorite_breed, breed.name)
)
} else {
Image(
painter = painterResource(id = R.drawable.ic_favorite_border_24px),
contentDescription = stringResource(R.string.favorite_breed, breed.name)
)
) { fav ->
if (fav) {
Image(
painter = painterResource(id = R.drawable.ic_favorite_border_24px),
contentDescription = stringResource(R.string.favorite_breed, breed.name)
)
} else {
Image(
painter = painterResource(id = R.drawable.ic_favorite_24px),
contentDescription = stringResource(R.string.unfavorite_breed, breed.name)
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package co.touchlab.kampkit.android.ui

import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import org.koin.androidx.compose.get
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf

private const val BREEDS = "breeds"
private const val BREED_DETAILS = "breedDetails"
private const val BREED_ID_ARG = "breedId"

@Composable
fun MainNavCoordinator() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "breeds") {
composable(BREEDS) {
BreedsScreen(
viewModel = koinViewModel(),
onBreedDetailsNavRequest = { breedId ->
navController.navigateToBreedDetails(breedId)
},
log = get { parametersOf("BreedsScreen") }
)
}
composable(
route = "$BREED_DETAILS/{$BREED_ID_ARG}",
arguments = listOf(navArgument(BREED_ID_ARG) { type = NavType.LongType })
) {
val breedId = it.arguments?.getLong(BREED_ID_ARG)
BreedDetailsScreen(
viewModel = koinViewModel { parametersOf(breedId) },
)
}
}
}

private fun NavController.navigateToBreedDetails(breedId: Long) {
navigate("$BREED_DETAILS/$breedId")
}
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ androidx-core = "1.9.0"
androidx-test-junit = "1.1.3"
androidx-activity-compose = "1.5.1"
androidx-lifecycle = "2.6.0"
androidx-navigation-compose = "2.5.3"

junit = "4.13.2"

Expand Down Expand Up @@ -51,6 +52,7 @@ compose-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "co
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
compose-material = { module = "androidx.compose.material:material", version.ref = "compose" }
compose-activity = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" }
compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation-compose" }

coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
Expand All @@ -62,6 +64,7 @@ junit = { module = "junit:junit", version.ref = "junit" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin"}

kotlinx-dateTime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }

Expand Down
Loading

0 comments on commit 4f4f82b

Please sign in to comment.