diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/utils/ResultState.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/utils/ResultState.kt new file mode 100644 index 0000000..fd9d01b --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/utils/ResultState.kt @@ -0,0 +1,8 @@ +package com.syaroful.agrilinkvocpro.core.utils + +sealed class ResultState { + data object Idle : ResultState() + data object Loading : ResultState() + data class Success(val data: T?) : ResultState() + data class Error(val message: String) : ResultState() +} \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/UserPreferences.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/UserPreferences.kt new file mode 100644 index 0000000..4ec188b --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/UserPreferences.kt @@ -0,0 +1,72 @@ +package com.syaroful.agrilinkvocpro.data + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.auth0.jwt.JWT +import com.auth0.jwt.exceptions.JWTDecodeException +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import java.util.Date + +class UserPreferences(private val context: Context) { + companion object { + private val Context.dataStore: DataStore by preferencesDataStore(name = "user_prefs") + private val TOKEN_KEY = stringPreferencesKey("auth_token") + private val JWT_TOKEN = stringPreferencesKey("jwt_token") + } + + // Flow untuk token biasa + val tokenFlow: Flow = context.dataStore.data.map { preferences -> + preferences[TOKEN_KEY] + } + + suspend fun saveToken(token: String) { + context.dataStore.edit { preferences -> + preferences[TOKEN_KEY] = token + } + } + + suspend fun clearToken() { + context.dataStore.edit { preferences -> + preferences.remove(TOKEN_KEY) + } + } + + // Flow untuk JWT token + val jwtTokenFlow: Flow = context.dataStore.data.map { preferences -> + preferences[JWT_TOKEN] + } + + suspend fun saveJwtToken(token: String) { + context.dataStore.edit { preferences -> + preferences[JWT_TOKEN] = token + } + } + + suspend fun clearJwtToken() { + context.dataStore.edit { preferences -> + preferences.remove(JWT_TOKEN) + } + } + + // Fungsi untuk cek apakah user sudah login berdasarkan JWT token valid dan belum expired + fun isUserLoggedIn(): Flow = jwtTokenFlow.map { token -> + !token.isNullOrEmpty() && !isTokenExpired(token) + } + + // Fungsi private untuk cek expired JWT token menggunakan library Auth0 java-jwt + private fun isTokenExpired(token: String): Boolean { + return try { + val jwt = JWT.decode(token) + val expiresAt: Date? = jwt.expiresAt + expiresAt?.let { Date().after(it) } != false + } catch (e: JWTDecodeException) { + true + } + return true + } +} \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/ErrorLoginResponse.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/ErrorLoginResponse.kt new file mode 100644 index 0000000..4d41ed8 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/ErrorLoginResponse.kt @@ -0,0 +1,7 @@ +package com.syaroful.agrilinkvocpro.data.model + +data class ErrorLoginResponse( + val errors: Int, + val message: String, + val statusCode: Int +) \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/LoginResponse.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/LoginResponse.kt new file mode 100644 index 0000000..d5acf2c --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/LoginResponse.kt @@ -0,0 +1,7 @@ +package com.syaroful.agrilinkvocpro.data.model + +data class LoginResponse( + val data: Token?, + val message: String?, + val statusCode: Int? +) \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/Token.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/Token.kt new file mode 100644 index 0000000..d156b76 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/Token.kt @@ -0,0 +1,6 @@ +package com.syaroful.agrilinkvocpro.data.model + +data class Token( + val jwtToken: String?, + val token: String? +) \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/UserProfile.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/UserProfile.kt new file mode 100644 index 0000000..561b87d --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/UserProfile.kt @@ -0,0 +1,8 @@ +package com.syaroful.agrilinkvocpro.data.model + +data class UserProfile( + val name: String?, + val username: String?, + val email: String?, + val phone: String? = null +) \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/login/LoginScreen.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/login/LoginScreen.kt index e3ac5c7..3b1317b 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/login/LoginScreen.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/login/LoginScreen.kt @@ -2,9 +2,10 @@ package com.syaroful.agrilinkvocpro.ui.screen.login import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -13,9 +14,17 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign @@ -26,19 +35,32 @@ import com.syaroful.agrilinkvocpro.core.components.AppButton import com.syaroful.agrilinkvocpro.core.components.AppPasswordField import com.syaroful.agrilinkvocpro.core.components.AppTextField import com.syaroful.agrilinkvocpro.core.components.textTheme +import com.syaroful.agrilinkvocpro.core.utils.ResultState import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.ui.theme.DarkGrey import com.syaroful.agrilinkvocpro.ui.theme.MainGreen +import org.koin.androidx.compose.koinViewModel @Composable -fun LoginScreen() { - Scaffold(modifier = Modifier.fillMaxSize()) { paddingValues -> +fun LoginScreen( + loginViewModel: LoginViewModel, + onLoginSuccess: () -> Unit, + onNavigateToRegister: () -> Unit, +) { + val loginState by loginViewModel.loginState.collectAsState() + + val focusManager = LocalFocusManager.current + var username by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + + Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Column( modifier = Modifier .fillMaxWidth() - .padding(paddingValues) + .padding(innerPadding), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { Image( painter = painterResource(id = R.drawable.greenhouse_banner), @@ -48,46 +70,72 @@ fun LoginScreen() { .align(Alignment.CenterHorizontally), contentScale = ContentScale.Crop ) - Spacer(modifier = Modifier.height(16.dp)) Text( modifier = Modifier.fillMaxWidth(), text = "Login", style = textTheme.titleMedium, textAlign = TextAlign.Center ) - Spacer(modifier = Modifier.height(16.dp)) Text( modifier = Modifier.fillMaxWidth(), text = "Halo! yuk masuk ke dalam akunmu", style = textTheme.titleSmall.copy(color = DarkGrey), textAlign = TextAlign.Center ) - Spacer(modifier = Modifier.height(16.dp)) AppTextField( - hint = "Username", keyboardType = KeyboardType.Email, leadingIcon = painterResource( + hint = "Username", + keyboardType = KeyboardType.Email, + leadingIcon = painterResource( R.drawable.icon_email - ) + ), + value = username, + onValueChange = { username = it } ) AppPasswordField( hint = "Password", - keyboardType = KeyboardType.Password + keyboardType = KeyboardType.Password, + value = password, + onValueChange = { password = it } ) AppButton( - label = "Login" - ) { } + label = if (loginState is ResultState.Loading) "Memuat..." else "Login", + isEnable = loginState !is ResultState.Loading, + ) { + focusManager.clearFocus() + loginViewModel.login(username = username, password = password) + + } + // Error Message + if (loginState is ResultState.Error) { + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = (loginState as ResultState.Error).message, + color = Color.Red + ) + } Spacer(modifier = Modifier.height(16.dp)) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { Text( text = "Belum punya akun? ", style = textTheme.titleSmall.copy(color = DarkGrey), textAlign = TextAlign.Center ) Text( + modifier = Modifier.clickable { onNavigateToRegister() }, text = "Daftar disini", style = textTheme.titleSmall.copy(color = MainGreen), textAlign = TextAlign.Center ) } + // Navigate to Home if Success + LaunchedEffect(loginState) { + if (loginState is ResultState.Success<*>) { + onLoginSuccess() + loginViewModel.resetLoginState() + } + } } - } } @@ -96,6 +144,10 @@ fun LoginScreen() { @Composable fun LoginScreenPreview() { AgrilinkVocproTheme { - LoginScreen() + LoginScreen( + loginViewModel = koinViewModel(), + onLoginSuccess = {}, + onNavigateToRegister = {} + ) } } diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/login/LoginViewModel.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/login/LoginViewModel.kt new file mode 100644 index 0000000..2972b72 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/login/LoginViewModel.kt @@ -0,0 +1,78 @@ +package com.syaroful.agrilinkvocpro.ui.screen.login + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.syaroful.agrilinkvocpro.core.utils.ResultState +import com.syaroful.agrilinkvocpro.data.UserPreferences +import com.syaroful.agrilinkvocpro.data.model.LoginResponse +import com.syaroful.agrilinkvocpro.data.repository.AuthRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class LoginViewModel( + private val repository: AuthRepository, + private val userPreferences: UserPreferences +) : ViewModel() { + private val _loginState = MutableStateFlow>(ResultState.Idle) + val loginState: StateFlow> = _loginState + + fun login(username: String, password: String) { + _loginState.value = ResultState.Loading + viewModelScope.launch { + try { + val response = repository.login(username, password) + if (response.isSuccessful) { + val token = response.body()?.data?.token + val jwtToken = response.body()?.data?.jwtToken + if (token != null && jwtToken != null) { + userPreferences.saveToken(token) + userPreferences.saveJwtToken(jwtToken) + _loginState.value = ResultState.Success(response.body()) + + } else { + val errorMessage = response.body()?.message ?: "Login Failed: Token is null" + _loginState.value = ResultState.Error(errorMessage) + } + } else { + val errorBody = response.errorBody()?.string() + val errorMessage = if (errorBody != null) { + // Assuming errorBody is a JSON string with a "message" key + org.json.JSONObject(errorBody) + .optString("message", "Login Failed: ${response.code()}") + } else { + "Login Failed: ${response.code()}" + } + _loginState.value = ResultState.Error(errorMessage) + } + } catch (e: Exception) { + val errorMessage = mapToUserFriendlyError(e) + _loginState.value = ResultState.Error(errorMessage) + } + } + } + + fun resetLoginState() { + _loginState.value = ResultState.Idle + } + + private fun mapToUserFriendlyError(e: Exception): String { + return when (e) { + is java.net.UnknownHostException -> "Tidak dapat terhubung ke server. Periksa koneksi internet Anda." + is java.net.SocketTimeoutException -> "Waktu koneksi habis. Mohon coba lagi." + is java.io.IOException -> "Terjadi kesalahan jaringan. Silakan cek koneksi Anda." + is retrofit2.HttpException -> { + // Kamu bisa cek kode HTTP di sini juga kalau mau + when (e.code()) { + 401 -> "Akses ditolak. Silakan periksa kredensial login Anda." + 403 -> "Anda tidak memiliki izin untuk mengakses ini." + 404 -> "Data tidak ditemukan." + 500 -> "Terjadi kesalahan pada server. Silakan coba lagi nanti." + else -> "Terjadi kesalahan. Kode: ${e.code()}" + } + } + + else -> "Terjadi kesalahan. Silakan coba lagi." + } + } +} diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/profile/ProfileScreen.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/profile/ProfileScreen.kt index 02db04a..85fe82b 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/profile/ProfileScreen.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/profile/ProfileScreen.kt @@ -1,6 +1,5 @@ package com.syaroful.agrilinkvocpro.ui.screen.profile -import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.Image import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -14,29 +13,48 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.syaroful.agrilinkvocpro.R import com.syaroful.agrilinkvocpro.core.components.textTheme -import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.ui.theme.LightGrey import com.syaroful.agrilinkvocpro.ui.theme.MainGreen +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun ProfileScreen() { +fun ProfileScreen( + profileViewModel: ProfileViewModel = koinViewModel(), + onNavigateToLogin: () -> Unit, +) { + val profileState by profileViewModel.profileState.collectAsState() + var showLogoutDialog by remember { mutableStateOf(false) } + + LaunchedEffect(profileState) { + profileViewModel.loadUserProfile() + } + Scaffold( topBar = { TopAppBar( @@ -76,19 +94,19 @@ fun ProfileScreen() { ) { RowItemWidget( label = "Nama", - value = "Muhamad Syaroful Anam" + value = profileState?.name ?: "-" ) RowItemWidget( label = "Username", - value = "syaroful" + value = profileState?.username ?: "-" ) RowItemWidget( label = "Email", - value = "syaroful@gamil.com" + value = profileState?.email ?: "-" ) RowItemWidget( label = "No. Telepon", - value = "08123456789" + value = profileState?.phone ?: "-" ) } Text(text = "Informasi", style = textTheme.titleMedium) @@ -117,10 +135,43 @@ fun ProfileScreen() { RowItemWidgetWithIcon( label = "Log Out", icon = painterResource(id = R.drawable.ic_out) - ) + ) { + showLogoutDialog = true + } } } } + + if (showLogoutDialog) { + AlertDialog( + onDismissRequest = { showLogoutDialog = false }, + title = { Text("Logout") }, + text = { Text("Apakah Anda yakin ingin logout?") }, + confirmButton = { + TextButton( + onClick = { + profileViewModel.logout { + onNavigateToLogin() + } + showLogoutDialog = false + }, + colors = ButtonDefaults.textButtonColors( + contentColor = Color.Red.copy(alpha = 0.5f) + ) + ) { + Text("Logout") + } + }, + dismissButton = { + TextButton( + onClick = { showLogoutDialog = false }, + colors = ButtonDefaults.textButtonColors( + contentColor = Color.Gray + ) + ) { Text("Batal") } + } + ) + } } @Composable @@ -161,13 +212,4 @@ private fun RowItemWidget( Text(text = label) Text(text = value, color = LightGrey) } -} - -@Preview(showBackground = true, name = "Light Mode") -@Preview(showBackground = true, name = "Dark Mode", uiMode = UI_MODE_NIGHT_YES) -@Composable -fun ProfilePreview() { - AgrilinkVocproTheme { - ProfileScreen() - } } \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/profile/ProfileViewModel.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/profile/ProfileViewModel.kt new file mode 100644 index 0000000..3ddb142 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/profile/ProfileViewModel.kt @@ -0,0 +1,63 @@ +package com.syaroful.agrilinkvocpro.ui.screen.profile + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.auth0.jwt.JWT +import com.syaroful.agrilinkvocpro.data.UserPreferences +import com.syaroful.agrilinkvocpro.data.model.UserProfile +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +private const val TAG = "ProfileViewModel" + +class ProfileViewModel( + private val userPreferences: UserPreferences +) : ViewModel() { + private val _profileState = MutableStateFlow(null) + val profileState: StateFlow = _profileState + + fun loadUserProfile() { + viewModelScope.launch { + val token = userPreferences.jwtTokenFlow.first() + + if (!token.isNullOrEmpty()) { + Log.d(TAG, "loadUserProfile: $token") + try { + val jwt = JWT.decode(token) + val userMap = jwt.getClaim("user").asMap() + val username = userMap["username"] as? String + val email = userMap["email"] as? String + val fullname = userMap["fullname"] as? String + + Log.d(TAG, "loadUserProfile: $username") + + val profile = + UserProfile(name = fullname, username = username, email = email) + _profileState.value = profile + } catch (e: Exception) { + Log.e(TAG, "loadUserProfile: ${e.message}") + _profileState.value = null + } + } else { + Log.d(TAG, "loadUserProfile: null") + _profileState.value = null + } + + } + } + + fun logout(onLogoutCompleted: () -> Unit) { + viewModelScope.launch { + withContext(Dispatchers.IO) { + userPreferences.clearToken() + userPreferences.clearJwtToken() + } + onLogoutCompleted() + } + } +} \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/register/RegisterScreen.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/register/RegisterScreen.kt new file mode 100644 index 0000000..2a50b60 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/register/RegisterScreen.kt @@ -0,0 +1,129 @@ +package com.syaroful.agrilinkvocpro.ui.screen.register + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.syaroful.agrilinkvocpro.R +import com.syaroful.agrilinkvocpro.core.components.AppButton +import com.syaroful.agrilinkvocpro.core.components.AppPasswordField +import com.syaroful.agrilinkvocpro.core.components.AppTextField +import com.syaroful.agrilinkvocpro.core.components.textTheme +import com.syaroful.agrilinkvocpro.ui.theme.DarkGrey +import com.syaroful.agrilinkvocpro.ui.theme.MainGreen + +@Composable +fun RegisterScreen( + onLoginSuccess: () -> Unit, + onNavigateToLogin: () -> Unit, +) { + + var name by remember { mutableStateOf("") } + var email by remember { mutableStateOf("") } + var username by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var phone by remember { mutableStateOf("") } + + Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> + Column( + modifier = Modifier + .fillMaxWidth() + .padding(innerPadding), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Image( + painter = painterResource(id = R.drawable.greenhouse_banner), + contentDescription = "Banner", + modifier = Modifier + .height(180.dp) + .align(Alignment.CenterHorizontally), + contentScale = ContentScale.Crop + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = "Login", style = textTheme.titleMedium, textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.fillMaxWidth(), + text = "Halo! yuk masuk ke dalam akunmu", + style = textTheme.titleSmall.copy(color = DarkGrey), + textAlign = TextAlign.Center + ) + AppTextField( + hint = "Username", + keyboardType = KeyboardType.Email, + leadingIcon = painterResource( + R.drawable.icon_email + ), + value = username, + onValueChange = { username = it } + ) + AppPasswordField( + hint = "Password", + keyboardType = KeyboardType.Password, + value = password, + onValueChange = { password = it } + ) + AppButton( + label = "Daftar", + ) { + } + + Spacer(modifier = Modifier.height(16.dp)) + + // Error Message +// if (loginState is ResultState.Error) { +// Text( +// modifier = Modifier.padding(16.dp), +// text = (loginState as ResultState.Error).message, +// color = Color.Red +// ) +// } + + // Navigate to Home if Success +// LaunchedEffect(loginState) { +// if (loginState is ResultState.Success<*>) { +// onLoginSuccess() +// } +// } + + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Text( + text = "Sudah punya akun? ", + style = textTheme.titleSmall.copy(color = DarkGrey), + textAlign = TextAlign.Center + ) + Text( + modifier = Modifier.clickable { onNavigateToLogin() }, + text = "Masuk", + style = textTheme.titleSmall.copy(color = MainGreen), + textAlign = TextAlign.Center + ) + } + } + } +} \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/splash/SplashScreen.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/splash/SplashScreen.kt new file mode 100644 index 0000000..6e7a826 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/splash/SplashScreen.kt @@ -0,0 +1,75 @@ +package com.syaroful.agrilinkvocpro.ui.screen.splash + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.BlurredEdgeTreatment +import androidx.compose.ui.draw.blur +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.syaroful.agrilinkvocpro.R +import com.syaroful.agrilinkvocpro.data.UserPreferences +import com.syaroful.agrilinkvocpro.ui.theme.DarkGreen +import com.syaroful.agrilinkvocpro.ui.theme.MainGreen +import kotlinx.coroutines.delay + +@Composable +fun SplashScreen( + userPreferences: UserPreferences, + onNavigate: (String) -> Unit, +) { + val isLoggedIn by userPreferences.isUserLoggedIn() + .collectAsState(initial = false) + + LaunchedEffect(isLoggedIn) { + delay(1000) + if (isLoggedIn) { + onNavigate("home") + } else { + onNavigate("login") + } + } + Box( + modifier = Modifier + .fillMaxSize() + .background(DarkGreen) + ) { + Box( + modifier = Modifier + .align(Alignment.TopStart) + .padding(top = 100.dp) + .blur(radius = 150.dp, edgeTreatment = BlurredEdgeTreatment.Unbounded) + .width(120.dp) + .height(120.dp) + .background(color = MainGreen) + ) + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(bottom = 100.dp) + .blur(radius = 150.dp, edgeTreatment = BlurredEdgeTreatment.Unbounded) + .width(180.dp) + .height(180.dp) + .background(color = MainGreen) + + ) + Image( + modifier = Modifier + .align(Alignment.Center) + .size(120.dp), + painter = painterResource(id = R.drawable.app_logo), + contentDescription = "App Logo" + ) + } +} \ No newline at end of file