diff --git a/app/build.gradle b/app/build.gradle
index 9ea2b8c..5e58fcd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 92f5a5e..f90a9ed 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -25,6 +25,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/test/showcase/ui/compose/ComposeActivity.kt b/app/src/main/java/org/fnives/test/showcase/ui/compose/ComposeActivity.kt
new file mode 100644
index 0000000..bcecc68
--- /dev/null
+++ b/app/src/main/java/org/fnives/test/showcase/ui/compose/ComposeActivity.kt
@@ -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()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/AppNavigation.kt b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/AppNavigation.kt
new file mode 100644
index 0000000..4ab1c20
--- /dev/null
+++ b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/AppNavigation.kt
@@ -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()
+ 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") }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/auth/AuthScreen.kt b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/auth/AuthScreen.kt
new file mode 100644
index 0000000..4b4f740
--- /dev/null
+++ b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/auth/AuthScreen.kt
@@ -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
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/auth/AuthScreenState.kt b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/auth/AuthScreenState.kt
new file mode 100644
index 0000000..a7fa90b
--- /dev/null
+++ b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/auth/AuthScreenState.kt
@@ -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?>(null)
+ private set
+ var navigateToHome by mutableStateOf?>(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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/home/HomeScreen.kt b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/home/HomeScreen.kt
new file mode 100644
index 0000000..b0cd4fa
--- /dev/null
+++ b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/home/HomeScreen.kt
@@ -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
+ )
+}
diff --git a/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/home/HomeScreenState.kt b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/home/HomeScreenState.kt
new file mode 100644
index 0000000..fe59ac1
--- /dev/null
+++ b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/home/HomeScreenState.kt
@@ -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
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/splash/SplashScreen.kt b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/splash/SplashScreen.kt
new file mode 100644
index 0000000..a557a66
--- /dev/null
+++ b/app/src/main/java/org/fnives/test/showcase/ui/compose/screen/splash/SplashScreen.kt
@@ -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)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fnives/test/showcase/ui/shared/Event.kt b/app/src/main/java/org/fnives/test/showcase/ui/shared/Event.kt
index 30a323c..dde6082 100644
--- a/app/src/main/java/org/fnives/test/showcase/ui/shared/Event.kt
+++ b/app/src/main/java/org/fnives/test/showcase/ui/shared/Event.kt
@@ -1,7 +1,6 @@
package org.fnives.test.showcase.ui.shared
-@Suppress("DataClassContainsFunctions")
-data class Event(private val data: T) {
+class Event(private val data: T) {
private var consumed: Boolean = false
diff --git a/gradlescripts/versions.gradle b/gradlescripts/versions.gradle
index d9fc0f0..e1d8d3e 100644
--- a/gradlescripts/versions.gradle
+++ b/gradlescripts/versions.gradle
@@ -7,6 +7,10 @@ project.ext {
androidx_swiperefreshlayout_version = "1.1.0"
androidx_room_version = "2.4.1"
activity_ktx_version = "1.4.0"
+ androidx_navigation = "2.4.0"
+
+ androidx_compose = "1.1.0-rc03"
+ google_accompanist = "0.20.3"
coroutines_version = "1.6.0"
turbine_version = "0.7.0"