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

Develop #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
23 changes: 19 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,33 @@ android {
jvmTarget = '11'
}
buildFeatures {
viewBinding true
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.5'
kotlinCompilerVersion '1.5.21'
}
}

dependencies {
// android
implementation 'androidx.activity:activity-ktx:1.8.1'
implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.activity:activity-ktx:1.8.2'
implementation 'com.google.android.material:material:1.11.0'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.compose.ui:ui:1.5.4"
implementation "androidx.compose.material:material:1.5.4"
implementation "androidx.compose.ui:ui-tooling-preview:1.5.4"
implementation 'androidx.activity:activity-compose:1.8.2'
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
implementation "androidx.compose.runtime:runtime-livedata:1.5.4"
implementation "io.coil-kt:coil-compose:2.5.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01"


// images
implementation 'io.coil-kt:coil:2.4.0'
implementation 'io.coil-kt:coil:2.5.0'

// network
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/goodrequest/hiring/api.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ private suspend fun <T> OkHttpClient.httpGet(url: HttpUrl, parse: (String) -> T)
val good: Request = Request.Builder().url(url).get().build()
val result = newCall(good).await()
val parsed = parse(result.body!!.string())
Result.success(parsed)
Result.Success(parsed)
} catch (e: Exception) {
Result.failure(e)
Result.Failure(e)
}

private fun parsePokemons(json: String): List<Pokemon> =
Expand Down
204 changes: 184 additions & 20 deletions app/src/main/java/com/goodrequest/hiring/ui/activity.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,54 @@
package com.goodrequest.hiring.ui

import android.os.Bundle
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.activity.ComponentActivity
import androidx.lifecycle.*
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.SnackbarDuration
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.AbstractSavedStateViewModelFactory
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelLazy
import coil.compose.AsyncImage
import com.goodrequest.hiring.PokemonApi
import com.goodrequest.hiring.databinding.ActivityBinding
import com.goodrequest.hiring.R
import kotlinx.coroutines.launch

class PokemonActivity: ComponentActivity() {

Expand All @@ -16,27 +58,149 @@ class PokemonActivity: ComponentActivity() {
val vm by viewModel { PokemonViewModel(it, null, PokemonApi) }
vm.load()

ActivityBinding.inflate(layoutInflater).run {
setContentView(root)
refresh.setOnRefreshListener { vm.load() }
retry.setOnClickListener { vm.load() }

vm.pokemons.observe(this@PokemonActivity) { result: Result<List<Pokemon>>? ->
result?.fold(
onSuccess = { pokemons ->
loading.visibility = GONE
val adapter = PokemonAdapter()
items.adapter = adapter
adapter.show(pokemons)
},
onFailure = {
loading.visibility = GONE
failure.visibility = VISIBLE
setContent {
MaterialTheme {
Column(modifier = Modifier.fillMaxSize()) {
Box(modifier = Modifier.fillMaxSize()) {
val pokemons by vm.pokemons.observeAsState(initial = Result.Failure(Exception()))
when (val result = pokemons) {
is Result.Success<List<Pokemon>> -> PokemonList(pokemons = result.data)
is Result.Failure -> FailureView(onRetry = { vm.load(forceRefresh = true) })
else -> CircularProgressIndicator(Modifier.align(Alignment.Center))
}
}
}
}
}
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun PokemonList(pokemons: List<Pokemon>) {
//inject viewModel here
val vm by viewModel { PokemonViewModel(it, null, PokemonApi) }
val errorMessage = vm.state.value.errorMessage
val pullRefreshState = rememberPullRefreshState(
refreshing = vm.state.value.isLoading,
onRefresh = { vm.load(forceRefresh = true) }
)
val lazyColumnListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
val shouldStartPaginate = remember {
derivedStateOf {
vm.canPaginate && (lazyColumnListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -9) >= (lazyColumnListState.layoutInfo.totalItemsCount - 1)
}
}
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
// show pokemons count
topBar = { TopAppBar(title = { Text("Gotta Catch 'Em All! Loaded: ${(pokemons.size)}") }) },
snackbarHost = { SnackbarHost(
hostState = snackbarHostState,
) },
) { padding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.pullRefresh(pullRefreshState)
) {
LazyColumn(state = lazyColumnListState) {
items(pokemons.size) { index ->
val pokemon = pokemons[index]
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Row {
if (pokemon.detail == null) {
// Display placeholder image for Pokemon with failed details
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "Placeholder image for ${pokemon.name}",
modifier = Modifier.fillMaxHeight()
)
} else {
pokemon.detail.let { detail ->
AsyncImage(
model = detail.image,
contentDescription = null,
placeholder = painterResource(id = R.drawable.ic_launcher_foreground),
modifier = Modifier.fillMaxHeight()
)
}
}
Column {
Text(text = pokemon.name, modifier = Modifier.padding(16.dp))
pokemon.detail?.let { detail ->
Text(
text = "Move: ${detail.move}",
modifier = Modifier.padding(16.dp)
)
Text(
text = "Weight: ${detail.weight}",
modifier = Modifier.padding(16.dp)
)
}
}
}
}
if (index == pokemons.size - 1) {
LaunchedEffect(remember {
derivedStateOf { lazyColumnListState.firstVisibleItemIndex }
}) {
vm.load()
}
}
}
}
PullRefreshIndicator(
refreshing = vm.state.value.isLoading,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
backgroundColor = if (vm.state.value.isLoading) Color.Red else Color.Green,
)
LaunchedEffect(vm.state.value.errorMessage) {
if (errorMessage != null) {
snackbarHostState.showSnackbar(
message = errorMessage,
actionLabel = "Zavřít",
duration = SnackbarDuration.Short,
)
}
}
}
}
}
}

@Composable
fun FailureView(onRetry: () -> Unit) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Something went wrong!", fontSize = 20.sp, modifier = Modifier.padding(bottom = 16.dp))
Button(onClick = onRetry) {
Text(text = "Try again")
}
}

}

sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Failure(val exception: Exception) : Result<Nothing>()
}

enum class ListState {
IDLE,
LOADING,
PAGINATING,
ERROR,
PAGINATION_EXHAUST,
}

/**
Expand Down
40 changes: 0 additions & 40 deletions app/src/main/java/com/goodrequest/hiring/ui/adapter.kt

This file was deleted.

Loading