diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index ba3a483..744370f 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -14,6 +14,9 @@ + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 36a4c12..33f6faa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) + kotlin("plugin.serialization") version "2.0.21" } android { @@ -45,6 +46,12 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) + implementation(libs.retrofit) + implementation (libs.converter.kotlinx.serialization) + implementation(libs.kotlinx.serialization.json) + implementation (libs.androidx.navigation.compose) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.ui) implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f4bc553..d1d611f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - + { RegistrationScreen(onNavigateToAuthorization = { + navController.navigate(route = Authorization) + }) } + composable { AuthorizeScreen({ userId -> + navController.navigate(route = Clicker(userId = userId)) + }) } + composable{ navBackStackEntry -> + val clicker: Clicker = navBackStackEntry.toRoute() + ClickerScreen(clicker.userId, {navController.navigate(route = Score)}) + } + composable { ScoreScreen({navController.navigate(route = Clicker)}) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/appforkids/data/AppForKidsApi.kt b/app/src/main/java/com/example/appforkids/data/AppForKidsApi.kt new file mode 100644 index 0000000..c7e4698 --- /dev/null +++ b/app/src/main/java/com/example/appforkids/data/AppForKidsApi.kt @@ -0,0 +1,24 @@ +package com.example.appforkids.data + +import com.example.appforkids.data.Models.Requests.AuthorizeUserRequest +import com.example.appforkids.data.Models.Requests.RegisterUserRequest +import com.example.appforkids.data.Models.Response.UserResponse +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Path + +interface AppForKidsApi { + @GET("/user") + suspend fun getUsers():List + + @PUT("/user/{user_id}/clicks") + suspend fun registerClick(@Path("user_id") idUser:Int) + + @POST("/authorize/auth") + suspend fun authorizeUser(@Body authorizeUserRequest: AuthorizeUserRequest):UserResponse? + + @POST("/authorize/register") + suspend fun registerUser(@Body registerUserRequest: RegisterUserRequest):UserResponse? +} \ No newline at end of file diff --git a/app/src/main/java/com/example/appforkids/data/Models/Requests/AuthorizeUserRequest.kt b/app/src/main/java/com/example/appforkids/data/Models/Requests/AuthorizeUserRequest.kt new file mode 100644 index 0000000..2a2c3cb --- /dev/null +++ b/app/src/main/java/com/example/appforkids/data/Models/Requests/AuthorizeUserRequest.kt @@ -0,0 +1,6 @@ +package com.example.appforkids.data.Models.Requests + +import kotlinx.serialization.Serializable + +@Serializable +data class AuthorizeUserRequest(val login:String, val password: String) diff --git a/app/src/main/java/com/example/appforkids/data/Models/Requests/RegisterUserRequest.kt b/app/src/main/java/com/example/appforkids/data/Models/Requests/RegisterUserRequest.kt new file mode 100644 index 0000000..2406e6c --- /dev/null +++ b/app/src/main/java/com/example/appforkids/data/Models/Requests/RegisterUserRequest.kt @@ -0,0 +1,6 @@ +package com.example.appforkids.data.Models.Requests + +import kotlinx.serialization.Serializable + +@Serializable +data class RegisterUserRequest(val login:String, val password:String, var url:String?=null) \ No newline at end of file diff --git a/app/src/main/java/com/example/appforkids/data/Models/Response/UserResponse.kt b/app/src/main/java/com/example/appforkids/data/Models/Response/UserResponse.kt new file mode 100644 index 0000000..9c5d125 --- /dev/null +++ b/app/src/main/java/com/example/appforkids/data/Models/Response/UserResponse.kt @@ -0,0 +1,11 @@ +package com.example.appforkids.data.Models.Response + +import kotlinx.serialization.Serializable + +@Serializable +data class UserResponse( + val id:Int, + val nickname: String, + val password: String, + val clicks:Int, + var url:String? = null ) diff --git a/app/src/main/java/com/example/appforkids/data/Retrofit.kt b/app/src/main/java/com/example/appforkids/data/Retrofit.kt new file mode 100644 index 0000000..69ad2ed --- /dev/null +++ b/app/src/main/java/com/example/appforkids/data/Retrofit.kt @@ -0,0 +1,19 @@ +package com.example.appforkids.data + +import kotlinx.serialization.json.Json +import okhttp3.MediaType +import retrofit2.Retrofit +import retrofit2.converter.kotlinx.serialization.asConverterFactory + +object HttpClient { + const val URL = "http://192.168.0.108:8080" + private val retrofit: Retrofit by lazy { + Retrofit.Builder() + .baseUrl(URL) + .addConverterFactory(Json.asConverterFactory(MediaType.get("application/json; charset=UTF8"))) + .build() + } + val retrofitInstance by lazy { + retrofit.create(AppForKidsApi::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/appforkids/screen/authorize/AuthorizeScreen.kt b/app/src/main/java/com/example/appforkids/screen/authorize/AuthorizeScreen.kt index 13dd4a0..3433757 100644 --- a/app/src/main/java/com/example/appforkids/screen/authorize/AuthorizeScreen.kt +++ b/app/src/main/java/com/example/appforkids/screen/authorize/AuthorizeScreen.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -15,33 +17,43 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.appforkids.data.Models.Response.UserResponse import com.example.appforkids.screen.common.CommonButton import com.example.appforkids.screen.common.CommonTextFieldWithLabel +import kotlinx.coroutines.flow.asStateFlow @Preview @Composable -fun AuthorizeScreen() { +fun AuthorizeScreen(onNavigateToClickerScreen: (Int) -> Unit) { + val authorizeViewModel:AuthorizeViewModel = viewModel() Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxSize().padding(5.dp), + modifier = Modifier + .fillMaxSize() + .padding(5.dp), ) { - var login by remember { mutableStateOf("") } + if(authorizeViewModel.isSignIn) { + onNavigateToClickerScreen(authorizeViewModel.userId) + authorizeViewModel.isSignIn = false; + } CommonTextFieldWithLabel( - value = login, - onValueChange = { login = it}, + value = authorizeViewModel.login, + onValueChange = { authorizeViewModel.login = it}, label = "Введите никнейм" ) - var password by remember { mutableStateOf("") } CommonTextFieldWithLabel( - value = password, - onValueChange = { password = it}, + value = authorizeViewModel.password, + onValueChange = {authorizeViewModel.password = it}, label = "Введите пароль" ) CommonButton( - text = "Зарегистрироваться", - onClick = {} + text = "Войти", + onClick = { + authorizeViewModel.authorize() + } ) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/appforkids/screen/authorize/AuthorizeViewModel.kt b/app/src/main/java/com/example/appforkids/screen/authorize/AuthorizeViewModel.kt new file mode 100644 index 0000000..1fca3c9 --- /dev/null +++ b/app/src/main/java/com/example/appforkids/screen/authorize/AuthorizeViewModel.kt @@ -0,0 +1,31 @@ +package com.example.appforkids.screen.authorize + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.viewModelFactory +import com.example.appforkids.data.HttpClient +import com.example.appforkids.data.Models.Requests.AuthorizeUserRequest +import com.example.appforkids.data.Models.Response.UserResponse +import kotlinx.coroutines.launch + +class AuthorizeViewModel(): ViewModel() { + var login by mutableStateOf("") + var password by mutableStateOf("") + var isSignIn by mutableStateOf(false) + var userId by mutableIntStateOf(-1) + fun authorize(){ + val authorizeUserRequest = AuthorizeUserRequest(login = login, password = password) + viewModelScope.launch { + var user = HttpClient.retrofitInstance.authorizeUser(authorizeUserRequest) + if (user != null) { + isSignIn = true + userId = user.id + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/appforkids/screen/common/CommonComponents.kt b/app/src/main/java/com/example/appforkids/screen/common/CommonComponents.kt index 12be35e..310549f 100644 --- a/app/src/main/java/com/example/appforkids/screen/common/CommonComponents.kt +++ b/app/src/main/java/com/example/appforkids/screen/common/CommonComponents.kt @@ -12,13 +12,15 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -@Preview @Composable fun CommonButton(onClick: () -> Unit, text: String){ Button(onClick = onClick, modifier = Modifier - .fillMaxWidth() - .height(40.dp)) { + .padding(vertical = 10.dp) + .fillMaxWidth() + .height(40.dp) + + ) { Text(text = text) } } diff --git a/app/src/main/java/com/example/appforkids/screen/main/ClickerScreen.kt b/app/src/main/java/com/example/appforkids/screen/main/ClickerScreen.kt index 1437339..eb82b5a 100644 --- a/app/src/main/java/com/example/appforkids/screen/main/ClickerScreen.kt +++ b/app/src/main/java/com/example/appforkids/screen/main/ClickerScreen.kt @@ -24,9 +24,11 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import com.example.appforkids.R +import com.example.appforkids.data.Models.Response.UserResponse +import com.example.appforkids.screen.common.CommonButton @Composable -fun ClickerScreen(){ +fun ClickerScreen(userId: Int, OnRouteToRating: () -> Unit){ var countClick by remember { mutableIntStateOf(0) } Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -43,5 +45,6 @@ fun ClickerScreen(){ .border(border = BorderStroke(1.dp, Color.Gray)) ) Text(text = countClick.toString()) + CommonButton(onClick = {OnRouteToRating()}, text = "Открыть рейтинг") } } \ No newline at end of file diff --git a/app/src/main/java/com/example/appforkids/screen/reigistration/RegistrationScreen.kt b/app/src/main/java/com/example/appforkids/screen/reigistration/RegistrationScreen.kt index 1af410b..a5cfb2c 100644 --- a/app/src/main/java/com/example/appforkids/screen/reigistration/RegistrationScreen.kt +++ b/app/src/main/java/com/example/appforkids/screen/reigistration/RegistrationScreen.kt @@ -4,44 +4,51 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState 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.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import com.example.appforkids.screen.common.CommonButton import com.example.appforkids.screen.common.CommonTextFieldWithLabel @Preview @Composable -fun RegistrationScreen() { +fun RegistrationScreen(onNavigateToAuthorization: () -> Unit) { + val registrationViewModel: RegistrationViewModel = viewModel(factory = RegistrationViewModel.Factory) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize().padding(5.dp), ) { - var login by remember { mutableStateOf("") } + val registerState by registrationViewModel.registrationState.collectAsState() + CommonTextFieldWithLabel( - value = login, - onValueChange = { login = it}, + value = registerState.login, + onValueChange = { registrationViewModel.setLogin(it)}, label = "Введите никнейм" ) - var password by remember { mutableStateOf("") } CommonTextFieldWithLabel( - value = password, - onValueChange = { password = it}, + value = registerState.password, + onValueChange = {registrationViewModel.setPassword(it)}, label = "Введите пароль" ) CommonButton( - text = "Войти", - onClick = {} + text = "Зарегестрироваться", + onClick = { + registrationViewModel.registerUser() + onNavigateToAuthorization() + } + ) + CommonButton( + text = "Авторизоваться", + onClick = { + onNavigateToAuthorization() + } ) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/appforkids/screen/reigistration/RegistrationState.kt b/app/src/main/java/com/example/appforkids/screen/reigistration/RegistrationState.kt new file mode 100644 index 0000000..33dfab3 --- /dev/null +++ b/app/src/main/java/com/example/appforkids/screen/reigistration/RegistrationState.kt @@ -0,0 +1,3 @@ +package com.example.appforkids.screen.reigistration + +data class RegistrationState(var login: String = "", var password: String = "") diff --git a/app/src/main/java/com/example/appforkids/screen/reigistration/RegistrationViewModel.kt b/app/src/main/java/com/example/appforkids/screen/reigistration/RegistrationViewModel.kt new file mode 100644 index 0000000..3768261 --- /dev/null +++ b/app/src/main/java/com/example/appforkids/screen/reigistration/RegistrationViewModel.kt @@ -0,0 +1,42 @@ +package com.example.appforkids.screen.reigistration + +import androidx.lifecycle.AbstractSavedStateViewModelFactory +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.example.appforkids.data.AppForKidsApi +import com.example.appforkids.data.HttpClient +import com.example.appforkids.data.Models.Requests.RegisterUserRequest +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch + +class RegistrationViewModel(val appForKidsApi: AppForKidsApi):ViewModel() { + val registrationState = MutableStateFlow(RegistrationState()) + + fun registerUser(){ + val userRequest = RegisterUserRequest(login = registrationState.value.login, password = registrationState.value.password) + + viewModelScope.launch { + appForKidsApi.registerUser(userRequest) + } + } + fun setLogin(login: String){ + viewModelScope.launch { + registrationState.emit(registrationState.value.copy(login = login)) + } + } + fun setPassword(password: String){ + viewModelScope.launch { + registrationState.emit(registrationState.value.copy(password = password)) + } + } + companion object { + val Factory:ViewModelProvider.Factory = viewModelFactory { + initializer { + RegistrationViewModel(HttpClient.retrofitInstance) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/appforkids/screen/score/ScoreScreen.kt b/app/src/main/java/com/example/appforkids/screen/score/ScoreScreen.kt new file mode 100644 index 0000000..601a984 --- /dev/null +++ b/app/src/main/java/com/example/appforkids/screen/score/ScoreScreen.kt @@ -0,0 +1,40 @@ +package com.example.appforkids.screen.score + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.appforkids.screen.common.CommonButton +import com.example.appforkids.screen.common.CommonTextFieldWithLabel + +@Composable +fun ScoreScreen(onNavigateToClickerScreen: () -> Unit) { + val registrationViewModel: ScoreViewModel = viewModel() + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxSize().padding(5.dp), + + ) { + val registerState by registrationViewModel.registrationState.collectAsState() + + registerState.forEach { + Text(text = "${it.nickname} ${it.clicks}") + } + CommonButton( + text = "Вернуться", + onClick = { + onNavigateToClickerScreen() + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/appforkids/screen/score/ScoreViewModel.kt b/app/src/main/java/com/example/appforkids/screen/score/ScoreViewModel.kt new file mode 100644 index 0000000..76ca6cc --- /dev/null +++ b/app/src/main/java/com/example/appforkids/screen/score/ScoreViewModel.kt @@ -0,0 +1,24 @@ +package com.example.appforkids.screen.score + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.example.appforkids.data.AppForKidsApi +import com.example.appforkids.data.HttpClient +import com.example.appforkids.data.Models.Requests.RegisterUserRequest +import com.example.appforkids.data.Models.Response.UserResponse +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import okhttp3.Response + +class ScoreViewModel():ViewModel() { + val registrationState = MutableStateFlow>(emptyList()) + + init { + viewModelScope.launch { + registrationState.emit(HttpClient.retrofitInstance.getUsers()) + } + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a5581cd..8ce8d49 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,25 @@ [versions] agp = "8.7.2" +converterGson = "2.11.0" +converterKotlinxSerialization = "2.11.0" kotlin = "2.0.0" coreKtx = "1.10.1" junit = "4.13.2" junitVersion = "1.1.5" espressoCore = "3.5.1" +kotlinxSerializationJson = "1.7.3" lifecycleRuntimeKtx = "2.6.1" activityCompose = "1.8.0" composeBom = "2024.04.01" +navigationCompose = "2.8.5" +retrofit2KotlinxSerializationConverter = "1.0.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } +converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" } +converter-kotlinx-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "converterKotlinxSerialization" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -24,6 +33,9 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } +retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "converterGson" } +retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }