Add home screen

This commit is contained in:
Alexandru Gabor 2022-02-28 16:57:29 +02:00 committed by Alex Gabor
parent a9dc65d0b6
commit b6e4d282b7
7 changed files with 173 additions and 21 deletions

View file

@ -95,6 +95,7 @@ dependencies {
implementation "androidx.room:room-ktx:$androidx_room_version" implementation "androidx.room:room-ktx:$androidx_room_version"
implementation "io.coil-kt:coil:$coil_version" implementation "io.coil-kt:coil:$coil_version"
implementation "io.coil-kt:coil-compose:$coil_version"
implementation project(":core") implementation project(":core")

View file

@ -2,7 +2,6 @@ package org.fnives.test.showcase.ui.compose.screen
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -12,6 +11,9 @@ import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase
import org.fnives.test.showcase.ui.compose.screen.auth.AuthScreen import org.fnives.test.showcase.ui.compose.screen.auth.AuthScreen
import org.fnives.test.showcase.ui.compose.screen.auth.rememberAuthScreenState
import org.fnives.test.showcase.ui.compose.screen.home.HomeScreen
import org.fnives.test.showcase.ui.compose.screen.home.rememberHomeScreenState
import org.fnives.test.showcase.ui.compose.screen.splash.SplashScreen import org.fnives.test.showcase.ui.compose.screen.splash.SplashScreen
import org.koin.androidx.compose.get import org.koin.androidx.compose.get
@ -25,9 +27,23 @@ fun AppNavigation() {
navController.navigate(if (isUserLogeInUseCase.invoke()) "Home" else "Auth") navController.navigate(if (isUserLogeInUseCase.invoke()) "Home" else "Auth")
} }
NavHost(navController, startDestination = "Splash", modifier = Modifier.background(MaterialTheme.colors.surface)) { NavHost(
navController,
startDestination = "Splash",
modifier = Modifier.background(MaterialTheme.colors.surface)
) {
composable("Splash") { SplashScreen() } composable("Splash") { SplashScreen() }
composable("Auth") { AuthScreen() } composable("Auth") {
composable("Home") { Text("Home") } val authState = rememberAuthScreenState()
AuthScreen(authState)
if (authState.navigateToHome?.consume() != null) {
navController.navigate("Home")
}
}
composable("Home") {
HomeScreen(rememberHomeScreenState {
navController.navigate("Auth")
})
}
} }
} }

View file

@ -15,7 +15,7 @@ import org.fnives.test.showcase.R
@Composable @Composable
fun AuthScreen( fun AuthScreen(
authScreenState: AuthScreenState = rememberAuthScreen() authScreenState: AuthScreenState = rememberAuthScreenState()
) { ) {
Column( Column(
Modifier Modifier

View file

@ -11,7 +11,7 @@ import org.fnives.test.showcase.ui.shared.Event
import org.koin.androidx.compose.get import org.koin.androidx.compose.get
@Composable @Composable
fun rememberAuthScreen( fun rememberAuthScreenState(
stateScope: CoroutineScope = rememberCoroutineScope(), stateScope: CoroutineScope = rememberCoroutineScope(),
loginUseCase: LoginUseCase = get(), loginUseCase: LoginUseCase = get(),
): AuthScreenState { ): AuthScreenState {
@ -64,8 +64,7 @@ class AuthScreenState(
when (loginStatus) { when (loginStatus) {
LoginStatus.SUCCESS -> navigateToHome = Event(Unit) LoginStatus.SUCCESS -> navigateToHome = Event(Unit)
LoginStatus.INVALID_CREDENTIALS -> error = Event(ErrorType.INVALID_CREDENTIALS) LoginStatus.INVALID_CREDENTIALS -> error = Event(ErrorType.INVALID_CREDENTIALS)
LoginStatus.INVALID_USERNAME -> error = Event(ErrorType.UNSUPPORTED_USERNAME).also { println("asdasdasd: ${it.hashCode()}") LoginStatus.INVALID_USERNAME -> error = Event(ErrorType.UNSUPPORTED_USERNAME)
}
LoginStatus.INVALID_PASSWORD -> error = Event(ErrorType.UNSUPPORTED_PASSWORD) LoginStatus.INVALID_PASSWORD -> error = Event(ErrorType.UNSUPPORTED_PASSWORD)
} }
println("asdasdasd: ${error.hashCode()}") println("asdasdasd: ${error.hashCode()}")

View file

@ -1,39 +1,100 @@
package org.fnives.test.showcase.ui.compose.screen.home package org.fnives.test.showcase.ui.compose.screen.home
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.rememberImagePainter
import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import org.fnives.test.showcase.R import org.fnives.test.showcase.R
import org.fnives.test.showcase.model.content.FavouriteContent
@Composable @Composable
fun HomeScreen( fun HomeScreen(
homeScreenState = rememberHomeScreenState() homeScreenState: HomeScreenState = rememberHomeScreenState()
) { ) {
Column(Modifier.fillMaxSize()) { Column(Modifier.fillMaxSize()) {
Title() Row(verticalAlignment = Alignment.CenterVertically) {
SwipeRefresh(state = rememberSwipeRefreshState(isRefreshing = false), onRefresh = { }) { Title(Modifier.weight(1f))
Image(
painter = painterResource(id = R.drawable.logout_24),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colors.primary),
modifier = Modifier
.padding(16.dp)
.clickable { homeScreenState.onLogout() }
)
}
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing = homeScreenState.loading),
onRefresh = {
homeScreenState.onRefresh()
}) {
LazyColumn { LazyColumn {
items(homeScreenState.content) { item ->
Item(
Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
favouriteContent = item,
onFavouriteToggle = { homeScreenState.onFavouriteToggleClicked(item.content.id) }
)
}
} }
} }
} }
} }
@Composable
private fun Item(
modifier: Modifier = Modifier,
favouriteContent: FavouriteContent,
onFavouriteToggle: () -> Unit,
) {
Row(modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Image(
painter = rememberImagePainter(favouriteContent.content.imageUrl.url),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.height(120.dp)
.aspectRatio(1f)
.clip(RoundedCornerShape(12.dp))
)
Column(
Modifier
.weight(1f)
.padding(horizontal = 16.dp)
) {
Text(text = favouriteContent.content.title)
Text(text = favouriteContent.content.description)
}
Image(
painter = painterResource(id = if (favouriteContent.isFavourite) R.drawable.favorite_24 else R.drawable.favorite_border_24),
contentDescription = null,
Modifier.clickable { onFavouriteToggle() }
)
}
}
@Composable @Composable
private fun Title() { private fun Title(modifier: Modifier = Modifier) {
Text( Text(
stringResource(id = R.string.login_title), stringResource(id = R.string.login_title),
modifier = Modifier.padding(16.dp), modifier = modifier.padding(16.dp),
style = MaterialTheme.typography.h4 style = MaterialTheme.typography.h4
) )
} }

View file

@ -1,41 +1,116 @@
package org.fnives.test.showcase.ui.compose.screen.home package org.fnives.test.showcase.ui.compose.screen.home
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase
import org.fnives.test.showcase.core.content.FetchContentUseCase import org.fnives.test.showcase.core.content.FetchContentUseCase
import org.fnives.test.showcase.core.content.GetAllContentUseCase import org.fnives.test.showcase.core.content.GetAllContentUseCase
import org.fnives.test.showcase.core.content.RemoveContentFromFavouritesUseCase import org.fnives.test.showcase.core.content.RemoveContentFromFavouritesUseCase
import org.fnives.test.showcase.core.login.LogoutUseCase import org.fnives.test.showcase.core.login.LogoutUseCase
import org.fnives.test.showcase.model.content.ContentId
import org.fnives.test.showcase.model.content.FavouriteContent
import org.fnives.test.showcase.model.shared.Resource
import org.koin.androidx.compose.get import org.koin.androidx.compose.get
@Composable @Composable
fun rememberHomeScreenState( fun rememberHomeScreenState(
stateScope: CoroutineScope = rememberCoroutineScope(),
getAllContentUseCase: GetAllContentUseCase = get(), getAllContentUseCase: GetAllContentUseCase = get(),
logoutUseCase: LogoutUseCase = get(), logoutUseCase: LogoutUseCase = get(),
fetchContentUseCase: FetchContentUseCase = get(), fetchContentUseCase: FetchContentUseCase = get(),
addContentToFavouriteUseCase: AddContentToFavouriteUseCase = get(), addContentToFavouriteUseCase: AddContentToFavouriteUseCase = get(),
removeContentFromFavouritesUseCase: RemoveContentFromFavouritesUseCase = get(), removeContentFromFavouritesUseCase: RemoveContentFromFavouritesUseCase = get(),
onLogout: () -> Unit = {},
): HomeScreenState { ): HomeScreenState {
return remember { return remember {
HomeScreenState( HomeScreenState(
stateScope,
getAllContentUseCase, getAllContentUseCase,
logoutUseCase, logoutUseCase,
fetchContentUseCase, fetchContentUseCase,
addContentToFavouriteUseCase, addContentToFavouriteUseCase,
removeContentFromFavouritesUseCase removeContentFromFavouritesUseCase,
onLogout,
) )
} }
} }
class HomeScreenState( class HomeScreenState(
private val stateScope: CoroutineScope,
private val getAllContentUseCase: GetAllContentUseCase, private val getAllContentUseCase: GetAllContentUseCase,
private val logoutUseCase: LogoutUseCase, private val logoutUseCase: LogoutUseCase,
private val fetchContentUseCase: FetchContentUseCase, private val fetchContentUseCase: FetchContentUseCase,
private val addContentToFavouriteUseCase: AddContentToFavouriteUseCase, private val addContentToFavouriteUseCase: AddContentToFavouriteUseCase,
private val removeContentFromFavouritesUseCase: RemoveContentFromFavouritesUseCase private val removeContentFromFavouritesUseCase: RemoveContentFromFavouritesUseCase,
private val logoutEvent: () -> Unit,
) { ) {
var loading by mutableStateOf(false) var loading by mutableStateOf(false)
private set private set
var isError by mutableStateOf(false)
private set
var content by mutableStateOf<List<FavouriteContent>>(emptyList())
private set
init {
stateScope.launch {
fetch().collect {
content = it
}
}
}
private fun fetch() = getAllContentUseCase.get()
.mapNotNull {
when (it) {
is Resource.Error -> {
isError = true
loading = false
return@mapNotNull emptyList<FavouriteContent>()
}
is Resource.Loading -> {
isError = false
loading = true
return@mapNotNull null
}
is Resource.Success -> {
isError = false
loading = false
return@mapNotNull it.data
}
}
}
fun onLogout() {
stateScope.launch {
logoutUseCase.invoke()
logoutEvent()
}
}
fun onRefresh() {
if (loading) return
loading = true
stateScope.launch {
fetchContentUseCase.invoke()
}
}
fun onFavouriteToggleClicked(contentId: ContentId) {
stateScope.launch {
val item = content.firstOrNull { it.content.id == contentId } ?: return@launch
if (item.isFavourite) {
removeContentFromFavouritesUseCase.invoke(contentId)
} else {
addContentToFavouriteUseCase.invoke(contentId)
}
}
}
} }

View file

@ -15,7 +15,7 @@ project.ext {
coroutines_version = "1.6.0" coroutines_version = "1.6.0"
turbine_version = "0.7.0" turbine_version = "0.7.0"
koin_version = "3.1.2" koin_version = "3.1.2"
coil_version = "1.1.1" coil_version = "1.4.0"
retrofit_version = "2.9.0" retrofit_version = "2.9.0"
okhttp_version = "4.9.1" okhttp_version = "4.9.1"
moshi_version = "1.13.0" moshi_version = "1.13.0"