From ba5e517178119c7718ff9973583b2012969d00f5 Mon Sep 17 00:00:00 2001 From: irreaaa Date: Mon, 31 Mar 2025 11:45:53 +0300 Subject: [PATCH] api_auth --- .../com/example/shoesapptest/MainActivity.kt | 20 ++---- .../shoesapptest/common/CommonButton.kt | 1 + .../remote/network/auth/AuthRemoteSource.kt | 5 +- .../network/dto/AuthorizationRequest.kt | 9 +++ .../network/dto/AuthorizationResponse.kt | 9 +++ .../data/repository/AuthRepository.kt | 6 ++ .../com/example/shoesapptest/di/appModules.kt | 2 + .../domain/usecase/AuthUseCase.kt | 17 +++++ .../screen/signin/SignInViewModel.kt | 48 +++++++++++-- .../screen/signin/SigninScreen.kt | 67 ++++++++++++++----- .../screen/signin/component/AuthButton.kt | 12 ++-- 11 files changed, 154 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/com/example/shoesapptest/data/remote/network/dto/AuthorizationRequest.kt create mode 100644 app/src/main/java/com/example/shoesapptest/data/remote/network/dto/AuthorizationResponse.kt diff --git a/app/src/main/java/com/example/shoesapptest/MainActivity.kt b/app/src/main/java/com/example/shoesapptest/MainActivity.kt index 14593b1..5808dd8 100644 --- a/app/src/main/java/com/example/shoesapptest/MainActivity.kt +++ b/app/src/main/java/com/example/shoesapptest/MainActivity.kt @@ -7,19 +7,12 @@ import androidx.activity.enableEdgeToEdge import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.example.shoesapp.ui.screen.SigninScreen +import com.example.shoesapp.ui.screen.SignInScreen import com.example.shoesapp.ui.theme.MatuleTheme -import com.example.shoesapptest.data.local.DataStore -import com.example.shoesapptest.data.remote.network.RetrofitClient -import com.example.shoesapptest.data.repository.AuthRepository -import com.example.shoesapptest.di.appModules -import com.example.shoesapptest.domain.usecase.AuthUseCase import com.example.shoesapptest.screen.StartsScreens.FirstScreen import com.example.shoesapptest.screen.StartsScreens.SlideScreen import com.example.shoesapptest.screen.forgotpassword.ForgotPassScreen import com.example.shoesapptest.screen.regscreen.RegisterAccountScreen -import org.koin.core.context.GlobalContext -import org.koin.core.context.startKoin class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -28,12 +21,6 @@ class MainActivity : ComponentActivity() { setContent { MatuleTheme { - if (GlobalContext.getOrNull() == null) { - startKoin { - modules(appModules) - } - } - val navController = rememberNavController() NavHost( @@ -57,10 +44,13 @@ class MainActivity : ComponentActivity() { } composable(Screen.SignIn.route) { - SigninScreen( + SignInScreen( onNavigationToRegScreen = { navController.navigate(Screen.Registration.route) }, + onSignInSuccess = { + // Добавить навигацию после успешного входа + }, navController = navController ) } diff --git a/app/src/main/java/com/example/shoesapptest/common/CommonButton.kt b/app/src/main/java/com/example/shoesapptest/common/CommonButton.kt index 2443656..7a3ff29 100644 --- a/app/src/main/java/com/example/shoesapptest/common/CommonButton.kt +++ b/app/src/main/java/com/example/shoesapptest/common/CommonButton.kt @@ -21,6 +21,7 @@ fun CommonButton( modifier: Modifier = Modifier, onClick: () -> Unit, buttonColors: ButtonColors, + enabled: Boolean = true, content: @Composable () -> Unit) { Button( diff --git a/app/src/main/java/com/example/shoesapptest/data/remote/network/auth/AuthRemoteSource.kt b/app/src/main/java/com/example/shoesapptest/data/remote/network/auth/AuthRemoteSource.kt index e73b7aa..84cf073 100644 --- a/app/src/main/java/com/example/shoesapptest/data/remote/network/auth/AuthRemoteSource.kt +++ b/app/src/main/java/com/example/shoesapptest/data/remote/network/auth/AuthRemoteSource.kt @@ -1,5 +1,7 @@ package com.example.shoesapptest.data.remote.network.auth +import com.example.shoesapptest.data.remote.network.dto.AuthorizationRequest +import com.example.shoesapptest.data.remote.network.dto.AuthorizationResponse import com.example.shoesapptest.data.remote.network.dto.RegistrationRequest import com.example.shoesapptest.data.remote.network.dto.RegistrationResponse import retrofit2.http.Body @@ -9,5 +11,6 @@ interface AuthRemoteSource { @POST("/registration") suspend fun registration(@Body registrationRequest: RegistrationRequest): RegistrationResponse - + @POST("/authorization") + suspend fun authorization(@Body authorizationRequest: AuthorizationRequest): AuthorizationResponse } diff --git a/app/src/main/java/com/example/shoesapptest/data/remote/network/dto/AuthorizationRequest.kt b/app/src/main/java/com/example/shoesapptest/data/remote/network/dto/AuthorizationRequest.kt new file mode 100644 index 0000000..e2b1d6e --- /dev/null +++ b/app/src/main/java/com/example/shoesapptest/data/remote/network/dto/AuthorizationRequest.kt @@ -0,0 +1,9 @@ +package com.example.shoesapptest.data.remote.network.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class AuthorizationRequest ( + val email: String, + val password: String + ) \ No newline at end of file diff --git a/app/src/main/java/com/example/shoesapptest/data/remote/network/dto/AuthorizationResponse.kt b/app/src/main/java/com/example/shoesapptest/data/remote/network/dto/AuthorizationResponse.kt new file mode 100644 index 0000000..ba091e1 --- /dev/null +++ b/app/src/main/java/com/example/shoesapptest/data/remote/network/dto/AuthorizationResponse.kt @@ -0,0 +1,9 @@ +package com.example.shoesapptest.data.remote.network.dto + +data class AuthorizationResponse ( + val token: String, + val userId: String, + val userName: String, + val email: String, + val expiresIn: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/example/shoesapptest/data/repository/AuthRepository.kt b/app/src/main/java/com/example/shoesapptest/data/repository/AuthRepository.kt index 7c53cfd..849d3b2 100644 --- a/app/src/main/java/com/example/shoesapptest/data/repository/AuthRepository.kt +++ b/app/src/main/java/com/example/shoesapptest/data/repository/AuthRepository.kt @@ -3,6 +3,8 @@ package com.example.shoesapptest.data.repository import com.example.shoesapptest.data.local.DataStore import com.example.shoesapptest.data.remote.network.NetworkResponse import com.example.shoesapptest.data.remote.network.auth.AuthRemoteSource +import com.example.shoesapptest.data.remote.network.dto.AuthorizationRequest +import com.example.shoesapptest.data.remote.network.dto.AuthorizationResponse import com.example.shoesapptest.data.remote.network.dto.RegistrationRequest import com.example.shoesapptest.data.remote.network.dto.RegistrationResponse @@ -10,4 +12,8 @@ class AuthRepository(val authRemoteSource: AuthRemoteSource) { suspend fun registration(registrationRequest: RegistrationRequest): RegistrationResponse { return authRemoteSource.registration(registrationRequest) } + + suspend fun authorization(authorizationRequest: AuthorizationRequest): AuthorizationResponse { + return authRemoteSource.authorization(authorizationRequest) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/shoesapptest/di/appModules.kt b/app/src/main/java/com/example/shoesapptest/di/appModules.kt index 0ba9439..68f5007 100644 --- a/app/src/main/java/com/example/shoesapptest/di/appModules.kt +++ b/app/src/main/java/com/example/shoesapptest/di/appModules.kt @@ -6,6 +6,7 @@ import com.example.shoesapptest.data.remote.network.auth.AuthRemoteSource import com.example.shoesapptest.data.repository.AuthRepository import com.example.shoesapptest.domain.usecase.AuthUseCase import com.example.shoesapptest.screen.regscreen.RegistrationViewModel +import com.example.shoesapptest.screen.signin.SignInViewModel import org.koin.core.module.dsl.viewModel import org.koin.dsl.module @@ -15,4 +16,5 @@ val appModules = module { single {AuthRepository(get()) } single { AuthUseCase(get (),get()) } viewModel { RegistrationViewModel(get()) } + viewModel { SignInViewModel(get())} } \ No newline at end of file diff --git a/app/src/main/java/com/example/shoesapptest/domain/usecase/AuthUseCase.kt b/app/src/main/java/com/example/shoesapptest/domain/usecase/AuthUseCase.kt index e017d71..c87e0bb 100644 --- a/app/src/main/java/com/example/shoesapptest/domain/usecase/AuthUseCase.kt +++ b/app/src/main/java/com/example/shoesapptest/domain/usecase/AuthUseCase.kt @@ -2,6 +2,8 @@ package com.example.shoesapptest.domain.usecase import com.example.shoesapptest.data.local.DataStore import com.example.shoesapptest.data.remote.network.NetworkResponse +import com.example.shoesapptest.data.remote.network.dto.AuthorizationRequest +import com.example.shoesapptest.data.remote.network.dto.AuthorizationResponse import com.example.shoesapptest.data.remote.network.dto.RegistrationRequest import com.example.shoesapptest.data.remote.network.dto.RegistrationResponse import com.example.shoesapptest.data.repository.AuthRepository @@ -25,4 +27,19 @@ class AuthUseCase(private val dataStore: DataStore, private val authRepository: emit(NetworkResponse.Error("Unknown Error")) } } + + suspend fun authorization(authorizationRequest: AuthorizationRequest): Flow> = flow { + try { + emit(NetworkResponse.Loading) + val result = authRepository.authorization(authorizationRequest) + dataStore.setToken(result.token) + emit(NetworkResponse.Success(result)) + } catch (e: Exception) { + e.message?.let { + emit(NetworkResponse.Error(it)) + return@flow + } + emit(NetworkResponse.Error("Unknown Error")) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/shoesapptest/screen/signin/SignInViewModel.kt b/app/src/main/java/com/example/shoesapptest/screen/signin/SignInViewModel.kt index 7af977d..f5b4873 100644 --- a/app/src/main/java/com/example/shoesapptest/screen/signin/SignInViewModel.kt +++ b/app/src/main/java/com/example/shoesapptest/screen/signin/SignInViewModel.kt @@ -3,25 +3,61 @@ package com.example.shoesapptest.screen.signin import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.shoesapptest.data.remote.network.NetworkResponse +import com.example.shoesapptest.data.remote.network.dto.AuthorizationRequest +import com.example.shoesapptest.domain.usecase.AuthUseCase +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch -class SignInViewMode: ViewModel(){ +class SignInViewModel(private val authUseCase: AuthUseCase) : ViewModel() { var signInState = mutableStateOf(SignInState()) - private set + private set val emailHasError = derivedStateOf { - if(signInState.value.email.isEmpty()) return@derivedStateOf false + if (signInState.value.email.isEmpty()) return@derivedStateOf false !android.util.Patterns.EMAIL_ADDRESS.matcher(signInState.value.email).matches() } - fun setEmail(email: String){ + fun setEmail(email: String) { signInState.value = signInState.value.copy(email = email) } - - fun setPassword(password: String){ + fun setPassword(password: String) { signInState.value = signInState.value.copy(password = password) } + fun setErrorMessage(message: String?) { + signInState.value = signInState.value.copy(errorMessage = message) + } + private fun setLoading(isLoading: Boolean) { + signInState.value = signInState.value.copy(isLoading = isLoading) + } + fun signIn(onSuccess: () -> Unit) { + viewModelScope.launch { + val authorizationRequest = AuthorizationRequest( + email = signInState.value.email, + password = signInState.value.password + ) + + authUseCase.authorization(authorizationRequest).collect { response -> + when (response) { + is NetworkResponse.Error -> { + setLoading(false) + setErrorMessage(response.errorMessage) + } + is NetworkResponse.Success -> { + setLoading(false) + signInState.value = signInState.value.copy(isSignIn = true) + onSuccess() + } + is NetworkResponse.Loading -> { + setLoading(true) + } + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/shoesapptest/screen/signin/SigninScreen.kt b/app/src/main/java/com/example/shoesapptest/screen/signin/SigninScreen.kt index 2be5b45..7a819cb 100644 --- a/app/src/main/java/com/example/shoesapptest/screen/signin/SigninScreen.kt +++ b/app/src/main/java/com/example/shoesapptest/screen/signin/SigninScreen.kt @@ -16,13 +16,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -41,19 +45,36 @@ import androidx.navigation.NavController import com.example.shoesapp.ui.theme.MatuleTheme import com.example.shoesapptest.R import com.example.shoesapptest.common.CommonButton -import com.example.shoesapptest.screen.signin.SignInViewMode +import com.example.shoesapptest.screen.signin.SignInViewModel import com.example.shoesapptest.screen.signin.component.AuthButton import com.example.shoesapptest.screen.signin.component.AuthTextField import com.example.shoesapptest.screen.signin.component.TitleWithSubtitleText +import org.koin.compose.viewmodel.koinViewModel @Composable -fun SigninScreen( +fun SignInScreen( onNavigationToRegScreen: () -> Unit, + onSignInSuccess: () -> Unit, navController: NavController ) { - val signInViewModel: SignInViewMode = viewModel() + val viewModel: SignInViewModel = koinViewModel() + val snackbarHostState = remember { SnackbarHostState() } + + LaunchedEffect(viewModel.signInState.value.isSignIn) { + if (viewModel.signInState.value.isSignIn) { + onSignInSuccess() + } + } + + LaunchedEffect(viewModel.signInState.value.errorMessage) { + viewModel.signInState.value.errorMessage?.let { + snackbarHostState.showSnackbar(it) + viewModel.setErrorMessage(null) + } + } Scaffold( + snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { Row( modifier = Modifier @@ -92,17 +113,24 @@ fun SigninScreen( ) { paddingValues -> SignInContent( paddingValues = paddingValues, - signInViewMode = signInViewModel, + viewModel = viewModel, navController = navController ) } } @Composable -fun SignInContent(paddingValues: PaddingValues, signInViewMode: SignInViewMode, navController: NavController) { - val signInState = signInViewMode.signInState +fun SignInContent( + paddingValues: PaddingValues, + viewModel: SignInViewModel, + navController: NavController +) { + val state = viewModel.signInState.value + Column( - modifier = Modifier.padding(paddingValues = paddingValues), + modifier = Modifier + .padding(paddingValues) + .padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { TitleWithSubtitleText( @@ -112,24 +140,23 @@ fun SignInContent(paddingValues: PaddingValues, signInViewMode: SignInViewMode, Spacer(modifier = Modifier.height(35.dp)) AuthTextField( - value = signInState.value.email, - onChangeValue = { signInViewMode.setEmail(it) }, - isError = signInViewMode.emailHasError.value, + value = state.email, + onChangeValue = { viewModel.setEmail(it) }, + isError = viewModel.emailHasError.value, supportingText = { Text(text = stringResource(R.string.LoginError))}, placeholder = { Text(text = stringResource(R.string.template_email)) }, label = { Text(text = stringResource(R.string.email)) } ) AuthTextField( - value = signInState.value.password, - onChangeValue = { signInViewMode.setPassword(it) }, + value = state.password, + onChangeValue = { viewModel.setPassword(it) }, isError = false, supportingText = { Text(text = "Неверный пароль")}, placeholder = { Text(text = stringResource(R.string.PasswordPlaceHolder)) }, label = { Text(text = stringResource(R.string.Password)) } ) - Text( text = "Забыл пароль", style = MatuleTheme.typography.bodyRegular16.copy(color = MatuleTheme.colors.text), @@ -140,8 +167,17 @@ fun SignInContent(paddingValues: PaddingValues, signInViewMode: SignInViewMode, .padding(top = 8.dp) ) - AuthButton(onClick = {}) { - Text(stringResource(R.string.Sign_In)) + AuthButton( + onClick = { viewModel.signIn {} }, + enabled = !viewModel.emailHasError.value && + state.email.isNotEmpty() && + state.password.isNotEmpty() + ) { + if (state.isLoading) { + CircularProgressIndicator() + } else { + Text(stringResource(R.string.Sign_In)) + } } } } @@ -152,4 +188,3 @@ fun SignInContent(paddingValues: PaddingValues, signInViewMode: SignInViewMode, - diff --git a/app/src/main/java/com/example/shoesapptest/screen/signin/component/AuthButton.kt b/app/src/main/java/com/example/shoesapptest/screen/signin/component/AuthButton.kt index 4b20eea..5bf31f3 100644 --- a/app/src/main/java/com/example/shoesapptest/screen/signin/component/AuthButton.kt +++ b/app/src/main/java/com/example/shoesapptest/screen/signin/component/AuthButton.kt @@ -13,16 +13,20 @@ import com.example.shoesapptest.common.CommonButton fun AuthButton( onClick: () -> Unit, modifier: Modifier = Modifier, + enabled: Boolean = true, content: @Composable () -> Unit -){ +) { CommonButton( onClick = onClick, + modifier = modifier.padding(50.dp), + enabled = enabled, buttonColors = ButtonColors( contentColor = MatuleTheme.colors.background, containerColor = MatuleTheme.colors.accent, disabledContainerColor = MatuleTheme.colors.accent, disabledContentColor = MatuleTheme.colors.accent - ), - modifier = modifier.padding(50.dp) - ) { content()} + ) + ) { + content() + } } \ No newline at end of file