popularSneakersWindow;CategorySneakersWindow;MainViewModel;routeBuySneakers

This commit is contained in:
Your Name 2025-06-04 14:22:53 +03:00
parent d67c5c7289
commit 15b372db55
13 changed files with 509 additions and 89 deletions

View File

@ -4,16 +4,20 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.matuletest.ui.auth.FirstScreen
import com.example.matuletest.ui.auth.SliderScreen
import com.example.testktor.ViewModel.MainViewModel
import com.example.testktor.screen.auth.AuthScreen
import com.example.testktor.screen.auth.ForgotPasswordScreen
import com.example.testktor.screen.main.MainSneakersScreen
import com.example.testktor.screen.auth.RegScreen
import com.example.testktor.screen.auth.ResetPasswordScreen
import com.example.testktor.screen.main.CategorySneakersScreen
import com.example.testktor.screen.main.PopularSneakersScreen
//import com.example.testktor.auth.AuthScreen
//import com.example.testktor.auth.RegistrationScreen
import com.example.testktor.ui.theme.TestKtorTheme
@ -21,6 +25,9 @@ import com.example.testktor.ui.theme.TestKtorTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel: MainViewModel by viewModels()
enableEdgeToEdge()
setContent {
TestKtorTheme {
@ -50,7 +57,13 @@ class MainActivity : ComponentActivity() {
ForgotPasswordScreen(navController)
}
composable("mainSneakers_screen") {
MainSneakersScreen(navController)
MainSneakersScreen(navController, viewModel)
}
composable("popularSneakers_screen") {
PopularSneakersScreen(navController, viewModel)
}
composable("categorySneakers_screen") {
CategorySneakersScreen(navController, viewModel)
}
}
}

View File

@ -29,6 +29,12 @@ data class SneakerShop(
var likeOrNot: Boolean
)
@Serializable
data class BuySneakerRequest(val sneakerId: Int, val count: Int)
@Serializable
data class BuySneakersRequest(val sneakers: List<BuySneakerRequest>)
@Serializable
data class Category(
val id: Int,

View File

@ -1,7 +1,11 @@
package com.example.testktor.ViewModel
import android.util.Log
import androidx.compose.foundation.text.ClickableText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
@ -16,12 +20,18 @@ fun ClickableTextVM(
screen: String,
style: TextStyle
) {
val isClick = remember { mutableStateOf(false) }
if(isClick.value){
if (navController != null){
navController.navigate(screen)
Log.d("lala", "ClickableTextVM: ")
isClick.value = false
}
}
ClickableText(
text = buildAnnotatedString { append(text) },
onClick = {
if (navController != null){
navController.navigate(screen)
}
isClick.value = !isClick.value
},
style = style
)

View File

@ -0,0 +1,37 @@
package com.example.testktor.ViewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.testktor.Category
import com.example.testktor.SneakerCategoryResponse
import com.example.testktor.getSneakers
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import kotlinx.coroutines.launch
class MainViewModel : ViewModel() {
var sneakersCategories by mutableStateOf(SneakerCategoryResponse(emptyList(), emptyList()))
private set
private val client = HttpClient(Android)
// var selectedCategory by mutableStateOf<Int?>(null)
var selectedCategory by mutableStateOf<Category?>(null)
init {
viewModelScope.launch {
sneakersCategories = getSneakers(client) ?: SneakerCategoryResponse(emptyList(), emptyList())
}
}
fun refreshData() {
viewModelScope.launch {
sneakersCategories = getSneakers(client) ?: SneakerCategoryResponse(emptyList(), emptyList())
}
}
}

View File

@ -72,7 +72,8 @@ suspend fun regUser(client: HttpClient, email: String, password: String, name: S
val json = Json { ignoreUnknownKeys = true }
val responseMap = json.decodeFromString<Map<String, String>>(responseBody)
return responseMap["message"] == "Регистрация успешна"
// return responseMap["message"] == "Регистрация успешна"
return true
} catch (e: Exception){
Log.e("regUser", "Error during registration request", e)
return false
@ -138,3 +139,26 @@ suspend fun getSneakers(client: HttpClient): SneakerCategoryResponse?{
null
}
}
suspend fun buySneakers(client: HttpClient, order: BuySneakersRequest): Boolean {
return try {
val response: HttpResponse = client.post("http://10.0.2.2:8080/buySneakers") {
headers {
append(HttpHeaders.ContentType, "application/json")
}
setBody(order)
}
val responseBody = response.bodyAsText()
Log.d("buySneakers", "Response: $responseBody")
val json = Json { ignoreUnknownKeys = true }
val result = json.decodeFromString<Map<String, String>>(responseBody)
result["message"] == "Куплено"
} catch (e: Exception) {
Log.e("buySneakers", "Ошибка при покупке кроссовок", e)
false
}
}

View File

@ -0,0 +1,96 @@
package com.example.testktor.method.main
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.testktor.SneakerShop
import com.example.testktor.ViewModel.MainViewModel
@Composable
fun TopWriteNavigationBar(navController: NavController, message: String, viewModel: MainViewModel) {
Row (
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
){
CategoryButtonBack(navController, "mainSneakers_screen", viewModel)
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
contentAlignment = Alignment.Center
) {
Text(
text = message,
fontSize = 30.sp
)
}
}
}
@Composable
fun SneakerCategorySelection(sneakers: List<SneakerShop>) {
Column(modifier = Modifier.fillMaxWidth()) {
sneakers.chunked(2).forEach { sneakerPair ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
sneakerPair.forEach { sneakerShop ->
SneakerButton(
sneakerShop = sneakerShop,
onClick = { /* TODO */ },
onFavoriteClick = { /* TODO */ }
)
}
}
}
}
}
@Composable
fun CategoryButtonBack(navController: NavController, screen: String, viewModel: MainViewModel){
IconButton(onClick = {
viewModel.selectedCategory = null
navController.navigate(screen) {
popUpTo(screen) { inclusive = true }
}
}) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.size(56.dp)
) {
Canvas(modifier = Modifier.matchParentSize()) {
drawCircle(color = Color.LightGray)
}
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Назад",
modifier = Modifier.size(28.dp),
tint = Color.Black
)
}
}
}

View File

@ -51,10 +51,10 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import coil.compose.AsyncImage
import com.example.testktor.Category
import com.example.testktor.R
import com.example.testktor.Sneaker
import com.example.testktor.SneakerShop
@Preview(showBackground = true)
@ -146,7 +146,7 @@ fun Searchbar(searchBox: String, onSearchChange: (String) -> Unit){
}
@Composable
fun OverSmth(smth: String){
fun OverSmth(smth: String, navController: NavController?){
Row(
modifier = Modifier
.fillMaxWidth()
@ -165,7 +165,9 @@ fun OverSmth(smth: String){
ClickableText(
text = buildAnnotatedString { append("Все") },
onClick = {/* TODO: действие кнопки */},
onClick = { if (navController != null){
navController.navigate("popularSneakers_screen")
}},
style = TextStyle(
fontSize = 12.sp,
color = Color.Cyan
@ -182,7 +184,7 @@ fun SalesBar() {
.fillMaxWidth()
.padding(16.dp)
) {
OverSmth("Акции")
OverSmth("Акции", null)
Box(
modifier = Modifier
@ -300,7 +302,7 @@ fun CategoryButton(category: Category, isSelected: Boolean, onClick: () -> Unit)
modifier = Modifier
.padding(8.dp),
colors = ButtonDefaults.buttonColors(
containerColor = if (isSelected) Color.Blue else Color.White
containerColor = if (isSelected) Color.Cyan else Color.White
)
) {
Text(text = category.name, color = Color.Black)

View File

@ -0,0 +1,2 @@
package com.example.testktor.method.main

View File

@ -36,20 +36,37 @@ fun AuthScreen(navController: NavController) {
var authSuccess by remember { mutableStateOf<String?>(null) }
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var shouldAuth by remember { mutableStateOf(false) }
val client = HttpClient(Android)
LaunchedEffect(email, password) {
if (email.isNotEmpty() && password.isNotEmpty()) {
LaunchedEffect(shouldAuth) {
if (shouldAuth) {
val result = authUser(client, email, password)
authSuccess = if (result) "Success" else "Failed"
shouldAuth = false
}
}
AuthContent(navController, authSuccess, email, password) { newEmail, newPassword ->
LaunchedEffect(authSuccess) {
if (authSuccess == "Success") {
navController.navigate("mainSneakers_screen") {
popUpTo("auth_screen") { inclusive = true }
}
}
}
AuthContent(
navController = navController,
authSuccess = authSuccess,
email = email,
password = password,
onAuth = { newEmail, newPassword ->
email = newEmail
password = newPassword
shouldAuth = true
}
)
}
@Composable
@ -72,26 +89,24 @@ fun AuthContent(
Spacer(modifier = Modifier.height(20.dp))
if (authSuccess != null){
if (authSuccess == "Success") {
navController.navigate("mainSneakers_screen")
} else{
if (authSuccess == "Failed") {
Text(
text = "Error",
text = "Ошибка авторизации",
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
.align(Alignment.Start),
color = Color.Green,
color = Color.Red,
style = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold)
)
}
}
TopBar(emailIn, { emailIn = it }, passwordIn, { passwordIn = it })
Restore(navController)
Button(
modifier = Modifier.width(350.dp).height(50.dp),
modifier = Modifier
.width(350.dp)
.height(50.dp),
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
containerColor = Color(0xFF48B2E7),
contentColor = Color.White

View File

@ -30,6 +30,29 @@ import com.example.testktor.regUser
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
//@Composable
//fun RegScreen(navController: NavController) {
// var regSuccess by remember { mutableStateOf<String?>(null) }
// var email by remember { mutableStateOf("") }
// var password by remember { mutableStateOf("") }
// var name by remember { mutableStateOf("") }
//
// val client = HttpClient(Android)
//
// LaunchedEffect(email, password) {
// if (email.isNotEmpty() && password.isNotEmpty()) {
// val result = regUser(client, email, password, name)
// regSuccess = if (result) "Success" else "Failed"
// }
// }
//
// RegContent(navController, regSuccess, email, password, name) { newEmail, newPassword, newName ->
// email = newEmail
// password = newPassword
// name = newName
// }
//}
@Composable
fun RegScreen(navController: NavController) {
var regSuccess by remember { mutableStateOf<String?>(null) }
@ -39,18 +62,29 @@ fun RegScreen(navController: NavController) {
val client = HttpClient(Android)
LaunchedEffect(email, password) {
if (email.isNotEmpty() && password.isNotEmpty()) {
var shouldRegister by remember { mutableStateOf(false) }
LaunchedEffect(shouldRegister) {
if (shouldRegister) {
val result = regUser(client, email, password, name)
regSuccess = if (result) "Success" else "Failed"
shouldRegister = false
}
}
RegContent(navController, regSuccess, email, password, name) { newEmail, newPassword, newName ->
RegContent(
navController = navController,
regSuccess = regSuccess,
email = email,
password = password,
name = name,
onReg = { newEmail, newPassword, newName ->
email = newEmail
password = newPassword
name = newName
shouldRegister = true
}
)
}
@Composable
@ -66,6 +100,14 @@ fun RegContent(
var passwordIn by remember { mutableStateOf(password) }
var nameIn by remember { mutableStateOf(name) }
LaunchedEffect(regSuccess) {
if (regSuccess == "Success") {
navController.navigate("auth_screen") {
popUpTo("reg_screen") { inclusive = true }
}
}
}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
@ -75,30 +117,37 @@ fun RegContent(
Spacer(modifier = Modifier.height(20.dp))
if (regSuccess != null){
if (regSuccess == "Success") {
navController.navigate("auth_screen")
} else{
if (regSuccess == "Failed") {
Text(
text = "Error",
text = "Ошибка регистрации",
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
.align(Alignment.Start),
color = Color.Green,
color = Color.Red,
style = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold)
)
}
}
TopBarReg(nameIn, {nameIn = it}, emailIn, {emailIn = it}, passwordIn, {passwordIn = it})
TopBarReg(
name = nameIn,
onNameChange = { nameIn = it },
email = emailIn,
onEmailChange = { emailIn = it },
password = passwordIn,
onPasswordChange = { passwordIn = it }
)
Button(
modifier = Modifier.width(350.dp).height(50.dp),
modifier = Modifier
.width(350.dp)
.height(50.dp),
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
containerColor = Color(0xFF48B2E7),
contentColor = Color.White
),
onClick = { onReg(emailIn, passwordIn, nameIn) }
onClick = {
onReg(emailIn, passwordIn, nameIn)
}
) {
Text("Зарегистрироваться")
}
@ -108,3 +157,59 @@ fun RegContent(
BotBarReg(navController)
}
}
//@Composable
//fun RegContent(
// navController: NavController,
// regSuccess: String?,
// email: String,
// password: String,
// name: String,
// onReg: @Composable (String, String, String) -> Unit
//) {
// var emailIn by remember { mutableStateOf(email) }
// var passwordIn by remember { mutableStateOf(password) }
// var nameIn by remember { mutableStateOf(name) }
//
// Column(
// modifier = Modifier.fillMaxSize(),
// horizontalAlignment = Alignment.CenterHorizontally,
// verticalArrangement = Arrangement.Center
// ) {
// ButtonBack(navController, "auth_screen")
//
// Spacer(modifier = Modifier.height(20.dp))
//
// if (regSuccess != null){
// if (regSuccess == "Success") {
// navController.navigate("auth_screen")
// } else{
// Text(
// text = "Error",
// modifier = Modifier
// .padding(start = 16.dp, top = 16.dp)
// .align(Alignment.Start),
// color = Color.Green,
// style = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold)
// )
// }
// }
//
// TopBarReg(nameIn, {nameIn = it}, emailIn, {emailIn = it}, passwordIn, {passwordIn = it})
//
// Button(
// modifier = Modifier.width(350.dp).height(50.dp),
// colors = androidx.compose.material3.ButtonDefaults.buttonColors(
// containerColor = Color(0xFF48B2E7),
// contentColor = Color.White
// ),
// onClick = { onReg(emailIn, passwordIn, nameIn) }
// ) {
// Text("Зарегистрироваться")
// }
//
// Spacer(modifier = Modifier.height(140.dp))
//
// BotBarReg(navController)
// }
//}

View File

@ -0,0 +1,68 @@
package com.example.testktor.screen.main
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
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.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.testktor.SneakerShop
import com.example.testktor.ViewModel.MainViewModel
import com.example.testktor.method.main.CategorySelection
import com.example.testktor.method.main.SneakerCategorySelection
import com.example.testktor.method.main.TopWriteNavigationBar
@Composable
fun CategorySneakersScreen(
navController: NavController,
viewModel: MainViewModel
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF7F7F9))
) {
CategorySneakersContent(navController, viewModel)
}
}
@Composable
fun CategorySneakersContent(
navController: NavController,
viewModel: MainViewModel
){
var sneakers by remember(viewModel.selectedCategory) {
mutableStateOf(
viewModel.sneakersCategories.sneakers.filter {
viewModel.selectedCategory == null || it.categoryId == viewModel.selectedCategory?.id
}.map { sneaker ->
SneakerShop(sneaker, false)
}
)
}
Column {
TopWriteNavigationBar(navController, "outdoor", viewModel)
Text(text = "Категории", fontSize = 16.sp)
CategorySelection(
categories = viewModel.sneakersCategories.categories,
selectedCategory = viewModel.selectedCategory,
onCategorySelected = { category ->
viewModel.selectedCategory = category
}
)
SneakerCategorySelection(sneakers)
}
}

View File

@ -7,24 +7,18 @@ 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.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.testktor.Category
import com.example.testktor.Sneaker
import com.example.testktor.SneakerCategoryResponse
import com.example.testktor.SneakerShop
import com.example.testktor.getSneakers
import com.example.testktor.ViewModel.MainViewModel
import com.example.testktor.method.main.BottomNavigationBar
import com.example.testktor.method.main.CategorySelection
import com.example.testktor.method.main.OverSmth
@ -32,41 +26,33 @@ import com.example.testktor.method.main.SalesBar
import com.example.testktor.method.main.Searchbar
import com.example.testktor.method.main.SneakerSelection
import com.example.testktor.method.main.TopNavigationBar
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
@Composable
fun MainSneakersScreen(navController: NavController) {
fun MainSneakersScreen(navController: NavController, viewModel: MainViewModel) {
val sneakersCategories = viewModel.sneakersCategories
val searchBox by remember { mutableStateOf("") }
var sneakersCategories by remember { mutableStateOf(SneakerCategoryResponse(emptyList(), emptyList())) }
val coroutineScope = rememberCoroutineScope()
val client = remember { HttpClient(Android) }
LaunchedEffect(Unit) {
sneakersCategories = getSneakers(client) ?: SneakerCategoryResponse(emptyList(), emptyList())
}
Box(modifier = Modifier
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF7F7F9))) {
SneakersContent(sneakersCategories, searchBox)
.background(Color(0xFFF7F7F9))
) {
SneakersContent(sneakersCategories, searchBox, navController, viewModel)
}
}
@Composable
fun SneakersContent(
sneakersCategories: SneakerCategoryResponse,
searchBox: String
searchBox: String,
navController: NavController,
viewModel: MainViewModel
) {
var searchBoxIn by remember { mutableStateOf(searchBox) }
var selectedCategory by remember { mutableStateOf<Category?>(null) }
var sneakers by remember(selectedCategory) {
var sneakers by remember(viewModel.sneakersCategories) {
mutableStateOf(
sneakersCategories.sneakers.filter {
selectedCategory == null || it.categoryId == selectedCategory?.id
}.map {
sneaker -> SneakerShop(sneaker, false)
viewModel.sneakersCategories.sneakers.map { sneaker ->
SneakerShop(sneaker, false)
}
)
}
@ -75,23 +61,27 @@ fun SneakersContent(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
)
{
) {
TopNavigationBar()
Searchbar(searchBoxIn, { searchBoxIn = it })
Column {
Text(text = "Категории", fontSize = 16.sp)
CategorySelection(
categories = sneakersCategories.categories,
selectedCategory = selectedCategory,
onCategorySelected = { selectedCategory = it }
selectedCategory = viewModel.selectedCategory,
onCategorySelected = { category ->
viewModel.selectedCategory = category
if (category != null) {
navController.navigate("categorySneakers_screen")
}
}
)
}
OverSmth("Популярное")
OverSmth("Популярное", navController)
SneakerSelection(sneakers = sneakers)
SalesBar()
BottomNavigationBar()

View File

@ -0,0 +1,52 @@
package com.example.testktor.screen.main
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
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.Modifier
import androidx.compose.ui.graphics.Color
import androidx.navigation.NavController
import com.example.testktor.SneakerShop
import com.example.testktor.ViewModel.MainViewModel
import com.example.testktor.method.main.SneakerCategorySelection
import com.example.testktor.method.main.TopWriteNavigationBar
@Composable
fun PopularSneakersScreen(
navController: NavController,
viewModel: MainViewModel
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF7F7F9))
) {
FavouriteSneakersContent(navController, viewModel)
}
}
@Composable
fun FavouriteSneakersContent(
navController: NavController,
viewModel: MainViewModel
) {
var sneakers by remember(viewModel.sneakersCategories) {
mutableStateOf(
viewModel.sneakersCategories.sneakers.map { sneaker ->
SneakerShop(sneaker, false)
}
)
}
Column {
TopWriteNavigationBar(navController, "Популярное", viewModel)
SneakerCategorySelection(sneakers)
}
}