Add compose UI
This commit is contained in:
parent
8866ac8477
commit
a9dc65d0b6
11 changed files with 377 additions and 2 deletions
|
|
@ -34,6 +34,10 @@ android {
|
|||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = project.androidx_compose
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
@ -73,7 +77,18 @@ dependencies {
|
|||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$androidx_livedata_version"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:$androidx_swiperefreshlayout_version"
|
||||
|
||||
implementation "androidx.activity:activity-compose:$activity_ktx_version"
|
||||
implementation "androidx.navigation:navigation-compose:$androidx_navigation"
|
||||
|
||||
implementation "androidx.compose.ui:ui:$androidx_compose"
|
||||
implementation "androidx.compose.ui:ui-tooling:$androidx_compose"
|
||||
implementation "androidx.compose.foundation:foundation:$androidx_compose"
|
||||
implementation "androidx.compose.material:material:$androidx_compose"
|
||||
implementation "com.google.accompanist:accompanist-insets:$google_accompanist"
|
||||
implementation "com.google.accompanist:accompanist-swiperefresh:$google_accompanist"
|
||||
|
||||
implementation "io.insert-koin:koin-android:$koin_version"
|
||||
implementation "io.insert-koin:koin-androidx-compose:$koin_version"
|
||||
|
||||
implementation "androidx.room:room-runtime:$androidx_room_version"
|
||||
kapt "androidx.room:room-compiler:$androidx_room_version"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,16 @@
|
|||
</activity>
|
||||
<activity android:name=".ui.home.MainActivity" />
|
||||
<activity android:name=".ui.auth.AuthActivity" />
|
||||
<activity
|
||||
android:name=".ui.compose.ComposeActivity"
|
||||
android:configChanges="colorMode|density|fontScale|fontWeightAdjustment|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package org.fnives.test.showcase.ui.compose
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import com.google.accompanist.insets.ProvideWindowInsets
|
||||
import org.fnives.test.showcase.ui.compose.screen.AppNavigation
|
||||
|
||||
class ComposeActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
ProvideWindowInsets {
|
||||
MaterialTheme {
|
||||
AppNavigation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package org.fnives.test.showcase.ui.compose.screen
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import kotlinx.coroutines.delay
|
||||
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.splash.SplashScreen
|
||||
import org.koin.androidx.compose.get
|
||||
|
||||
@Composable
|
||||
fun AppNavigation() {
|
||||
val navController = rememberNavController()
|
||||
|
||||
val isUserLogeInUseCase = get<IsUserLoggedInUseCase>()
|
||||
LaunchedEffect(isUserLogeInUseCase) {
|
||||
delay(500)
|
||||
navController.navigate(if (isUserLogeInUseCase.invoke()) "Home" else "Auth")
|
||||
}
|
||||
|
||||
NavHost(navController, startDestination = "Splash", modifier = Modifier.background(MaterialTheme.colors.surface)) {
|
||||
composable("Splash") { SplashScreen() }
|
||||
composable("Auth") { AuthScreen() }
|
||||
composable("Home") { Text("Home") }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package org.fnives.test.showcase.ui.compose.screen.auth
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.insets.systemBarsPadding
|
||||
import org.fnives.test.showcase.R
|
||||
|
||||
@Composable
|
||||
fun AuthScreen(
|
||||
authScreenState: AuthScreenState = rememberAuthScreen()
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding()
|
||||
) {
|
||||
Title()
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
TextField(
|
||||
value = authScreenState.username,
|
||||
onValueChange = { authScreenState.onUsernameChanged(it) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
TextField(
|
||||
value = authScreenState.password,
|
||||
onValueChange = { authScreenState.onPasswordChanged(it) },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Snackbar(authScreenState)
|
||||
LoginButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(16.dp),
|
||||
onClick = { authScreenState.onLogin() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Snackbar(authScreenState: AuthScreenState) {
|
||||
val snackbarState = remember { SnackbarHostState() }
|
||||
val errorType = authScreenState.error?.consume()
|
||||
LaunchedEffect(errorType) {
|
||||
if (errorType != null) {
|
||||
snackbarState.showSnackbar(errorType.name)
|
||||
}
|
||||
}
|
||||
SnackbarHost(hostState = snackbarState) {
|
||||
val stringId = errorType?.stringResId()
|
||||
if (stringId != null) {
|
||||
Snackbar(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
Text(text = stringResource(stringId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoginButton(modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||
Box(modifier) {
|
||||
Button(onClick = onClick, Modifier.fillMaxWidth()) {
|
||||
Text(text = "Login")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Title() {
|
||||
Text(
|
||||
stringResource(id = R.string.login_title),
|
||||
modifier = Modifier.padding(16.dp),
|
||||
style = MaterialTheme.typography.h4
|
||||
)
|
||||
}
|
||||
|
||||
private fun AuthScreenState.ErrorType.stringResId() = when (this) {
|
||||
AuthScreenState.ErrorType.INVALID_CREDENTIALS -> R.string.credentials_invalid
|
||||
AuthScreenState.ErrorType.GENERAL_NETWORK_ERROR -> R.string.something_went_wrong
|
||||
AuthScreenState.ErrorType.UNSUPPORTED_USERNAME -> R.string.username_is_invalid
|
||||
AuthScreenState.ErrorType.UNSUPPORTED_PASSWORD -> R.string.password_is_invalid
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package org.fnives.test.showcase.ui.compose.screen.auth
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.fnives.test.showcase.core.login.LoginUseCase
|
||||
import org.fnives.test.showcase.model.auth.LoginCredentials
|
||||
import org.fnives.test.showcase.model.auth.LoginStatus
|
||||
import org.fnives.test.showcase.model.shared.Answer
|
||||
import org.fnives.test.showcase.ui.shared.Event
|
||||
import org.koin.androidx.compose.get
|
||||
|
||||
@Composable
|
||||
fun rememberAuthScreen(
|
||||
stateScope: CoroutineScope = rememberCoroutineScope(),
|
||||
loginUseCase: LoginUseCase = get(),
|
||||
): AuthScreenState {
|
||||
return remember { AuthScreenState(stateScope, loginUseCase) }
|
||||
}
|
||||
|
||||
class AuthScreenState(
|
||||
private val stateScope: CoroutineScope,
|
||||
private val loginUseCase: LoginUseCase,
|
||||
) {
|
||||
|
||||
var username by mutableStateOf("")
|
||||
private set
|
||||
var password by mutableStateOf("")
|
||||
private set
|
||||
var loading by mutableStateOf(false)
|
||||
private set
|
||||
var error by mutableStateOf<Event<ErrorType>?>(null)
|
||||
private set
|
||||
var navigateToHome by mutableStateOf<Event<Unit>?>(null)
|
||||
private set
|
||||
|
||||
fun onUsernameChanged(username: String) {
|
||||
this.username = username
|
||||
}
|
||||
|
||||
fun onPasswordChanged(password: String) {
|
||||
this.password = password
|
||||
}
|
||||
|
||||
fun onLogin() {
|
||||
if (loading) {
|
||||
return
|
||||
}
|
||||
loading = true
|
||||
stateScope.launch {
|
||||
val credentials = LoginCredentials(
|
||||
username = username,
|
||||
password = password
|
||||
)
|
||||
when (val response = loginUseCase.invoke(credentials)) {
|
||||
is Answer.Error -> error = Event(ErrorType.GENERAL_NETWORK_ERROR)
|
||||
is Answer.Success -> processLoginStatus(response.data)
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun processLoginStatus(loginStatus: LoginStatus) {
|
||||
when (loginStatus) {
|
||||
LoginStatus.SUCCESS -> navigateToHome = Event(Unit)
|
||||
LoginStatus.INVALID_CREDENTIALS -> error = Event(ErrorType.INVALID_CREDENTIALS)
|
||||
LoginStatus.INVALID_USERNAME -> error = Event(ErrorType.UNSUPPORTED_USERNAME).also { println("asdasdasd: ${it.hashCode()}")
|
||||
}
|
||||
LoginStatus.INVALID_PASSWORD -> error = Event(ErrorType.UNSUPPORTED_PASSWORD)
|
||||
}
|
||||
println("asdasdasd: ${error.hashCode()}")
|
||||
}
|
||||
|
||||
enum class ErrorType {
|
||||
INVALID_CREDENTIALS,
|
||||
GENERAL_NETWORK_ERROR,
|
||||
UNSUPPORTED_USERNAME,
|
||||
UNSUPPORTED_PASSWORD
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package org.fnives.test.showcase.ui.compose.screen.home
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import org.fnives.test.showcase.R
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
homeScreenState = rememberHomeScreenState()
|
||||
) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
Title()
|
||||
SwipeRefresh(state = rememberSwipeRefreshState(isRefreshing = false), onRefresh = { }) {
|
||||
LazyColumn {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun Title() {
|
||||
Text(
|
||||
stringResource(id = R.string.login_title),
|
||||
modifier = Modifier.padding(16.dp),
|
||||
style = MaterialTheme.typography.h4
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package org.fnives.test.showcase.ui.compose.screen.home
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import org.fnives.test.showcase.core.content.AddContentToFavouriteUseCase
|
||||
import org.fnives.test.showcase.core.content.FetchContentUseCase
|
||||
import org.fnives.test.showcase.core.content.GetAllContentUseCase
|
||||
import org.fnives.test.showcase.core.content.RemoveContentFromFavouritesUseCase
|
||||
import org.fnives.test.showcase.core.login.LogoutUseCase
|
||||
import org.koin.androidx.compose.get
|
||||
|
||||
@Composable
|
||||
fun rememberHomeScreenState(
|
||||
getAllContentUseCase: GetAllContentUseCase = get(),
|
||||
logoutUseCase: LogoutUseCase = get(),
|
||||
fetchContentUseCase: FetchContentUseCase = get(),
|
||||
addContentToFavouriteUseCase: AddContentToFavouriteUseCase = get(),
|
||||
removeContentFromFavouritesUseCase: RemoveContentFromFavouritesUseCase = get(),
|
||||
): HomeScreenState {
|
||||
return remember {
|
||||
HomeScreenState(
|
||||
getAllContentUseCase,
|
||||
logoutUseCase,
|
||||
fetchContentUseCase,
|
||||
addContentToFavouriteUseCase,
|
||||
removeContentFromFavouritesUseCase
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class HomeScreenState(
|
||||
private val getAllContentUseCase: GetAllContentUseCase,
|
||||
private val logoutUseCase: LogoutUseCase,
|
||||
private val fetchContentUseCase: FetchContentUseCase,
|
||||
private val addContentToFavouriteUseCase: AddContentToFavouriteUseCase,
|
||||
private val removeContentFromFavouritesUseCase: RemoveContentFromFavouritesUseCase
|
||||
) {
|
||||
|
||||
var loading by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package org.fnives.test.showcase.ui.compose.screen.splash
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import org.fnives.test.showcase.R
|
||||
|
||||
|
||||
@Composable
|
||||
fun SplashScreen() {
|
||||
Box(Modifier.fillMaxSize().background(colorResource(R.color.purple_700)), contentAlignment = Alignment.Center) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_launcher_foreground),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(120.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package org.fnives.test.showcase.ui.shared
|
||||
|
||||
@Suppress("DataClassContainsFunctions")
|
||||
data class Event<T : Any>(private val data: T) {
|
||||
class Event<T : Any>(private val data: T) {
|
||||
|
||||
private var consumed: Boolean = false
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue