From fc9c9614ba3c7eb6da4834e7e030b9253cb811b4 Mon Sep 17 00:00:00 2001 From: Cutiful <113351087+Syaroful@users.noreply.github.com> Date: Sat, 5 Jul 2025 11:26:07 +0700 Subject: [PATCH] feat: implement register feature --- .../data/model/RegisterResponse.kt | 6 ++ .../agrilinkvocpro/di/ViewModelModule.kt | 2 + .../agrilinkvocpro/navigation/NavGraph.kt | 2 +- .../screen/login/LoginViewModel.kt | 21 +------ .../screen/register/RegisterScreen.kt | 58 +++++++++++++++++-- .../screen/register/RegisterViewModel.kt | 31 +++++++--- .../control_feature/di/ViewModelModule.kt | 2 + 7 files changed, 88 insertions(+), 34 deletions(-) create mode 100644 agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/RegisterResponse.kt diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/RegisterResponse.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/RegisterResponse.kt new file mode 100644 index 0000000..1febc9c --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/RegisterResponse.kt @@ -0,0 +1,6 @@ +package com.syaroful.agrilinkvocpro.data.model + +data class RegisterResponse( + val message: String?, + val success: Boolean? +) \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/di/ViewModelModule.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/di/ViewModelModule.kt index 0ad1154..8167aa8 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/di/ViewModelModule.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/di/ViewModelModule.kt @@ -5,6 +5,7 @@ import com.syaroful.agrilinkvocpro.presentation.screen.home.DynamicModuleViewMod import com.syaroful.agrilinkvocpro.presentation.screen.home.HomeViewModel import com.syaroful.agrilinkvocpro.presentation.screen.login.LoginViewModel import com.syaroful.agrilinkvocpro.presentation.screen.profile.ProfileViewModel +import com.syaroful.agrilinkvocpro.presentation.screen.register.RegisterViewModel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module @@ -14,4 +15,5 @@ val viewModelModule = module { viewModel { ProfileViewModel(get()) } viewModel { HomeViewModel(get(), get()) } viewModel { DetailViewModel(get(), get()) } + viewModel { RegisterViewModel(get()) } } \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/navigation/NavGraph.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/navigation/NavGraph.kt index 720502c..1fc5cd8 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/navigation/NavGraph.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/navigation/NavGraph.kt @@ -55,7 +55,7 @@ fun SetupNavigation( } composable("register") { RegisterScreen( - onLoginSuccess = { + onRegisterSuccess = { navController.navigate("login") { popUpTo("login") { inclusive = true } } diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/login/LoginViewModel.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/login/LoginViewModel.kt index b92d327..4a7b353 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/login/LoginViewModel.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/login/LoginViewModel.kt @@ -3,6 +3,7 @@ package com.syaroful.agrilinkvocpro.presentation.screen.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.syaroful.agrilinkvocpro.core.utils.ResultState +import com.syaroful.agrilinkvocpro.core.utils.extention.mapToUserFriendlyError import com.syaroful.agrilinkvocpro.data.UserPreferences import com.syaroful.agrilinkvocpro.data.model.LoginResponse import com.syaroful.agrilinkvocpro.data.repository.AuthRepository @@ -55,24 +56,4 @@ class LoginViewModel( 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/presentation/screen/register/RegisterScreen.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/register/RegisterScreen.kt index 0517e2e..b1fe9c5 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/register/RegisterScreen.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/register/RegisterScreen.kt @@ -12,16 +12,25 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState 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 @@ -30,23 +39,43 @@ 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.CustomSnackBarComponent import com.syaroful.agrilinkvocpro.core.components.textTheme +import com.syaroful.agrilinkvocpro.core.utils.ResultState import com.syaroful.agrilinkvocpro.presentation.theme.DarkGrey import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen +import org.koin.androidx.compose.koinViewModel @Composable fun RegisterScreen( - onLoginSuccess: () -> Unit, + registerViewModel: RegisterViewModel = koinViewModel(), + onRegisterSuccess: () -> Unit, onNavigateToLogin: () -> Unit, ) { + val signupState by registerViewModel.signupState.collectAsState() + + val focusManager = LocalFocusManager.current + + val snackBarHostState = remember { SnackbarHostState() } + 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 -> + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackBarHostState) { data -> + CustomSnackBarComponent( + modifier = Modifier.padding(16.dp), + message = data.visuals.message, + icon = if (data.visuals.message.contains("Failed")) Icons.Default.Clear else Icons.Default.Check + ) + } + }, + modifier = Modifier.fillMaxSize() + ) { innerPadding -> Column( modifier = Modifier .verticalScroll(rememberScrollState()) @@ -108,8 +137,18 @@ fun RegisterScreen( ) AppButton( - label = "Daftar", + label = if (signupState is ResultState.Loading) "Memuat..." else "Daftar", + isEnable = signupState !is ResultState.Loading, ) { + focusManager.clearFocus() + registerViewModel.signup(name, username, email, password) + } + if (signupState is ResultState.Error) { + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = (signupState as ResultState.Error).message, + color = Color.Red + ) } Row( @@ -128,6 +167,17 @@ fun RegisterScreen( textAlign = TextAlign.Center ) } + + LaunchedEffect(signupState) { + if (signupState is ResultState.Success<*>) { + snackBarHostState.showSnackbar(message = "Pendaftaran Akun Berhasil") + // Navigate to Home if Success + onRegisterSuccess() + registerViewModel.resetRegisterState() + } else if (signupState is ResultState.Error) { + snackBarHostState.showSnackbar(message = (signupState as ResultState.Error).message) + } + } } } } \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/register/RegisterViewModel.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/register/RegisterViewModel.kt index c53be24..35f4703 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/register/RegisterViewModel.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/register/RegisterViewModel.kt @@ -3,26 +3,39 @@ package com.syaroful.agrilinkvocpro.presentation.screen.register import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.syaroful.agrilinkvocpro.core.utils.ResultState -import com.syaroful.agrilinkvocpro.data.model.LoginResponse +import com.syaroful.agrilinkvocpro.core.utils.extention.mapToUserFriendlyError +import com.syaroful.agrilinkvocpro.data.model.RegisterResponse import com.syaroful.agrilinkvocpro.data.repository.AuthRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch class RegisterViewModel( - authRepository: AuthRepository, -) : ViewModel(){ - private val _signupState = MutableStateFlow>(ResultState.Idle) - val signupState: StateFlow> = _signupState + private val authRepository: AuthRepository, +) : ViewModel() { + private val _signupState = MutableStateFlow>(ResultState.Idle) + val signupState: StateFlow> = _signupState - fun signup(name: String, username: String, email: String, password: String){ + fun signup(name: String, username: String, email: String, password: String) { _signupState.value = ResultState.Loading viewModelScope.launch { try { - - }catch (e: Exception){ - + val response = authRepository.register(username, password, email, name) + if (response.isSuccessful) { + _signupState.value = ResultState.Success(response.body()) + } else { + val errorMessage = response.body()?.message ?: "Register Failed" + _signupState.value = ResultState.Error(errorMessage) + } + } catch (e: Exception) { + val errorMessage = mapToUserFriendlyError(e) + _signupState.value = ResultState.Error(errorMessage) } } } + + fun resetRegisterState(){ + _signupState.value = ResultState.Idle + + } } \ No newline at end of file diff --git a/agrilinkvocpro/control_feature/src/main/java/com/syaroful/agrilinkvocpro/control_feature/di/ViewModelModule.kt b/agrilinkvocpro/control_feature/src/main/java/com/syaroful/agrilinkvocpro/control_feature/di/ViewModelModule.kt index 1e73f54..c346d56 100644 --- a/agrilinkvocpro/control_feature/src/main/java/com/syaroful/agrilinkvocpro/control_feature/di/ViewModelModule.kt +++ b/agrilinkvocpro/control_feature/src/main/java/com/syaroful/agrilinkvocpro/control_feature/di/ViewModelModule.kt @@ -1,9 +1,11 @@ package com.syaroful.agrilinkvocpro.control_feature.di import com.syaroful.agrilinkvocpro.control_feature.presentation.control.ControlViewModel +import com.syaroful.agrilinkvocpro.control_feature.presentation.history.ControlHistoryViewModel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module val viewModelModule = module { viewModel { ControlViewModel(get(), get()) } + viewModel { ControlHistoryViewModel(get(), get()) } } \ No newline at end of file