Jelajahi Sumber

Login API integrated, response getting in viewmodel, navigating to home screen if user creds are correct

Khubaib 2 minggu lalu
induk
melakukan
0ce4b54959

+ 1 - 0
app/src/main/java/com/fastest/pass/login/data/model/LoginResponse.kt

@@ -5,3 +5,4 @@ data class LoginResponse<T>(
     val message: String,
     val data: T? = null
 )
+

+ 1 - 1
app/src/main/java/com/fastest/pass/login/data/model/LoginResult.kt

@@ -3,6 +3,6 @@ package com.fastest.pass.login.data.model
 sealed class LoginResult {
     data object None : LoginResult()
     data object Loading : LoginResult()
-    data class Success(val loginResponse: Any?) : LoginResult()
+    data class Success(val loginResponse: LoginResponse<Any>) : LoginResult()
     data class Error(val message: String) : LoginResult()
 }

+ 8 - 6
app/src/main/java/com/fastest/pass/login/data/repository/LoginRepositoryImpl.kt

@@ -2,6 +2,7 @@ package com.fastest.pass.login.data.repository
 
 import android.util.Log
 import com.fastest.pass.login.data.model.LoginRequest
+import com.fastest.pass.login.data.model.LoginResponse
 import com.fastest.pass.login.data.model.LoginResult
 import com.fastest.pass.login.data.remote.LoginApiService
 import com.fastest.pass.login.domain.repository.LoginRepository
@@ -10,18 +11,19 @@ import javax.inject.Inject
 class LoginRepositoryImpl @Inject constructor(
     private val loginApiService: LoginApiService
 ) : LoginRepository {
-    override suspend fun loginRepository(email: String, password: String) : LoginResult {
+    override suspend fun loginRepository(email: String, password: String) : Pair<LoginResult, LoginResponse<Any>> {
         Log.d("test_api_login", "loginRepository() , LoginRepositoryImpl")
-        return try {
+         return try {
+            Log.d("test_api_login", "loginResponse = ?")
             val loginResponse = loginApiService.login(LoginRequest(email, password))
-            Log.d("test_api_login", "loginResponse = ${loginResponse.message}")
+            Log.d("test_api_login", "loginResponse = = ${loginResponse.message}")
             if (loginResponse.status) {
-                LoginResult.Success(loginResponse)
+                Pair(LoginResult.Success(loginResponse), loginResponse)
             } else {
-                LoginResult.Error(loginResponse.message)
+                Pair(LoginResult.Error(loginResponse.message), loginResponse)
             }
         } catch (e: Exception) {
-            LoginResult.Error(e.message ?: "Error")
+            Pair(LoginResult.Error(e.message ?: "Error"), LoginResponse(false, e.message ?: "Error"))
         }
     }
 

+ 2 - 1
app/src/main/java/com/fastest/pass/login/domain/repository/LoginRepository.kt

@@ -1,7 +1,8 @@
 package com.fastest.pass.login.domain.repository
 
+import com.fastest.pass.login.data.model.LoginResponse
 import com.fastest.pass.login.data.model.LoginResult
 
 interface LoginRepository {
-    suspend fun loginRepository(email: String, password: String) : LoginResult
+    suspend fun loginRepository(email: String, password: String) : Pair<LoginResult, LoginResponse<Any>>
 }

+ 2 - 1
app/src/main/java/com/fastest/pass/login/domain/usecase/LoginUseCase.kt

@@ -1,6 +1,7 @@
 package com.fastest.pass.login.domain.usecase
 
 import android.util.Log
+import com.fastest.pass.login.data.model.LoginResponse
 import com.fastest.pass.login.data.model.LoginResult
 import com.fastest.pass.login.domain.repository.LoginRepository
 import javax.inject.Inject
@@ -8,7 +9,7 @@ import javax.inject.Inject
 class LoginUseCase @Inject constructor(
     private val loginRepository: LoginRepository
 ) {
-    suspend operator fun invoke(email: String, password: String) : LoginResult {
+    suspend operator fun invoke(email: String, password: String) : Pair<LoginResult, LoginResponse<Any>> {
         Log.d("test_api_login", "invoke() , LoginUseCase")
         return loginRepository.loginRepository(email, password)
     }

+ 7 - 2
app/src/main/java/com/fastest/pass/login/presentation/ui/LoginFragment.kt

@@ -56,9 +56,14 @@ class LoginFragment : BaseFragment() {
                                 .background(colorResource(id = R.color.white))
                                 .padding(paddingValues.calculateBottomPadding())
                         ) {
-                            val loginResult = viewmodel.loginResult.collectAsState()
+                            val loader = viewmodel.loader.collectAsState()
+                            val loginResponse = viewmodel.loginResponse.collectAsState()
 
-                            LoginScreen(loginResult.value, clickType = { clickType ->
+                            if (loginResponse.value.status) {
+                                viewmodel.navigateTo(LoginRoute.OpenDashBoardScreen)
+                            }
+
+                            LoginScreen(loader.value, clickType = { clickType ->
                                 when (clickType) {
                                     ClickType.SIGNUP_CLICK -> {
                                         viewmodel.navigateTo(LoginRoute.OpenSignUp)

+ 63 - 19
app/src/main/java/com/fastest/pass/login/presentation/ui/components/LoginScreen.kt

@@ -25,20 +25,26 @@ import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextField
 import androidx.compose.material3.TextFieldDefaults
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.graphics.ColorFilter
@@ -59,7 +65,9 @@ import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import com.fastest.pass.R
-import com.fastest.pass.login.data.model.LoginResult
+import com.fastest.views.ShowCustomSnackBar
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 
 enum class ClickType {
     SIGNUP_CLICK,
@@ -72,7 +80,7 @@ enum class ClickType {
 
 @Composable
 fun LoginScreen(
-    loginResult: LoginResult,
+    loader: Boolean,
     clickType: (ClickType) -> Unit,
     onLoginClickCredentials: (String, String) -> Unit
 ) {
@@ -80,10 +88,14 @@ fun LoginScreen(
     val focusManager = LocalFocusManager.current
     var emailLogin by remember { mutableStateOf("") }
     var passwordLogin by remember { mutableStateOf("") }
+    val snackBarStateRed = remember { SnackbarHostState() }
+
+    Log.d("loginResult", "loader = $loader")
 
     Box(
         modifier = Modifier
             .background(colorResource(id = R.color.blue_login))
+            .alpha(if (loader) 0.7f else 1f)
             .fillMaxSize()
             .statusBarsPadding()
             .pointerInput(Unit) {
@@ -93,12 +105,15 @@ fun LoginScreen(
                 }
             }
     ) {
+
         Column(
             modifier = Modifier
                 .fillMaxSize()
                 .padding(top = 0.dp)
                 .statusBarsPadding()
         ) {
+            ShowCustomSnackBar(snackBarStateRed, R.color.red_login_button, R.color.white)
+
             ShowHeaderLogin(text = stringResource(R.string.login)) { clickType ->
                 clickType(clickType)
             }
@@ -112,6 +127,7 @@ fun LoginScreen(
                         colorResource(id = R.color.light_gray_login),
                     )
             ) {
+
                 ShowWelcomeText(R.string.login_to_continue)
                 LoginTextField(keyboardController, focusManager, onEmailText = {
                     emailLogin = it
@@ -121,11 +137,18 @@ fun LoginScreen(
                     passwordLogin = it
                 })
                 Spacer(modifier = Modifier.height(25.dp))
-                LoginButton(buttonText = R.string.login, clickType = { clickType ->
+                LoginButton(
+                    buttonText = R.string.login,
+                    clickType = { clickType ->
                     clickType(clickType)
-                }, email = emailLogin, password = passwordLogin, onLoginClickCredentials = { email, password ->
+                },
+                    email = emailLogin,
+                    password = passwordLogin,
+                    onLoginClickCredentials = { email, password ->
                     onLoginClickCredentials.invoke(email, password)
-                })
+                },
+                    snackBarStateRed = snackBarStateRed
+                )
                 ForgotPasswordText() { clickType ->
                     clickType(clickType)
                 }
@@ -134,21 +157,30 @@ fun LoginScreen(
                 }
             }
         }
-    }
 
-    when (loginResult) {
-        is LoginResult.Error -> {
-            Log.d("loginResult", "Error")
-        }
-        LoginResult.Loading -> {
-            Log.d("loginResult", "Loading")
-        }
-        LoginResult.None -> {
-            Log.d("loginResult", "None")
-        }
-        is LoginResult.Success -> {
-            Log.d("loginResult", "Success")
+        if (loader) {
+            var progress by remember { mutableFloatStateOf(0.1F) }
+
+            LaunchedEffect(key1 = Unit) {
+                while (true) {
+                    for (i in 1..100) {
+                        progress = i.toFloat()/100F
+                        delay(50)
+                    }
+                    progress = 0.1F
+                }
+            }
+
+            CircularProgressIndicator(
+                progress = { progress },
+                modifier = Modifier
+                    .size(50.dp)
+                    .align(Alignment.Center),
+                color = colorResource(id = R.color.yellow_text),
+                strokeWidth = 5.dp,
+            )
         }
+
     }
 }
 
@@ -401,8 +433,12 @@ fun ColumnScope.LoginButton(
     clickType: (ClickType) -> Unit,
     email: String,
     password: String,
-    onLoginClickCredentials: (String, String) -> Unit
+    onLoginClickCredentials: (String, String) -> Unit,
+    snackBarStateRed: SnackbarHostState
 ) {
+    val coroutineScope = rememberCoroutineScope()
+    val context = LocalContext.current
+
     Button(
         modifier = Modifier
             .padding(start = 30.dp, end = 30.dp)
@@ -416,6 +452,14 @@ fun ColumnScope.LoginButton(
 
             if (email.isNotEmpty() && password.isNotEmpty()) {
                 onLoginClickCredentials.invoke(email, password)
+            } else if (email.isEmpty()) {
+                coroutineScope.launch {
+                    snackBarStateRed.showSnackbar(context.getString(R.string.email_field_req))
+                }
+            } else if (password.isEmpty()) {
+                coroutineScope.launch {
+                    snackBarStateRed.showSnackbar(context.getString(R.string.password_field_req))
+                }
             }
         },
         shape = RoundedCornerShape(15.dp),

+ 13 - 4
app/src/main/java/com/fastest/pass/login/presentation/viewmodels/LoginViewModel.kt

@@ -3,6 +3,7 @@ package com.fastest.pass.login.presentation.viewmodels
 import android.util.Log
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import com.fastest.pass.login.data.model.LoginResponse
 import com.fastest.pass.login.data.model.LoginResult
 import com.fastest.pass.login.domain.usecase.LoginUseCase
 import com.fastest.pass.login.utils.LoginRoute
@@ -19,19 +20,27 @@ class LoginViewModel @Inject constructor(
     private val _router = MutableStateFlow<LoginRoute>(LoginRoute.OpenNoneScreen)
     val router: MutableStateFlow<LoginRoute> = _router
 
-    private val _loginResult = MutableStateFlow<LoginResult>(LoginResult.None)
-    val loginResult: MutableStateFlow<LoginResult> = _loginResult
+    private val _loginResponse = MutableStateFlow<LoginResponse<Any>>(LoginResponse(false, ""))
+    val loginResponse: MutableStateFlow<LoginResponse<Any>> = _loginResponse
+
+    private val _loader = MutableStateFlow<Boolean>(false)
+    val loader: MutableStateFlow<Boolean> = _loader
 
     fun navigateTo(loginRoute: LoginRoute) {
         _router.value = loginRoute
     }
 
     fun onLoginClicked(email: String, password: String) {
-        _loginResult.value = LoginResult.Loading
         viewModelScope.launch {
+            _loader.value = true
             Log.d("test_api_login", "onLoginClicked => LoginViewModel")
             val loginResult = loginUseCase(email, password)
-            Log.d("loginResult", "loginResult = $loginResult")
+            _loader.value = false
+
+            if (loginResult.second.status) {
+                _loginResponse.value = loginResult.second
+            }
+            Log.d("loginResult", "loginResult = ${loginResult.second.message}")
         }
     }
 }

+ 60 - 0
app/src/main/java/com/fastest/views/CustomSnackBar.kt

@@ -0,0 +1,60 @@
+package com.fastest.views
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Snackbar
+import androidx.compose.material3.Icon
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+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.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.fastest.pass.R
+import com.fastest.pass.ui.theme.outfitFontFamily
+
+@Composable
+fun ShowCustomSnackBar(snackBarState: SnackbarHostState, color: Int, textColor: Int) {
+    SnackbarHost(
+        hostState = snackBarState,
+        modifier = Modifier.padding(10.dp),
+    ) {data ->
+        Snackbar(
+            elevation = 0.dp,
+            backgroundColor = colorResource(id = color),
+        ) {
+            Box(
+                modifier = Modifier,
+            ) {
+                Row(
+                    verticalAlignment = Alignment.CenterVertically,
+                    modifier = Modifier
+                ) {
+                    Icon(painter = painterResource(id = R.drawable.splashlogo),
+                        contentDescription = "Error",
+                        tint = Color.Unspecified,
+                        modifier = Modifier.size(25.dp)
+                    )
+                    Text(text = data.visuals.message,
+                        style = TextStyle(
+                            fontFamily = outfitFontFamily,
+                            fontWeight = FontWeight.Medium,
+                            fontSize = 16.sp,
+                            color = colorResource(id = textColor)
+                        ),
+                        modifier = Modifier.padding(start = 10.dp)
+                    )
+                }
+            }
+        }
+    }
+}

+ 1 - 0
app/src/main/res/values/colors.xml

@@ -23,6 +23,7 @@
     <color name="brown_text">#9B8334</color>
     <color name="light_green">#EAF7F2</color>
     <color name="green_text">#3C8866</color>
+    <color name="yellow_text">#fec32b</color>
 
 
 </resources>

+ 2 - 1
app/src/main/res/values/strings.xml

@@ -134,6 +134,7 @@
     <string name="uppercase_abc">Uppercase (ABC)</string>
     <string name="numbers_123">Numbers (123)</string>
     <string name="randomized_symbols">Randomized symbols (!#$)</string>
-
+    <string name="email_field_req">The email field is required.</string>
+    <string name="password_field_req">The password field is required.</string>
 
 </resources>