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.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController 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.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.FirstScreen
import com.example.shoesapptest.screen.StartsScreens.SlideScreen import com.example.shoesapptest.screen.StartsScreens.SlideScreen
import com.example.shoesapptest.screen.forgotpassword.ForgotPassScreen import com.example.shoesapptest.screen.forgotpassword.ForgotPassScreen
import com.example.shoesapptest.screen.regscreen.RegisterAccountScreen import com.example.shoesapptest.screen.regscreen.RegisterAccountScreen
import org.koin.core.context.GlobalContext
import org.koin.core.context.startKoin
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -28,12 +21,6 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
MatuleTheme { MatuleTheme {
if (GlobalContext.getOrNull() == null) {
startKoin {
modules(appModules)
}
}
val navController = rememberNavController() val navController = rememberNavController()
NavHost( NavHost(
@ -57,10 +44,13 @@ class MainActivity : ComponentActivity() {
} }
composable(Screen.SignIn.route) { composable(Screen.SignIn.route) {
SigninScreen( SignInScreen(
onNavigationToRegScreen = { onNavigationToRegScreen = {
navController.navigate(Screen.Registration.route) navController.navigate(Screen.Registration.route)
}, },
onSignInSuccess = {
// Добавить навигацию после успешного входа
},
navController = navController navController = navController
) )
} }

View File

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

View File

@ -1,5 +1,7 @@
package com.example.shoesapptest.data.remote.network.auth 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.RegistrationRequest
import com.example.shoesapptest.data.remote.network.dto.RegistrationResponse import com.example.shoesapptest.data.remote.network.dto.RegistrationResponse
import retrofit2.http.Body import retrofit2.http.Body
@ -9,5 +11,6 @@ interface AuthRemoteSource {
@POST("/registration") @POST("/registration")
suspend fun registration(@Body registrationRequest: RegistrationRequest): RegistrationResponse 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.local.DataStore
import com.example.shoesapptest.data.remote.network.NetworkResponse import com.example.shoesapptest.data.remote.network.NetworkResponse
import com.example.shoesapptest.data.remote.network.auth.AuthRemoteSource 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.RegistrationRequest
import com.example.shoesapptest.data.remote.network.dto.RegistrationResponse import com.example.shoesapptest.data.remote.network.dto.RegistrationResponse
@ -10,4 +12,8 @@ class AuthRepository(val authRemoteSource: AuthRemoteSource) {
suspend fun registration(registrationRequest: RegistrationRequest): RegistrationResponse { suspend fun registration(registrationRequest: RegistrationRequest): RegistrationResponse {
return authRemoteSource.registration(registrationRequest) 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.data.repository.AuthRepository
import com.example.shoesapptest.domain.usecase.AuthUseCase import com.example.shoesapptest.domain.usecase.AuthUseCase
import com.example.shoesapptest.screen.regscreen.RegistrationViewModel import com.example.shoesapptest.screen.regscreen.RegistrationViewModel
import com.example.shoesapptest.screen.signin.SignInViewModel
import org.koin.core.module.dsl.viewModel import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
@ -15,4 +16,5 @@ val appModules = module {
single <AuthRepository> {AuthRepository(get()) } single <AuthRepository> {AuthRepository(get()) }
single { AuthUseCase(get (),get()) } single { AuthUseCase(get (),get()) }
viewModel { RegistrationViewModel(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.local.DataStore
import com.example.shoesapptest.data.remote.network.NetworkResponse 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.RegistrationRequest
import com.example.shoesapptest.data.remote.network.dto.RegistrationResponse import com.example.shoesapptest.data.remote.network.dto.RegistrationResponse
import com.example.shoesapptest.data.repository.AuthRepository 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")) 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.derivedStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel 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()) var signInState = mutableStateOf(SignInState())
private set private set
val emailHasError = derivedStateOf { 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() !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) signInState.value = signInState.value.copy(email = email)
} }
fun setPassword(password: String) {
fun setPassword(password: String){
signInState.value = signInState.value.copy(password = password) 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.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -41,19 +45,36 @@ import androidx.navigation.NavController
import com.example.shoesapp.ui.theme.MatuleTheme import com.example.shoesapp.ui.theme.MatuleTheme
import com.example.shoesapptest.R import com.example.shoesapptest.R
import com.example.shoesapptest.common.CommonButton 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.AuthButton
import com.example.shoesapptest.screen.signin.component.AuthTextField import com.example.shoesapptest.screen.signin.component.AuthTextField
import com.example.shoesapptest.screen.signin.component.TitleWithSubtitleText import com.example.shoesapptest.screen.signin.component.TitleWithSubtitleText
import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
fun SigninScreen( fun SignInScreen(
onNavigationToRegScreen: () -> Unit, onNavigationToRegScreen: () -> Unit,
onSignInSuccess: () -> Unit,
navController: NavController 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( Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = { topBar = {
Row( Row(
modifier = Modifier modifier = Modifier
@ -92,17 +113,24 @@ fun SigninScreen(
) { paddingValues -> ) { paddingValues ->
SignInContent( SignInContent(
paddingValues = paddingValues, paddingValues = paddingValues,
signInViewMode = signInViewModel, viewModel = viewModel,
navController = navController navController = navController
) )
} }
} }
@Composable @Composable
fun SignInContent(paddingValues: PaddingValues, signInViewMode: SignInViewMode, navController: NavController) { fun SignInContent(
val signInState = signInViewMode.signInState paddingValues: PaddingValues,
viewModel: SignInViewModel,
navController: NavController
) {
val state = viewModel.signInState.value
Column( Column(
modifier = Modifier.padding(paddingValues = paddingValues), modifier = Modifier
.padding(paddingValues)
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
TitleWithSubtitleText( TitleWithSubtitleText(
@ -112,24 +140,23 @@ fun SignInContent(paddingValues: PaddingValues, signInViewMode: SignInViewMode,
Spacer(modifier = Modifier.height(35.dp)) Spacer(modifier = Modifier.height(35.dp))
AuthTextField( AuthTextField(
value = signInState.value.email, value = state.email,
onChangeValue = { signInViewMode.setEmail(it) }, onChangeValue = { viewModel.setEmail(it) },
isError = signInViewMode.emailHasError.value, isError = viewModel.emailHasError.value,
supportingText = { Text(text = stringResource(R.string.LoginError))}, supportingText = { Text(text = stringResource(R.string.LoginError))},
placeholder = { Text(text = stringResource(R.string.template_email)) }, placeholder = { Text(text = stringResource(R.string.template_email)) },
label = { Text(text = stringResource(R.string.email)) } label = { Text(text = stringResource(R.string.email)) }
) )
AuthTextField( AuthTextField(
value = signInState.value.password, value = state.password,
onChangeValue = { signInViewMode.setPassword(it) }, onChangeValue = { viewModel.setPassword(it) },
isError = false, isError = false,
supportingText = { Text(text = "Неверный пароль")}, supportingText = { Text(text = "Неверный пароль")},
placeholder = { Text(text = stringResource(R.string.PasswordPlaceHolder)) }, placeholder = { Text(text = stringResource(R.string.PasswordPlaceHolder)) },
label = { Text(text = stringResource(R.string.Password)) } label = { Text(text = stringResource(R.string.Password)) }
) )
Text( Text(
text = "Забыл пароль", text = "Забыл пароль",
style = MatuleTheme.typography.bodyRegular16.copy(color = MatuleTheme.colors.text), style = MatuleTheme.typography.bodyRegular16.copy(color = MatuleTheme.colors.text),
@ -140,10 +167,19 @@ fun SignInContent(paddingValues: PaddingValues, signInViewMode: SignInViewMode,
.padding(top = 8.dp) .padding(top = 8.dp)
) )
AuthButton(onClick = {}) { 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)) 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( fun AuthButton(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
enabled: Boolean = true,
content: @Composable () -> Unit content: @Composable () -> Unit
){ ) {
CommonButton( CommonButton(
onClick = onClick, onClick = onClick,
modifier = modifier.padding(50.dp),
enabled = enabled,
buttonColors = ButtonColors( buttonColors = ButtonColors(
contentColor = MatuleTheme.colors.background, contentColor = MatuleTheme.colors.background,
containerColor = MatuleTheme.colors.accent, containerColor = MatuleTheme.colors.accent,
disabledContainerColor = MatuleTheme.colors.accent, disabledContainerColor = MatuleTheme.colors.accent,
disabledContentColor = MatuleTheme.colors.accent disabledContentColor = MatuleTheme.colors.accent
), )
modifier = modifier.padding(50.dp) ) {
) { content()} content()
}
} }