This commit is contained in:
IsaykinEugene 2025-03-25 15:34:44 +03:00
parent 26e0b74bf3
commit 5c6f4c7815
48 changed files with 533 additions and 512 deletions

View File

@ -42,6 +42,11 @@ android {
dependencies {
implementation(libs.androidx.core.ktx)
implementation("androidx.datastore:datastore-preferences:1.1.3")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-kotlinx-serialization:2.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
@ -49,7 +54,6 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
@ -57,12 +61,37 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
implementation("androidx.navigation:navigation-compose:2.8.9")
implementation("androidx.datastore:datastore-preferences:1.1.3")
implementation("androidx.navigation:navigation-compose:2.8.9")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
implementation("androidx.navigation:navigation-compose:2.7.0")
implementation("com.squareup.retrofit2:converter-kotlinx-serialization:2.11.0")
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
implementation("com.squareup.okhttp3:okhttp:4.7.2")
implementation("io.insert-koin:koin-compose:4.1.0-Beta5")
implementation("io.insert-koin:koin-compose-viewmodel:4.1.0-Beta5")
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}

View File

@ -1,19 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".MainApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ShoesappTest"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name=".ui.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.ShoesappTest">

View File

@ -1,19 +0,0 @@
package com.example.shoesapptest
import com.example.shoesapptest.data.model.RegistrationRequest
import com.example.shoesapptest.data.model.TokenResponse
import com.example.shoesapptest.data.remote.AuthRemoteSource
import com.example.shoesapptest.data.remote.retrofit.Auth
import com.example.shoesapptest.data.remote.retrofit.RetrofitClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class AuthRemoteSourceImpl :AuthRemoteSource {
private val api: Auth = RetrofitClient.retrofit
override suspend fun registration(registrationRequest: RegistrationRequest): TokenResponse {
return withContext(Dispatchers.IO) {
api.registration(registrationRequest)
}
}
}

View File

@ -4,61 +4,59 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.lifecycle.ViewModelProvider
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.theme.MatuleTheme
import com.example.shoesapptest.AuthRemoteSourceImpl
import com.example.shoesapptest.RegistrationViewModelFactory
import com.example.shoesapptest.data.local.datastore.LocalDataStore
import com.example.shoesapptest.data.repository.AuthRepositoryImpl
import com.example.shoesapptest.screen.RegistrationScreen
import com.example.shoesapptest.screen.TestScreen
import com.example.shoesapptest.screen.registrationscreen.RegistrationViewModel
import com.example.shoesapptest.Screen
import com.example.shoesapptest.data.local.DataStore
import com.example.shoesapptest.data.remote.RetrofitClient
import com.example.shoesapptest.data.AuthRepository
import com.example.shoesapptest.domain.usecase.AuthUseCase
import com.example.shoesapptest.screen.ForgotPasswordScreen
import com.example.shoesapptest.screen.regscreen.RegistrationScreen
import com.example.shoesapptest.ui.screen.registrationscreen.Registration
import com.example.shoesapptest.ui.screen.registrationscreen.RegistrationViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
class MainActivity : ComponentActivity() {
private lateinit var authLocalStore: LocalDataStore
private lateinit var authRemoteSource: AuthRemoteSourceImpl
private lateinit var authRepository: AuthRepositoryImpl
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
// Инициализация зависимостей
authLocalStore = LocalDataStore(applicationContext)
authRemoteSource = AuthRemoteSourceImpl()
authRepository = AuthRepositoryImpl(authLocalStore, authRemoteSource)
val registrationViewModel: RegistrationViewModel by viewModel()
setContent {
MatuleTheme {
val navController = rememberNavController()
MatuleTheme {
NavHost(
navController = navController,
startDestination = "register"
startDestination = "signin"
) {
composable("register") {
// Создание и передача ViewModel
val registrationViewModel: RegistrationViewModel = ViewModelProvider(
this@MainActivity,
RegistrationViewModelFactory(authRepository)
).get(RegistrationViewModel::class.java)
RegistrationScreen(
navController = navController,
registrationViewModel = registrationViewModel
composable(Screen.SignIn.route) {
SigninScreen(
onNavigationToRegScreen = {
navController.navigate("registration")
},
navController = navController
)
}
composable("test") {
TestScreen()
composable(Screen.ForgotPass.route) {
ForgotPasswordScreen(onNavigateToSignInScreen = {
navController.navigate("signin")
})
}
composable(Screen.Registration.route) {
RegistrationScreen(
onNavigationToSigninScreen = {
navController.popBackStack()
}
)
}
}
}
}
}
}

View File

@ -0,0 +1,16 @@
package com.example.shoesapptest
import android.app.Application
import com.example.shoesapptest.di.appModules
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
class MainApplication: Application() {
override fun onCreate(){
super.onCreate()
startKoin{
androidContext(applicationContext)
modules(appModules)
}
}
}

View File

@ -1,18 +0,0 @@
package com.example.shoesapptest
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.shoesapptest.data.repository.AuthRepository
import com.example.shoesapptest.screen.registrationscreen.RegistrationViewModel
class RegistrationViewModelFactory(
private val authRepository: AuthRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(RegistrationViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return RegistrationViewModel(authRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@ -0,0 +1,7 @@
package com.example.shoesapptest
sealed class Screen (val route: String) {
object SignIn : Screen("signin")
object ForgotPass : Screen("forgotpass")
object Registration : Screen("registration")
}

View File

@ -0,0 +1,13 @@
package com.example.shoesapptest.data
import com.example.shoesapptest.data.remote.auth.AuthApi
import com.example.shoesapptest.data.remote.dto.request.RegistrationRequest
import com.example.shoesapptest.data.remote.dto.response.RegistrationResponse
import kotlinx.coroutines.delay
class AuthRepository(private val api: AuthApi) {
suspend fun registration(registrationRequest: RegistrationRequest): RegistrationResponse {
delay(3000)
return api.registration(registrationRequest)
}
}

View File

@ -1,9 +0,0 @@
package com.example.shoesapptest.data.local
import com.example.shoesapptest.data.model.SaveTokenRequest
import com.example.shoesapptest.data.model.TokenResponse
interface AuthLocalStore {
suspend fun getToken(): TokenResponse
suspend fun setToken(saveTokenRequest: SaveTokenRequest): Boolean
}

View File

@ -0,0 +1,23 @@
package com.example.shoesapptest.data.local
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
class DataStore(private val context: Context) {
val TOKEN_KEY = stringPreferencesKey("token_key")
val tokenFlow : Flow<String> = context.dataStore.data.map { pref ->
pref[TOKEN_KEY] ?: ""
}
suspend fun setToken(token: String){
context.dataStore.edit { pref ->
pref[TOKEN_KEY] = token
}
}
}

View File

@ -1,30 +0,0 @@
package com.example.shoesapptest.data.local.datastore
import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.example.shoesapptest.data.local.AuthLocalStore
import com.example.shoesapptest.data.model.SaveTokenRequest
import com.example.shoesapptest.data.model.TokenResponse
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
private val Context.dataStore by preferencesDataStore(name="auth")
class LocalDataStore(context: Context): AuthLocalStore {
private val dataStore = context.dataStore
override suspend fun getToken(): TokenResponse {
val token = dataStore.data.map { preferences ->
preferences[stringPreferencesKey("token")] ?: ""
}.first()
return TokenResponse("token")
}
override suspend fun setToken(saveTokenRequest: SaveTokenRequest): Boolean {
dataStore.edit { prefences -> prefences[stringPreferencesKey("token")] = saveTokenRequest.tokenAuthentication }
return true
}
}

View File

@ -1,7 +0,0 @@
package com.example.shoesapptest.data.model
data class RegistrationRequest(
val userName: String,
val email: String,
val password: String
)

View File

@ -1,5 +0,0 @@
package com.example.shoesapptest.data.model
data class SaveTokenRequest(
val tokenAuthentication: String
)

View File

@ -1,5 +0,0 @@
package com.example.shoesapptest.data.model
data class TokenResponse(
val tokenAuthentication: String
)

View File

@ -1,8 +0,0 @@
package com.example.shoesapptest.data.remote
import com.example.shoesapptest.data.model.RegistrationRequest
import com.example.shoesapptest.data.model.TokenResponse
interface AuthRemoteSource {
suspend fun registration(registrationRequest: RegistrationRequest):TokenResponse
}

View File

@ -0,0 +1,7 @@
package com.example.shoesapptest.data.remote
sealed class NetworkResponse {
data class Success<T>(val data: T): NetworkResponse()
data object Loading:NetworkResponse()
data class Error(val errorMessage: String): NetworkResponse()
}

View File

@ -0,0 +1,22 @@
package com.example.shoesapptest.data.remote
import com.example.shoesapptest.data.remote.auth.AuthApi
import com.example.shoesapptest.data.remote.auth.AuthRemoteSource
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory
object RetrofitClient {
private const val URL = "http://26.197.138.194:8080"
private val retrofit = Retrofit.Builder()
.baseUrl(URL)
.addConverterFactory(
Json.asConverterFactory(
"application/json; charset=UTF8".toMediaType()))
.build()
val auth by lazy {
retrofit.create(AuthApi::class.java)
}
}

View File

@ -0,0 +1,11 @@
package com.example.shoesapptest.data.remote.auth
import com.example.shoesapptest.data.remote.dto.request.RegistrationRequest
import com.example.shoesapptest.data.remote.dto.response.RegistrationResponse
import retrofit2.http.Body
import retrofit2.http.POST
interface AuthApi {
@POST("/regitstration")
suspend fun registration(@Body registrationRequest: RegistrationRequest): RegistrationResponse
}

View File

@ -0,0 +1,11 @@
package com.example.shoesapptest.data.remote.auth
import com.example.shoesapptest.data.remote.dto.request.RegistrationRequest
import com.example.shoesapptest.data.remote.dto.response.RegistrationResponse
import retrofit2.http.Body
import retrofit2.http.POST
interface AuthRemoteSource {
@POST("/registration")
suspend fun registration(@Body registrationRequest: RegistrationRequest): RegistrationResponse
}

View File

@ -0,0 +1,10 @@
package com.example.shoesapptest.data.remote.dto.request
import kotlinx.serialization.Serializable
@Serializable
data class RegistrationRequest(
val userName: String,
val email: String,
val password: String
)

View File

@ -0,0 +1,9 @@
package com.example.shoesapptest.data.remote.dto.response
import kotlinx.serialization.Serializable
@Serializable
data class RegistrationResponse (
val first: String,
val second: String
)

View File

@ -1,12 +0,0 @@
package com.example.shoesapptest.data.remote.retrofit
import com.example.shoesapptest.data.model.RegistrationRequest
import com.example.shoesapptest.data.model.TokenResponse
import com.example.shoesapptest.data.remote.AuthRemoteSource
import retrofit2.http.Body
import retrofit2.http.POST
interface Auth:AuthRemoteSource {
@POST("/registration")
override suspend fun registration(@Body registrationRequest: RegistrationRequest): TokenResponse
}

View File

@ -1,15 +0,0 @@
package com.example.shoesapptest.data.remote.retrofit
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
private const val URL = "http://172.18.112.1:8080"
object RetrofitClient {
private val retrofitBuilder = Retrofit.Builder()
.baseUrl(URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val retrofit by lazy {
retrofitBuilder.create(Auth::class.java)
}
}

View File

@ -1,10 +0,0 @@
package com.example.shoesapptest.data.repository
import com.example.shoesapptest.data.model.RegistrationRequest
import com.example.shoesapptest.data.model.TokenResponse
interface AuthRepository {
suspend fun registration(registrationRequest: RegistrationRequest): TokenResponse
suspend fun login()
}

View File

@ -1,23 +0,0 @@
package com.example.shoesapptest.data.repository
import com.example.shoesapptest.data.local.AuthLocalStore
import com.example.shoesapptest.data.model.RegistrationRequest
import com.example.shoesapptest.data.model.SaveTokenRequest
import com.example.shoesapptest.data.model.TokenResponse
import com.example.shoesapptest.data.remote.AuthRemoteSource
class AuthRepositoryImpl(
private val authLocalStore: AuthLocalStore,
private val authRemoteSource: AuthRemoteSource
):AuthRepository {
override suspend fun registration(registrationRequest: RegistrationRequest): TokenResponse {
val tokenResponse = authRemoteSource.registration(registrationRequest)
authLocalStore.setToken(SaveTokenRequest(tokenResponse.tokenAuthentication))
return tokenResponse
}
override suspend fun login() {
}
}

View File

@ -0,0 +1,18 @@
package com.example.shoesapptest.di
import com.example.shoesapptest.data.AuthRepository
import com.example.shoesapptest.data.local.DataStore
import com.example.shoesapptest.data.remote.RetrofitClient
import com.example.shoesapptest.domain.usecase.AuthUseCase
import com.example.shoesapptest.screen.regscreen.RegistrationScreen
import com.example.shoesapptest.ui.screen.registrationscreen.RegistrationViewModel
import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module
val appModules = module {
single { DataStore(get()) }
single { RetrofitClient.auth }
single<AuthRepository> { AuthRepository(get()) }
single { AuthUseCase(get(),get()) }
viewModel { RegistrationViewModel(get())}
}

View File

@ -0,0 +1,28 @@
package com.example.shoesapptest.domain.usecase
import com.example.shoesapptest.data.AuthRepository
import com.example.shoesapptest.data.local.DataStore
import com.example.shoesapptest.data.remote.NetworkResponse
import com.example.shoesapptest.data.remote.dto.request.RegistrationRequest
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class AuthUseCase (private val localStorage: DataStore,
private val authRepository: AuthRepository) {
val token: Flow<String> = localStorage.tokenFlow
suspend fun registration(registrationRequest: RegistrationRequest): Flow<NetworkResponse> = flow{
try{
emit(NetworkResponse.Loading)
val result = authRepository.registration(registrationRequest)
localStorage.setToken(result.second)
emit(NetworkResponse.Success(result))
}
catch (e:Exception){
e.message?.let{
emit(NetworkResponse.Error(it))
return@flow
}
emit(NetworkResponse.Error("Unknown Error"))
}
}
}

View File

@ -1,18 +0,0 @@
package com.example.shoesapptest.screen
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun TestScreen() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = "Добро пожаловать!")
}
}

View File

@ -1,13 +0,0 @@
package com.example.shoesapptest.screen.registrationscreen
import android.provider.ContactsContract.CommonDataKinds.Email
import com.example.shoesapptest.screen.forgotscreen.ChangePass
import java.lang.Error
data class Registration(
var email: String = "",
var password: String = "",
var name: String = "",
var isVisiblePassword: Boolean = false,
var errorMessage: String? = null
)

View File

@ -1,197 +0,0 @@
package com.example.shoesapptest.screen
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.example.shoesapptest.R
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
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.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.navigation.NavController
import com.example.shoesapp.ui.theme.MatuleTheme
import com.example.shoesapptest.screen.forgotscreen.component.TitleWithSubtitleText
import com.example.shoesapptest.screen.registrationscreen.RegistrationViewModel
import com.example.shoesapptest.screen.registrationscreen.component.RegistrationBtn
import com.example.shoesapptest.screen.registrationscreen.component.RegistrationTextField
@Composable
fun RegistrationScreen(
navController: NavController,
registrationViewModel: RegistrationViewModel
){
Scaffold(
topBar = {
Row(
modifier = Modifier
.padding(top = 35.dp)
.fillMaxWidth()
.height(40.dp)
){
IconButton(onClick={}) {
Icon(painter = painterResource(R.drawable.back_arrow),
contentDescription = null)
}
}
},
bottomBar = {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(bottom = 50.dp)
.fillMaxWidth()
.height(40.dp)
) {
Text(
text= stringResource(R.string.sign_in_on_reg),
style=MatuleTheme.typography.bodyRegular16.copy(MatuleTheme.colors.text),
textAlign = TextAlign.Center
)
}
}
) { paddingValues ->
RegistrationScreen(
paddingValues=paddingValues,
registrationViewModel=registrationViewModel,
onRegistrationSuccess = {navController.navigate("test")},
onRegistrationError = {errorMessage -> }
)
}
}
@Composable
fun RegistrationScreen(paddingValues: PaddingValues,
registrationViewModel: RegistrationViewModel,
onRegistrationSuccess: () -> Unit,
onRegistrationError: (String) -> Unit){
val register = registrationViewModel.registration
Column(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
) {
TitleWithSubtitleText(
title = "Регистрация",
subTitle = "Заполните Свои данные или продолжите через социальные медиа"
)
Spacer(modifier = Modifier.height(20.dp))
RegistrationTextField(
value = register.value.name,
onChangeValue = { registrationViewModel.setUserName(it) },
isError = false,
supportingText = { Text(text = stringResource(R.string.wrong_name)) },
placeholder = { Text(text = stringResource(R.string.template_name)) },
label = { Text(text = stringResource(R.string.name)) }
)
RegistrationTextField(
value = register.value.email,
onChangeValue = { registrationViewModel.setEmail(it) },
isError = registrationViewModel.emailHasError.value,
supportingText = { Text(text = stringResource(R.string.wrong_email)) },
placeholder = { Text(text = stringResource(R.string.template_email)) },
label = { Text(text = stringResource(R.string.email)) }
)
RegistrationTextField(
value = register.value.password,
onChangeValue = { registrationViewModel.setPassword(it) },
isError = false,
supportingText = { Text(text = stringResource(R.string.wrong_password)) },
placeholder = { Text(text = stringResource(R.string.template_password)) },
label = { Text(text = stringResource(R.string.password)) }
)
SimpleCheckbox()
RegistrationBtn(onClick = {
registrationViewModel.registration(
onSuccess = {
onRegistrationSuccess()
},
onError = { errorMessage ->
onRegistrationError(errorMessage)
}
)
}
) {
Text(text = stringResource(R.string.sign_up_on_reg))
}
}
}
@Composable
fun SimpleCheckbox(){
val isChecked = remember { mutableStateOf(false) }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
.padding(16.dp)
){
Checkbox(
checked = isChecked.value,
onCheckedChange = {isChecked.value = it}
)
ClickableText(
text = AnnotatedString(
"Даю согласие на обработку персональных данных"
),
onClick={},
style = MatuleTheme.typography.bodyRegular16.copy(color = MatuleTheme.colors.text),
modifier = Modifier.padding(start = 8.dp)
)
}
}

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.forgotscreen
package com.example.shoesapptest.ui.screen.forgotscreen
data class ChangePass(
var email: String = "",

View File

@ -38,14 +38,15 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.res.colorResource
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.shoesapptest.screen.forgotscreen.ForgotPasswordViewModel
import com.example.shoesapptest.screen.forgotscreen.component.TitleWithSubtitleText
import com.example.shoesapptest.screen.forgotscreen.component.EmailTextField
import com.example.shoesapptest.screen.forgotscreen.component.SendButton
import androidx.navigation.NavController
import com.example.shoesapptest.ui.screen.forgotscreen.ForgotPasswordViewModel
import com.example.shoesapptest.ui.screen.forgotscreen.component.TitleWithSubtitleText
import com.example.shoesapptest.ui.screen.forgotscreen.component.EmailTextField
import com.example.shoesapptest.ui.screen.forgotscreen.component.SendButton
@Composable
fun ForgotPasswordScreen(){
fun ForgotPasswordScreen(onNavigateToSignInScreen: () -> Unit) {
val forgotPasswordViewModel: ForgotPasswordViewModel = viewModel()
val showDialog = remember { mutableStateOf(false) }
@ -142,25 +143,3 @@ fun CheckEmailDialog(onDismiss: () -> Unit) {
containerColor = Color.White
)
}

View File

@ -1,16 +1,19 @@
package com.example.shoesapptest.screen.forgotscreen
package com.example.shoesapptest.ui.screen.forgotscreen
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class ForgotPasswordViewModel {
class ForgotPasswordViewModel: ViewModel(){
var changePass = mutableStateOf(ChangePass())
private set
val emailHasError = derivedStateOf {
if(changePass.value.email.isEmpty()) return@derivedStateOf false
!android.util.Patterns.EMAIL_ADDRESS.matcher(changePass.value.email).matches()
}
fun setEmail(email: String){
changePass.value = changePass.value.copy(email=email)
changePass.value = changePass.value.copy(email = email)
}
}

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.forgotscreen.component
package com.example.shoesapptest.ui.screen.forgotscreen.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.forgotscreen.component
package com.example.shoesapptest.ui.screen.forgotscreen.component
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ButtonColors

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.forgotscreen.component
package com.example.shoesapptest.ui.screen.forgotscreen.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column

View File

@ -0,0 +1,16 @@
package com.example.shoesapptest.ui.screen.registrationscreen
import android.provider.ContactsContract.CommonDataKinds.Email
import com.example.shoesapptest.ui.screen.forgotscreen.ChangePass
import java.lang.Error
data class Registration(
var email: String = "",
var name: String = "",
var password: String = "",
var isVisiblePassword: Boolean = false,
var errorMessage: String? = null,
var isLoading: Boolean = false,
var isSignedIn: Boolean = false,
var emailHasError: Boolean = false
)

View File

@ -0,0 +1,192 @@
package com.example.shoesapptest.screen.regscreen
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Checkbox
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
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.shoesapp.ui.theme.MatuleTheme
import com.example.shoesapptest.R
import com.example.shoesapptest.ui.screen.registrationscreen.RegistrationViewModel
import com.example.shoesapptest.ui.screen.registrationscreen.component.RegistrationBtn
import com.example.shoesapptest.ui.screen.registrationscreen.component.RegistrationTextField
import com.example.shoesapptest.ui.screen.registrationscreen.component.TextWithSubTitle
import org.koin.compose.viewmodel.koinViewModel
@Composable
fun RegistrationScreen(
onNavigationToSigninScreen: () -> Unit,
viewModel: RegistrationViewModel = koinViewModel()
) {
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
Row(
modifier = Modifier
.padding(top = 35.dp)
.fillMaxWidth()
.height(40.dp)
) {
IconButton(onClick = onNavigationToSigninScreen) {
Icon(
painter = painterResource(R.drawable.back_arrow),
contentDescription = null
)
}
}
},
bottomBar = {
Row(
modifier = Modifier
.padding(bottom = 50.dp)
.fillMaxWidth()
.height(40.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) { onNavigationToSigninScreen() },
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Есть аккаунт? Войти",
style = MatuleTheme.typography.bodyRegular16.copy(color = MatuleTheme.colors.text),
textAlign = TextAlign.Center
)
}
}
) { paddingValues ->
val state = viewModel.registration.value
LaunchedEffect(state.isSignedIn) {
if (state.isSignedIn) {
onNavigationToSigninScreen()
}
}
LaunchedEffect(state.errorMessage) {
state.errorMessage?.let {
snackbarHostState.showSnackbar(it)
viewModel.setErrorMessage(null)
}
}
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxWidth()
.padding(20.dp)
) {
TextWithSubTitle(
title = "Регистрация",
subtitle = "Заполните Свои данные или продолжите через социальные медиа"
)
Spacer(modifier = Modifier.height(20.dp))
RegistrationTextField(
value = state.name,
onChangeValue = { viewModel.setUserName(it) },
isError = false,
supportingText = { Text(text = "Неверное имя пользователя") },
placeholder = { Text(text = stringResource(R.string.template_password)) },
label = { Text(text = "Ваше имя") }
)
RegistrationTextField(
value = state.email,
onChangeValue = { viewModel.setEmail(it) },
isError = state.emailHasError,
supportingText = { Text(text = stringResource(R.string.wrong_email)) },
placeholder = { Text(text = stringResource(R.string.template_email)) },
label = { Text(text = stringResource(R.string.email)) },
)
RegistrationTextField(
value = state.password,
onChangeValue = { viewModel.setPassword(it) },
isError = false,
supportingText = { Text(text = stringResource(R.string.wrong_password)) },
placeholder = { Text(text = stringResource(R.string.template_password)) },
label = { Text(text = stringResource(R.string.password)) }
)
SimpleCheckbox()
RegistrationBtn(onClick = { viewModel.registration { } }) {
if (state.isLoading) {
CircularProgressIndicator()
} else {
Text(text = stringResource(R.string.sign_up_on_reg))
}
}
}
}
}
@Composable
private fun SimpleCheckbox() {
val isChecked = remember { mutableStateOf(false) }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Checkbox(
checked = isChecked.value,
onCheckedChange = { isChecked.value = it }
)
Text(
text = "Даю согласие на обработку персональных данных",
style = MatuleTheme.typography.bodyRegular16.copy(color = MatuleTheme.colors.text),
modifier = Modifier.padding(start = 8.dp)
)
}
}

View File

@ -1,18 +1,18 @@
package com.example.shoesapptest.screen.registrationscreen
package com.example.shoesapptest.ui.screen.registrationscreen
import androidx.compose.animation.core.animateDecay
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.shoesapptest.data.model.RegistrationRequest
import com.example.shoesapptest.data.repository.AuthRepository
import com.example.shoesapptest.data.remote.dto.request.RegistrationRequest
import com.example.shoesapptest.data.AuthRepository
import com.example.shoesapptest.screen.regscreen.RegistrationScreen
import kotlinx.coroutines.launch
import java.lang.Error
class RegistrationViewModel(
private val authRepository: AuthRepository
):ViewModel() {
var registration = mutableStateOf(Registration())
private set
val emailHasError = derivedStateOf {
@ -28,7 +28,10 @@ class RegistrationViewModel(
fun setUserName(name: String){
registration.value = registration.value.copy(name=name)
}
fun registration(onSuccess: () -> Unit, onError: (String) -> Unit){
fun setErrorMessage(message: String?){
registration.value=registration.value.copy(errorMessage = message)
}
fun registration(onSuccess: () -> Unit){
viewModelScope.launch {
try {
val registrationRequest = RegistrationRequest(
@ -36,11 +39,9 @@ class RegistrationViewModel(
email = registration.value.email,
password = registration.value.password
)
authRepository.registration(registrationRequest)
onSuccess()
} catch (e: Exception) {
onError(e.message ?: "Ошибка регистрации")
registration.value = registration.value.copy(errorMessage = "Ошибка регистрации: ${e.message}")
}
}
}

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.registrationscreen.component
package com.example.shoesapptest.ui.screen.registrationscreen.component
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ButtonColors
@ -11,8 +11,8 @@ import com.example.shoesapptest.common.CommonButton
@Composable
fun RegistrationBtn(
onClick: () -> Unit,
content: @Composable () -> Unit
){
modifier: Modifier = Modifier,
content: @Composable () -> Unit){
CommonButton(
modifier = Modifier.padding(50.dp),
onClick = onClick,

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.registrationscreen.component
package com.example.shoesapptest.ui.screen.registrationscreen.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.unit.dp
import androidx.datastore.preferences.protobuf.Internal.BooleanList
import com.example.shoesapptest.common.CommonTextField
import java.lang.Error
@ -21,7 +20,7 @@ fun RegistrationTextField(
placeholder: @Composable () -> Unit,
label: @Composable () -> Unit
){
Column(Modifier.padding(50.dp)
Column(Modifier.padding(horizontal = 20.dp)
.wrapContentSize(),
verticalArrangement = Arrangement.spacedBy(12.dp)){
label()

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.registrationscreen.component
package com.example.shoesapptest.ui.screen.registrationscreen.component
import android.icu.text.CaseMap.Title
import androidx.compose.foundation.layout.Arrangement

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.signin
package com.example.shoesapptest.ui.screen.signin
data class SignInState (
var email: String = "",

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.signin
package com.example.shoesapptest.ui.screen.signin
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf

View File

@ -1,7 +1,7 @@
package com.example.shoesapp.ui.screen
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -15,8 +15,7 @@ import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@ -24,10 +23,8 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
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.draw.clip
@ -40,16 +37,17 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.shoesapp.ui.theme.MatuleTheme
import com.example.shoesapptest.R
import com.example.shoesapptest.screen.signin.SignInViewMode
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 com.example.shoesapptest.common.CommonButton
import com.example.shoesapptest.ui.screen.signin.SignInViewMode
import com.example.shoesapptest.ui.screen.signin.component.AuthButton
import com.example.shoesapptest.ui.screen.signin.component.AuthTextField
import com.example.shoesapptest.ui.screen.signin.component.TitleWithSubtitleText
@Composable
fun SigninScreen() {
fun SigninScreen(onNavigationToRegScreen: () -> Unit, navController: NavController) {
val signInViewModel: SignInViewMode = viewModel()
Scaffold(
topBar = {
@ -79,17 +77,18 @@ fun SigninScreen() {
Text(
stringResource(R.string.sign_up),
style = MatuleTheme.typography.bodyRegular16.copy(color = MatuleTheme.colors.text),
textAlign = TextAlign.Center
textAlign = TextAlign.Center,
modifier = Modifier.clickable { onNavigationToRegScreen() }
)
}
}
) { paddingValues ->
SignInContent(paddingValues, signInViewModel)
SignInContent(paddingValues, signInViewModel, navController)
}
}
@Composable
fun SignInContent(paddingValues: PaddingValues, signInViewMode: SignInViewMode) {
fun SignInContent(paddingValues: PaddingValues, signInViewMode: SignInViewMode, navController: NavController) {
val signInState = signInViewMode.signInState
Column(
modifier = Modifier.padding(paddingValues = paddingValues),
@ -101,28 +100,45 @@ 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,
supportingText = { Text(text = stringResource(R.string.wrong_email))},
placeholder = { Text(text = stringResource(R.string.template_email)) },
label = { Text(text = stringResource(R.string.email))
}
label = { Text(text = stringResource(R.string.email)) }
)
AuthTextField(
value = signInState.value.password,
onChangeValue = { signInViewMode.setPassword(it)},
onChangeValue = { signInViewMode.setPassword(it) },
isError = false,
supportingText = { Text(text = "Неверный пароль")},
placeholder = { Text(text = stringResource(R.string.template_password))},
label = { Text(text = stringResource(R.string.password))}
placeholder = { Text(text = stringResource(R.string.template_password)) },
label = { Text(text = stringResource(R.string.password)) }
)
Text(
text = "Забыл пароль",
style = MatuleTheme.typography.bodyRegular16.copy(color = MatuleTheme.colors.text),
modifier = Modifier
.clickable {
navController.navigate("forgotpass")
}
.padding(top = 8.dp)
)
AuthButton(onClick = {}) {
Text(stringResource(R.string.sign_in))
}
}
}

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.signin.component
package com.example.shoesapptest.ui.screen.signin.component
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ButtonColors

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.signin.component
package com.example.shoesapptest.ui.screen.signin.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column

View File

@ -1,4 +1,4 @@
package com.example.shoesapptest.screen.signin.component
package com.example.shoesapptest.ui.screen.signin.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column