This commit is contained in:
irreaaa 2025-03-31 11:45:53 +03:00
parent fa5067e017
commit ba5e517178
11 changed files with 154 additions and 42 deletions

View File

@ -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
)
}

View File

@ -21,6 +21,7 @@ fun CommonButton(
modifier: Modifier = Modifier,
onClick: () -> Unit,
buttonColors: ButtonColors,
enabled: Boolean = true,
content: @Composable () -> Unit)
{
Button(

View File

@ -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
}

View File

@ -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
)

View File

@ -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
)

View File

@ -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)
}
}

View File

@ -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> {AuthRepository(get()) }
single { AuthUseCase(get (),get()) }
viewModel { RegistrationViewModel(get()) }
viewModel { SignInViewModel(get())}
}

View File

@ -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<NetworkResponse<AuthorizationResponse>> = 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"))
}
}
}

View File

@ -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)
}
}
}
}
}
}

View File

@ -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,

View File

@ -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()
}
}