Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add navigation for compose #20

Merged
merged 11 commits into from
Dec 8, 2022
10 changes: 8 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ android {

defaultConfig {
applicationId = "com.github.felipecastilhos.pokedexandroid"
minSdk = BuildConfigVersions.minSdk
minSdk = BuildConfigVersions
.minSdk
targetSdk = BuildConfigVersions.targetSdk
versionCode = BuildConfigVersions.versionCode
versionName = BuildConfigVersions.versionName
Expand Down Expand Up @@ -56,6 +57,8 @@ android {
}

dependencies {
implementation("androidx.test.ext:junit-ktx:1.1.3")

// Common libraries
jetpackCoreLibraries()
jetpackKotlinExtensionsLibraries()
Expand All @@ -75,8 +78,11 @@ dependencies {
// Add log libraries
logLibraries()

// Add async image libraries
asyncImageLibraries()

// Add Tests
unitTestsLibraries()
instrumentationTestsLibraries()
mockLibraries()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.github.felipecastilhos.pokedexandroid

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController

@Composable
fun PokemonNavGraph(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
startDestination: String = PokemonDestinations.SEARCH_ROUTE,
navActions:PokemonNavigationActions = remember(navController) {
felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved
felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved
PokemonNavigationActions(navController)
}
) {
NavHost(
navController = navController,
startDestination = startDestination,
modifier = modifier
) {
pokemonGraph(navActions)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.github.felipecastilhos.pokedexandroid

import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.github.felipecastilhos.pokedexandroid.features.pokemon.PokemonDetailScreen
import com.github.felipecastilhos.pokedexandroid.features.pokemon.PokemonSearchScreen

/**
* Screens used in [PokemonDestinations]
*/
private object PokemonScreens {
const val POKEMON_SEARCH = "search"
const val POKEMON_DETAILS = "details"
}

/**
* Available routes for Pokemons
*/
object PokemonDestinations {
const val SEARCH_ROUTE = PokemonScreens.POKEMON_SEARCH
const val DETAIL_ROUTE = PokemonScreens.POKEMON_DETAILS
}

class PokemonNavigationActions(private val navController: NavController) {
fun navigateToSearch() {
navController.navigate(PokemonDestinations .SEARCH_ROUTE) {
felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved
felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved
launchSingleTop = true
}
}

fun navigateToDetails() {
navController.navigate(PokemonDestinations.DETAIL_ROUTE) {
launchSingleTop = true
}
}
}

fun NavGraphBuilder.pokemonGraph(navigationActions: PokemonNavigationActions) {
composable(PokemonDestinations.SEARCH_ROUTE) {
PokemonSearchScreen(onNavigateToPokemonDetails = { navigationActions.navigateToDetails() })
}

composable(PokemonDestinations.DETAIL_ROUTE) {
PokemonDetailScreen()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,75 +3,31 @@ package com.github.felipecastilhos.pokedexandroid.features.pokemon
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.github.felipecastilhos.pokedexandroid.core.logs.LogHandler
import com.github.felipecastilhos.pokedexandroid.PokemonNavGraph
import com.github.felipecastilhos.pokedexandroid.core.ui.theme.PokedexandroidTheme
import com.github.felipecastilhos.pokedexandroid.features.pokemon.domain.viewmodel.PokemonListEntryUiData
import com.github.felipecastilhos.pokedexandroid.features.pokemon.domain.viewmodel.PokemonListViewModel
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved
class HomeActivity : ComponentActivity() {
private val pokemonListViewModel: PokemonListViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pokemonListViewModel
setContent {
val e by pokemonListViewModel.stateFlow.collectAsState()
PokedexandroidTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
if (e.isLoading) {
LogHandler.d("LOADING : ${e.isLoading}")
Text("Carregando...")
} else {
LogHandler.d("Pokemon Name: ${e}")
SearchPokemonResult(e.pokemons)
}
}
PokemonNavGraph()
}
}
}
}


@Composable
fun SearchPokemonResult(
pokemons: List<PokemonListEntryUiData>
) {
Column(modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())) {
pokemons.forEach {
Card(
modifier = Modifier
.padding(16.dp)
.clickable { },
elevation = 10.dp
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(text = it.name)
}
}
}
fun PokemonDetailScreen() {
Column {
Text("Some details here")
}
}


felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved
felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.github.felipecastilhos.pokedexandroid.features.pokemon
felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.AsyncImage
import com.github.felipecastilhos.pokedexandroid.features.pokemon.domain.viewmodel.PokemonListEntryUiData
import com.github.felipecastilhos.pokedexandroid.features.pokemon.domain.viewmodel.PokemonListViewModel

@Composable
fun PokemonSearchScreen(
viewModel: PokemonListViewModel = hiltViewModel(),
onNavigateToPokemonDetails: () -> Unit
) {
val viewState = viewModel.stateFlow.collectAsState().value

viewState.apply {
if (isLoading) {
LoadingScreen()
} else {
PokenonSearchList(
pokemons = pokemons,
onNavigateToPokemonDetails = onNavigateToPokemonDetails
)
}
}
}

@Composable
fun PokenonSearchList(
pokemons: List<PokemonListEntryUiData>,
onNavigateToPokemonDetails: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
) {
pokemons.forEach {
Card(
modifier = Modifier
.padding(16.dp)
.clickable(onClick = onNavigateToPokemonDetails),
elevation = 10.dp
) {
Column(
modifier = Modifier.padding(16.dp)
) {
AsyncImage(model = it.thumbUrl, contentDescription = "")
Text(text = it.name.capitalize())
}
}
}
}
}


felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved
felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved
@Composable
fun LoadingScreen() {
Text("Carregando...")
}


felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved
felipecastilhos marked this conversation as resolved.
Show resolved Hide resolved
@Composable
fun DetailScren() {
Text("Some pokemon details")
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.felipecastilhos.pokedexandroid.core.coroutines.DispatcherProvider
import com.github.felipecastilhos.pokedexandroid.core.logs.LogHandler
import com.github.felipecastilhos.pokedexandroid.core.ui.uistate.LoadingUiState
import com.github.felipecastilhos.pokedexandroid.features.pokemon.data.datasource.models.PokemonList
import com.github.felipecastilhos.pokedexandroid.features.pokemon.data.datasource.models.PokemonListEntry
import com.github.felipecastilhos.pokedexandroid.features.pokemon.domain.usecase.PokemonUseCase
Expand Down Expand Up @@ -65,7 +64,7 @@ fun List<PokemonListEntry>.toUiData(): List<PokemonListEntryUiData> {
}

fun thumbUrl(index: Int) =
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/${index}.png"
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/${index + 1}.png"

data class PokemonListUiState(
val isLoading: Boolean = true,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@ package com.github.felipecastilhos.pokedexandroid
import com.github.felipecastilhos.pokedexandroid.core.data.remote.DataSourceError
import com.github.felipecastilhos.pokedexandroid.features.pokemon.data.datasource.models.Pokemon
import com.github.felipecastilhos.pokedexandroid.features.pokemon.data.datasource.PokemonDataSource
import com.github.felipecastilhos.pokedexandroid.features.pokemon.data.datasource.models.PokemonList

class FakePokemonDataSource(private val pokemon: Pokemon) : PokemonDataSource {
class FakePokemonDataSource(private val pokemon: Pokemon, private val pokemonList: PokemonList) : PokemonDataSource {
override suspend fun pokemonData(): Result<Pokemon> {
return Result.success(pokemon)
}

override suspend fun list(offset: Long, limit: Int): Result<PokemonList> {
return Result.success(pokemonList)
}
}

class FakeFailingPokemonDataSource : PokemonDataSource {
override suspend fun pokemonData(): Result<Pokemon> {
return Result.failure(exception = DataSourceError.Unexpected(R.string.server_unexpected_error))
}

override suspend fun list(offset: Long, limit: Int): Result<PokemonList> {
return Result.failure(exception = DataSourceError.Unexpected(R.string.server_unexpected_error))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class PokemonRepositoryTest {
weight = 9.0
)

private val pokemonEntries = listOf<PokemonListEntry>(PokemonListEntry("Bulbassauro", "http://pokeapi/bulba"))
private val pokemonList = PokemonList(0L, "21", "20", pokemonEntries)

private lateinit var fakePokemonDataSource: FakePokemonDataSource
private lateinit var pokemonRepository: DefaultPokemonRemoteDataRepository

Expand All @@ -31,7 +34,7 @@ class PokemonRepositoryTest {
}

private fun mockSuccessRepository() {
fakePokemonDataSource = FakePokemonDataSource(pokemonSquirtle)
fakePokemonDataSource = FakePokemonDataSource(pokemonSquirtle, pokemonList)
pokemonRepository = DefaultPokemonRemoteDataRepository(fakePokemonDataSource)
}

Expand All @@ -46,6 +49,12 @@ class PokemonRepositoryTest {
assert(result.getOrNull()?.pokedexNumber == 7)
}

@Test
fun search_GetBulbassauroInPokemonList() = runTest {
val result = pokemonRepository.list(0, 10)
assert(result.getOrNull()?.results?.first()?.name == "Bulbassauro")
}

@ExperimentalCoroutinesApi
@Test
fun searchPokemonInFailingRepository_GetResultIsFailure() = runTest {
Expand Down
6 changes: 3 additions & 3 deletions buildSrc/src/main/java/BuildConfigVersions.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
object BuildConfigVersions {
const val compileSdk = 31
const val compileSdk = 32
const val minSdk = 23
const val targetSdk = 31
const val targetSdk = 32
const val versionCode = 1
const val versionName = "0.0.9"
}
}