commit 18de6efe6aad327e1b814ae95a383f4e7c079f5d Author: KP9lK Date: Wed Jun 11 14:30:16 2025 +0300 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b687156 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ae733f1 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..cde3e19 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,57 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..6d0ee1c --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..76c7d9e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,794 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..d843f34 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..62ab0de --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,80 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + kotlin("plugin.serialization") version "2.1.20" +} + +android { + namespace = "com.example.furintiture" + compileSdk = 35 + + defaultConfig { + applicationId = "com.example.furintiture" + minSdk = 24 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = true + } +} + +dependencies { + + implementation(libs.retrofit) + implementation(libs.converter.kotlinx.serialization) + implementation(libs.okhttp) + + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.datetime) + + implementation (libs.androidx.datastore.preferences) + + + + implementation(libs.androidx.lifecycle.viewmodel.compose) + + + implementation(libs.coil.compose) + implementation(libs.coil.network.okhttp) + + implementation (libs.androidx.navigation.compose) + + + 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) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/furintiture/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/furintiture/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..cb115e1 --- /dev/null +++ b/app/src/androidTest/java/com/example/furintiture/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.furintiture + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.furintiture", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c88f1ac --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/MainActivity.kt b/app/src/main/java/com/example/furintiture/MainActivity.kt new file mode 100644 index 0000000..c9268bc --- /dev/null +++ b/app/src/main/java/com/example/furintiture/MainActivity.kt @@ -0,0 +1,89 @@ +package com.example.furintiture + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.lifecycle.lifecycleScope +import androidx.navigation.compose.rememberNavController +import com.example.furintiture.data.DataStoreSettings +import com.example.furintiture.ui.common.BottomNavigation +import com.example.furintiture.ui.common.Destination +import com.example.furintiture.ui.common.FurnitureBottomNavigation +import com.example.furintiture.ui.screen.Cart +import com.example.furintiture.ui.screen.FurnitureGlobalNavigation +import com.example.furintiture.ui.screen.Login +import com.example.furintiture.ui.screen.Main +import com.example.furintiture.ui.screen.Order +import com.example.furintiture.ui.screen.Registration +import com.example.furintiture.ui.screen.Splash +import com.example.furintiture.ui.theme.FurintitureTheme +import kotlinx.coroutines.launch + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + val dataStoreSettings = DataStoreSettings(applicationContext) + setContent { + FurintitureTheme { + val navController = rememberNavController() + val bottomBarIsVisible = remember { mutableStateOf(false) } + val userUuid = remember { mutableStateOf("") } + + Scaffold( + bottomBar = { + if(bottomBarIsVisible.value) FurnitureBottomNavigation(){ destination -> + when(destination.route){ + BottomNavigation.Home -> navController.navigate(Main(userUuid.value)) + BottomNavigation.Cart -> navController.navigate(Cart(userUuid.value)) + BottomNavigation.Order -> { + navController.navigate(Order(userUuid.value)) + } + } + } + } + ) { paddingValues -> + paddingValues + FurnitureGlobalNavigation(dataStoreSettings, navController){ + bottomBarIsVisible.value = it + } + } + LaunchedEffect(Unit) { + lifecycleScope.launch { + navController.clearBackStack(Splash) + dataStoreSettings.userUuidFlow.collect{ + if(it.isEmpty()){ + navController.navigate(Login){ + popUpTo(Splash){ + inclusive = true + } + } + }else{ + userUuid.value = it + navController.navigate(Main(it)){ + popUpTo(Splash){ + inclusive = true + } + popUpTo(Registration){ + inclusive = true + } + popUpTo(Login){ + inclusive = true + } + } + } + } + } + + } + } + } + + } +} + diff --git a/app/src/main/java/com/example/furintiture/configure/Serializers.kt b/app/src/main/java/com/example/furintiture/configure/Serializers.kt new file mode 100644 index 0000000..a0d324e --- /dev/null +++ b/app/src/main/java/com/example/furintiture/configure/Serializers.kt @@ -0,0 +1,34 @@ +package com.example.furintiture.configure + +import androidx.navigation.NavType +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.math.BigDecimal +import java.util.UUID + +object BigDecimalSerializer: KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("BigDecimal", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): BigDecimal { + return BigDecimal(decoder.decodeString()) + } + + override fun serialize(encoder: Encoder, value: BigDecimal) { + encoder.encodeString(value.toString()) + } +} +object UuidSerializer: KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): UUID { + return UUID.fromString(decoder.decodeString()) + } + + override fun serialize(encoder: Encoder, value: UUID) { + encoder.encodeString(value.toString()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/data/DataStore.kt b/app/src/main/java/com/example/furintiture/data/DataStore.kt new file mode 100644 index 0000000..6eb87c5 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/data/DataStore.kt @@ -0,0 +1,25 @@ +package com.example.furintiture.data + +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 +import java.util.UUID + +val Context.dataStore: DataStore by preferencesDataStore(name = "settings") + +class DataStoreSettings(private val context: Context){ + private val UUID_KEY = stringPreferencesKey("user_uuid") + val userUuidFlow: Flow = context.dataStore.data.map { preferences -> + preferences[UUID_KEY] ?: "" + } + suspend fun setUuid(uuid: UUID){ + context.dataStore.edit { preferences -> + preferences[UUID_KEY] = uuid.toString() + } + } +} diff --git a/app/src/main/java/com/example/furintiture/data/RetrofitClient.kt b/app/src/main/java/com/example/furintiture/data/RetrofitClient.kt new file mode 100644 index 0000000..0b15b88 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/data/RetrofitClient.kt @@ -0,0 +1,21 @@ +package com.example.furintiture.data + +import com.example.furintiture.data.api.FurnitureApi +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import retrofit2.Retrofit +import retrofit2.converter.kotlinx.serialization.asConverterFactory + +private const val BASE_URL = "http://185.207.0.137:8090" +object RetrofitClient { + private val mediaType = "application/json".toMediaType() + private val client by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(Json.asConverterFactory(mediaType)) + .build() + } + val api by lazy { + client.create(FurnitureApi::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/data/api/FurnitureApi.kt b/app/src/main/java/com/example/furintiture/data/api/FurnitureApi.kt new file mode 100644 index 0000000..dccef3a --- /dev/null +++ b/app/src/main/java/com/example/furintiture/data/api/FurnitureApi.kt @@ -0,0 +1,88 @@ +package com.example.furintiture.data.api + +import com.example.furintiture.model.request.AddAddressRequest +import com.example.furintiture.model.request.AddToCartRequest +import com.example.furintiture.model.request.AddToWishlistRequest +import com.example.furintiture.model.request.ChangeCountFromCartRequest +import com.example.furintiture.model.request.CreateOrderRequest +import com.example.furintiture.model.request.LoginRequest +import com.example.furintiture.model.request.RegisterRequest +import com.example.furintiture.model.request.RemoveFromCartRequest +import com.example.furintiture.model.request.RemoveFromWishlistRequest +import com.example.furintiture.model.response.AllShopCategoryResponse +import com.example.furintiture.model.response.CartResponse +import com.example.furintiture.model.response.FurnitureCategoryResponse +import com.example.furintiture.model.response.FurnitureResponse +import com.example.furintiture.model.response.OrderResponse +import com.example.furintiture.model.response.SaleResponse +import com.example.furintiture.model.response.UserResponse +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Path +import java.util.UUID +import kotlin.uuid.Uuid + +interface FurnitureApi { + @POST("/auth/login") + suspend fun auth(@Body loginRequest: LoginRequest): UserResponse + + @POST("/auth/register") + suspend fun register(@Body registerRequest: RegisterRequest): UserResponse + + @GET("/user/{uuid}/wishlist") + suspend fun getWithListByUuid(@Path("uuid") uuid: UUID): List + + @GET("/user/{uuid}/cart") + suspend fun getCartByUuid(@Path("uuid") uuid: UUID): List + + @GET("/user/{uuid}/order") + suspend fun getOrdersByUuid(@Path("uuid") uuid: UUID): List + + @GET("/user/{uuid}/profile") + suspend fun getProfileByUuid(@Path("uuid") uuid: UUID): UserResponse + + @POST("/user/{uuid}/address") + suspend fun addAddressByUuid(@Path("uuid") uuid: UUID, @Body addAddressRequest: AddAddressRequest) + + @POST("/wishlist") + suspend fun addToWishlist(addToWishlistRequest: AddToWishlistRequest) + + @DELETE("/wishlist") + suspend fun addToWishlist(removeFromWishlistRequest: RemoveFromWishlistRequest) + + @GET("/shop_category") + suspend fun getAllShopCategory(): List + + @GET("/shop_category/{shopCategoryId}/furniture") + suspend fun getFurnitureByShopCategory(@Path("shopCategoryId") shopCategoryId: Long): List + + @GET("/sale") + suspend fun getAllSales(): List + + @POST("/order") + suspend fun createOrder(@Body createOrderRequest: CreateOrderRequest) + + @GET("/furniture") + suspend fun getAllFurniture(): List + + @GET("/furniture/{furnitureId}") + suspend fun getFurnitureById(@Path("furnitureId") furnitureId: Long): FurnitureResponse + + @GET("/category") + suspend fun getAllFurnitureCategory(): List + + @GET("/category/{id}/furniture") + suspend fun getFurnitureByCategory(@Path("id") categoryId: Long): List + + @POST("/cart") + suspend fun addToCartByUuid(@Body addToCartRequest: AddToCartRequest) + + @PUT("/cart") + suspend fun changeCountFromCartByUuid(@Body changeCountFromCartRequest: ChangeCountFromCartRequest) + + @DELETE("/cart") + suspend fun removeFromCartByUuid(@Body removeFromCartRequest: RemoveFromCartRequest) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/request/AddAddressRequest.kt b/app/src/main/java/com/example/furintiture/model/request/AddAddressRequest.kt new file mode 100644 index 0000000..9f8d883 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/request/AddAddressRequest.kt @@ -0,0 +1,12 @@ +package com.example.furintiture.model.request + +import kotlinx.serialization.Serializable + +@Serializable +data class AddAddressRequest( + var address: String = "", + var entrance: Int? = null, + var floor: Int? = null, + var apartment: Int? = null, + var comment: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/request/AddToCartRequest.kt b/app/src/main/java/com/example/furintiture/model/request/AddToCartRequest.kt new file mode 100644 index 0000000..52811bd --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/request/AddToCartRequest.kt @@ -0,0 +1,12 @@ +package com.example.furintiture.model.request + +import com.example.furintiture.configure.UuidSerializer +import kotlinx.serialization.Serializable +import java.util.UUID +@Serializable +data class AddToCartRequest( + @Serializable(with = UuidSerializer::class) + val uuid: UUID, + val furnitureId: Long, + val count: Int, +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/request/AddToWishlistRequest.kt b/app/src/main/java/com/example/furintiture/model/request/AddToWishlistRequest.kt new file mode 100644 index 0000000..ef2ea97 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/request/AddToWishlistRequest.kt @@ -0,0 +1,11 @@ +package com.example.furintiture.model.request + +import com.example.furintiture.configure.UuidSerializer +import kotlinx.serialization.Serializable +import java.util.UUID +@Serializable +data class AddToWishlistRequest( + @Serializable(with = UuidSerializer::class) + val uuid: UUID, + val furnitureId: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/request/ChangeCountFromCartRequest.kt b/app/src/main/java/com/example/furintiture/model/request/ChangeCountFromCartRequest.kt new file mode 100644 index 0000000..faac649 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/request/ChangeCountFromCartRequest.kt @@ -0,0 +1,12 @@ +package com.example.furintiture.model.request + +import com.example.furintiture.configure.UuidSerializer +import kotlinx.serialization.Serializable +import java.util.UUID +@Serializable +data class ChangeCountFromCartRequest( + @Serializable(with = UuidSerializer::class) + val uuid: UUID, + val furnitureId: Long, + val count: Int, +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/request/CreateOrderItemRequest.kt b/app/src/main/java/com/example/furintiture/model/request/CreateOrderItemRequest.kt new file mode 100644 index 0000000..580d632 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/request/CreateOrderItemRequest.kt @@ -0,0 +1,12 @@ +package com.example.furintiture.model.request + +import com.example.furintiture.configure.BigDecimalSerializer +import kotlinx.serialization.Serializable +import java.math.BigDecimal +@Serializable +data class CreateOrderItemRequest( + val furnitureId : Long, + @Serializable(with = BigDecimalSerializer::class) + val furniturePrice: BigDecimal, + val count: Int +) diff --git a/app/src/main/java/com/example/furintiture/model/request/CreateOrderRequest.kt b/app/src/main/java/com/example/furintiture/model/request/CreateOrderRequest.kt new file mode 100644 index 0000000..4502308 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/request/CreateOrderRequest.kt @@ -0,0 +1,18 @@ +package com.example.furintiture.model.request + +import com.example.furintiture.configure.BigDecimalSerializer +import com.example.furintiture.configure.UuidSerializer +import kotlinx.serialization.Serializable +import java.math.BigDecimal +import java.util.UUID + +@Serializable +data class CreateOrderRequest( + @Serializable(with = UuidSerializer::class) + val userUuid : UUID, + val addressId: Long, + val orderStatus : Int, + @Serializable(with = BigDecimalSerializer::class) + val orderTotalSum : BigDecimal, + val orderSet: List +) diff --git a/app/src/main/java/com/example/furintiture/model/request/LoginRequest.kt b/app/src/main/java/com/example/furintiture/model/request/LoginRequest.kt new file mode 100644 index 0000000..97eebfa --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/request/LoginRequest.kt @@ -0,0 +1,6 @@ +package com.example.furintiture.model.request + +import kotlinx.serialization.Serializable + +@Serializable +data class LoginRequest(val email: String, val password: String) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/request/RegisterRequest.kt b/app/src/main/java/com/example/furintiture/model/request/RegisterRequest.kt new file mode 100644 index 0000000..86a08e9 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/request/RegisterRequest.kt @@ -0,0 +1,11 @@ +package com.example.furintiture.model.request + +import kotlinx.serialization.Serializable + +@Serializable +data class RegisterRequest( + val firstName: String, + val lastName: String, + val email: String, + val password: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/request/RemoveFromCartRequest.kt b/app/src/main/java/com/example/furintiture/model/request/RemoveFromCartRequest.kt new file mode 100644 index 0000000..b7eab9c --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/request/RemoveFromCartRequest.kt @@ -0,0 +1,11 @@ +package com.example.furintiture.model.request + +import com.example.furintiture.configure.UuidSerializer +import kotlinx.serialization.Serializable +import java.util.UUID +@Serializable +data class RemoveFromCartRequest( + @Serializable(with = UuidSerializer::class) + val uuid: UUID, + val furnitureId: Long, +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/request/RemoveFromWishlistRequest.kt b/app/src/main/java/com/example/furintiture/model/request/RemoveFromWishlistRequest.kt new file mode 100644 index 0000000..f86d4e7 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/request/RemoveFromWishlistRequest.kt @@ -0,0 +1,11 @@ +package com.example.furintiture.model.request + +import com.example.furintiture.configure.UuidSerializer +import kotlinx.serialization.Serializable +import java.util.UUID +@Serializable +data class RemoveFromWishlistRequest( + @Serializable(with = UuidSerializer::class) + val uuid: UUID, + val furnitureId: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/response/AddressResponse.kt b/app/src/main/java/com/example/furintiture/model/response/AddressResponse.kt new file mode 100644 index 0000000..106be4e --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/AddressResponse.kt @@ -0,0 +1,13 @@ +package com.example.furintiture.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class AddressResponse( + val addressId: Int, + val entrance: Int, + val floor: Int, + val apartment: Int, + val comment: String?, + val address: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/response/AllShopCategoryResponse.kt b/app/src/main/java/com/example/furintiture/model/response/AllShopCategoryResponse.kt new file mode 100644 index 0000000..a1bc57c --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/AllShopCategoryResponse.kt @@ -0,0 +1,10 @@ +package com.example.furintiture.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class AllShopCategoryResponse( + val id: Long, + val name: String, + var furnitureList: List +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/response/CartResponse.kt b/app/src/main/java/com/example/furintiture/model/response/CartResponse.kt new file mode 100644 index 0000000..dcce1ff --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/CartResponse.kt @@ -0,0 +1,9 @@ +package com.example.furintiture.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class CartResponse( + val count: Int, + val furnitureResponse: FurnitureResponse +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/response/FurnitureCategoryResponse.kt b/app/src/main/java/com/example/furintiture/model/response/FurnitureCategoryResponse.kt new file mode 100644 index 0000000..39196e0 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/FurnitureCategoryResponse.kt @@ -0,0 +1,9 @@ +package com.example.furintiture.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class FurnitureCategoryResponse( + val id: Int, + val name: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/response/FurnitureResponse.kt b/app/src/main/java/com/example/furintiture/model/response/FurnitureResponse.kt new file mode 100644 index 0000000..f752849 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/FurnitureResponse.kt @@ -0,0 +1,19 @@ +package com.example.furintiture.model.response + +import com.example.furintiture.configure.BigDecimalSerializer +import kotlinx.serialization.Serializable +import java.math.BigDecimal + +@Serializable +data class FurnitureResponse( + val id: Long, + val name: String, + val description: String, + val url: String, + val category: FurnitureCategoryResponse, + @Serializable(with = BigDecimalSerializer::class) + val price: BigDecimal, + @Serializable(with = BigDecimalSerializer::class) + val sale: BigDecimal, + var shopCategories: List, +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/response/OrderItemResponse.kt b/app/src/main/java/com/example/furintiture/model/response/OrderItemResponse.kt new file mode 100644 index 0000000..1eca45f --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/OrderItemResponse.kt @@ -0,0 +1,14 @@ +package com.example.furintiture.model.response + +import com.example.furintiture.configure.BigDecimalSerializer +import kotlinx.serialization.Serializable +import java.math.BigDecimal +@Serializable +data class OrderItemResponse( + val furnitureId : Long, + val orderId : Long, + @Serializable(with = BigDecimalSerializer::class) + val furniturePrice: BigDecimal, + val count: Int, + val furnitureResponse: FurnitureResponse +) diff --git a/app/src/main/java/com/example/furintiture/model/response/OrderResponse.kt b/app/src/main/java/com/example/furintiture/model/response/OrderResponse.kt new file mode 100644 index 0000000..4329ba2 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/OrderResponse.kt @@ -0,0 +1,21 @@ +package com.example.furintiture.model.response + +import com.example.furintiture.configure.BigDecimalSerializer +import com.example.furintiture.configure.UuidSerializer +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.Serializable +import java.math.BigDecimal +import java.util.* +@Serializable +data class OrderResponse( + val id : Long, + @Serializable(with = UuidSerializer::class) + val userUuid : UUID, + val addressId: Long, + val addressResponse: AddressResponse, + val dateTime: LocalDateTime, + val orderStatus : OrderStatusResponse, + @Serializable(with = BigDecimalSerializer::class) + val orderTotalSum : BigDecimal, + var orderSet: List +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/response/OrderStatusResponse.kt b/app/src/main/java/com/example/furintiture/model/response/OrderStatusResponse.kt new file mode 100644 index 0000000..e10406a --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/OrderStatusResponse.kt @@ -0,0 +1,9 @@ +package com.example.furintiture.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class OrderStatusResponse( + val id: Int, + val name: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/response/SaleResponse.kt b/app/src/main/java/com/example/furintiture/model/response/SaleResponse.kt new file mode 100644 index 0000000..6beecb9 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/SaleResponse.kt @@ -0,0 +1,10 @@ +package com.example.furintiture.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class SaleResponse( + val id: Int, + val name: String, + val url: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/response/ShopCategoryResponse.kt b/app/src/main/java/com/example/furintiture/model/response/ShopCategoryResponse.kt new file mode 100644 index 0000000..8a39eab --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/ShopCategoryResponse.kt @@ -0,0 +1,9 @@ +package com.example.furintiture.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class ShopCategoryResponse( + val id: Long, + val name: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/model/response/UserResponse.kt b/app/src/main/java/com/example/furintiture/model/response/UserResponse.kt new file mode 100644 index 0000000..eccdee6 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/model/response/UserResponse.kt @@ -0,0 +1,15 @@ +package com.example.furintiture.model.response + +import com.example.furintiture.configure.UuidSerializer +import kotlinx.serialization.Serializable +import java.util.* +@Serializable +data class UserResponse( + @Serializable(with = UuidSerializer::class) + val userUuid: UUID, + val email: String, + val firstName: String, + val lastName: String, + val imageUrl: String? = null, + val address: AddressResponse? = null, + ) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/common/FurnitrueTextField.kt b/app/src/main/java/com/example/furintiture/ui/common/FurnitrueTextField.kt new file mode 100644 index 0000000..eae34fc --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/common/FurnitrueTextField.kt @@ -0,0 +1,173 @@ +package com.example.furintiture.ui.common + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.furintiture.R +import com.example.furintiture.ui.theme.Neutral_40 +import com.example.furintiture.ui.theme.Neutral_70 +import com.example.furintiture.ui.theme.Neutral_90 + +@Composable +fun BasieFurnitureTextField( + value: String, + onValueChange: (String) -> Unit, + textColor: Color, + label: @Composable () -> Unit, + modifier: Modifier = Modifier, + leadingIcon: @Composable (() -> Unit)? = null, + visualTransformation: VisualTransformation = VisualTransformation.None, + trailingIcon: @Composable (() -> Unit)? = null, +){ + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier + .fillMaxWidth() + .background( + color = Color.White, + shape = RoundedCornerShape(18.dp)) + .border( + width = 1.dp, color = Neutral_40, + shape = RoundedCornerShape(18.dp) + ), + singleLine = true, + textStyle = TextStyle( + color = Neutral_90, + fontSize = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ), + visualTransformation = visualTransformation, + ){ innerTextField -> + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp, horizontal = 18.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (leadingIcon != null) { + leadingIcon() + } + Spacer(modifier = Modifier.width(10.dp)) + Column { + label() + Spacer(modifier = Modifier.height(4.dp)) + innerTextField() + } + Spacer(modifier = Modifier.weight(1f)) + trailingIcon?.let { + it() + } + } + } +} + + + + + + +@Composable +fun FurnitureTextField( + value: String, + onValueChange: (String) -> Unit, + textLabel: String, + leadingResourceId: Int, + tintColor: Color = Neutral_70, + textColor: Color = Neutral_90, +){ + BasieFurnitureTextField( + value = value, + textColor = textColor, + onValueChange = onValueChange, + label = { + Text( + textLabel, + color = textColor, + style = TextStyle( + color = Neutral_90, + fontSize = 15.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ) + ) + }, + leadingIcon = { Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(leadingResourceId), + tint = tintColor, + contentDescription = null) + } + ) +} + + +@Composable +fun FurniturePasswordTextField( + value: String, + onValueChange: (String) -> Unit, + tintColor: Color = Neutral_70, + textColor: Color = Neutral_90, +){ + val passwordIsVisible = remember { mutableStateOf(false) } + BasieFurnitureTextField( + value = value, + textColor = textColor, + onValueChange = onValueChange, + label = { + Text("Password", + color = textColor, + style = TextStyle( + color = Neutral_90, + fontSize = 15.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + )) + }, + visualTransformation = if(passwordIsVisible.value) VisualTransformation.None else PasswordVisualTransformation('*'), + leadingIcon = { Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.lock_icon), + tint = tintColor, + contentDescription = null) + }, + trailingIcon = { Icon( + modifier = + Modifier.size(20.dp).clickable { + passwordIsVisible.value = !passwordIsVisible.value + }, + tint = tintColor, + painter = painterResource(R.drawable.fi_sr_eye_1), + contentDescription = null) + } + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/common/FurnitureBottomNavigation.kt b/app/src/main/java/com/example/furintiture/ui/common/FurnitureBottomNavigation.kt new file mode 100644 index 0000000..505ae9a --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/common/FurnitureBottomNavigation.kt @@ -0,0 +1,81 @@ +package com.example.furintiture.ui.common + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.ShoppingCart +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemColors +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import com.example.furintiture.ui.theme.Neutral_40 +import com.example.furintiture.ui.theme.Primary + +public enum class Destination( + val route: BottomNavigation, + val label: String, + val icon: ImageVector, + val contentDescription: String +) { + HOME(BottomNavigation.Home, "Основная", Icons.Default.Home, "Songs"), + CART(BottomNavigation.Cart, "Корзина", Icons.Default.ShoppingCart, "Album"), + ORDER(BottomNavigation.Order, "Заказы", Icons.Default.Menu, "Playlist") +} + +sealed class BottomNavigation{ + data object Home: BottomNavigation() + data object Cart: BottomNavigation() + data object Order: BottomNavigation() +} + +@Composable +fun FurnitureBottomNavigation( + onChangeBottomItem: (Destination) -> Unit +) { + + val startDestination = Destination.HOME + var selectedDestination by rememberSaveable { mutableIntStateOf(startDestination.ordinal) } + NavigationBar( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + containerColor = Color.White + ) { + Destination.entries.forEachIndexed { index, destination -> + NavigationBarItem( + selected = selectedDestination == index, + onClick = { + selectedDestination = index + onChangeBottomItem(destination) + }, + icon = { + Icon( + destination.icon, + contentDescription = destination.contentDescription + ) + }, + label = { Text(destination.label) }, + colors = NavigationBarItemColors( + selectedIconColor = Primary, + selectedTextColor = Primary, + selectedIndicatorColor = Color.Transparent, + unselectedIconColor = Neutral_40, + unselectedTextColor = Neutral_40, + disabledIconColor = Neutral_40, + disabledTextColor = Neutral_40 + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/common/FurnitureButton.kt b/app/src/main/java/com/example/furintiture/ui/common/FurnitureButton.kt new file mode 100644 index 0000000..3cdc294 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/common/FurnitureButton.kt @@ -0,0 +1,65 @@ +package com.example.furintiture.ui.common + +import android.content.res.Resources.Theme +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.furintiture.R +import com.example.furintiture.ui.theme.FurintitureTheme +import com.example.furintiture.ui.theme.Primary + +@Composable +fun FurnitureButton( + text: String, + modifier: Modifier = Modifier, + onClick : () -> Unit, + ){ + Button( + modifier = modifier + .fillMaxWidth() + .height(43.dp) + .clip(shape = RoundedCornerShape(8.dp)) + , + colors = ButtonDefaults.buttonColors().copy( + containerColor = Primary, + contentColor = Color.White, + disabledContainerColor = Primary, + disabledContentColor = Color.White + ), + onClick = onClick + ) { + Text( + text = text, + style = TextStyle( + fontFamily = FontFamily(Font(R.font.manrope)), + fontWeight = FontWeight.Bold, + fontSize = 16.sp + ) + ) + } +} + +@Preview +@Composable +fun FurniturePreview(){ + FurnitureButton( + "Войти" + ){ + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/common/FurnitureCard.kt b/app/src/main/java/com/example/furintiture/ui/common/FurnitureCard.kt new file mode 100644 index 0000000..de3b0b2 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/common/FurnitureCard.kt @@ -0,0 +1,142 @@ +package com.example.furintiture.ui.common + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +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.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade +import com.example.furintiture.R +import com.example.furintiture.model.response.FurnitureCategoryResponse +import com.example.furintiture.model.response.FurnitureResponse +import com.example.furintiture.ui.theme.Neutral_10 +import com.example.furintiture.ui.theme.Neutral_20 +import java.math.BigDecimal + +@Composable +fun FurnitureCard( + furnitureResponse: FurnitureResponse, + onFurnitureClick: (FurnitureResponse) -> Unit +){ + Column( + modifier = Modifier + .wrapContentHeight() + .width(150.dp) + .background(color = Neutral_10, shape = RoundedCornerShape(14.dp)) + .padding(16.dp) + .clickable { + onFurnitureClick(furnitureResponse) + }, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box( + modifier = Modifier.wrapContentSize() + ) { + AsyncImage( + modifier = Modifier + .size(120.dp, 120.dp) + , + model = ImageRequest + .Builder(LocalContext.current) + .data(furnitureResponse.url) + .crossfade(true) + .build(), + placeholder = painterResource(R.drawable.images), + contentScale = ContentScale.FillWidth, + contentDescription = null + ) + Text( + text = "${furnitureResponse.sale.multiply(BigDecimal(100)).toInt()}% OFF", + modifier = Modifier + .align(Alignment.BottomStart) + .background( + color = Color.Red, + shape = RoundedCornerShape(10.dp) + ) + .padding( + vertical = 4.dp, + horizontal = 6.dp + ), + style = TextStyle( + color = Color.White, + fontWeight = FontWeight.SemiBold, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 10.sp + ) + ) + } + Spacer(modifier = Modifier.height(4.dp)) + Column( + modifier = Modifier.wrapContentHeight() + ) { + Text( + text = furnitureResponse.name, + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 14.sp + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = "${furnitureResponse.price} ₽", + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 20.sp, + fontWeight = FontWeight.Bold + ) + ) + } + } +} + +@Composable +@Preview +fun FurnitureCardPreview(){ + val furnitureResponse = FurnitureResponse( + id = 1, + name = "Стул", + price = BigDecimal("1000.5"), + description = "!@3", + sale = BigDecimal("0.40"), + url = "https://laurendanger.com/wp-content/uploads/sites/33/2024/10/IMG_3826.jpeg", + category = FurnitureCategoryResponse( + id = 1, + name = "!@3" + ), + shopCategories = emptyList() + ) + FurnitureCard(furnitureResponse){ + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/common/FurnitureCartCard.kt b/app/src/main/java/com/example/furintiture/ui/common/FurnitureCartCard.kt new file mode 100644 index 0000000..bcebb07 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/common/FurnitureCartCard.kt @@ -0,0 +1,114 @@ +package com.example.furintiture.ui.common + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade +import com.example.furintiture.R +import com.example.furintiture.model.response.CartResponse +import com.example.furintiture.ui.theme.Neutral_10 +import java.math.BigDecimal + +@Composable +fun FurnitureCartCard(cartResponse: CartResponse) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .background(color = Neutral_10, shape = RoundedCornerShape(16.dp)) + .padding(16.dp) + ) { + AsyncImage( + modifier = Modifier + .size(100.dp, 100.dp) + , + model = ImageRequest + .Builder(LocalContext.current) + .data(cartResponse.furnitureResponse.url) + .crossfade(true) + .build(), + placeholder = painterResource(R.drawable.images), + contentScale = ContentScale.FillWidth, + contentDescription = null + ) + Spacer(Modifier.width(16.dp)) + Column( + modifier = Modifier + .fillMaxSize() + .fillMaxHeight() + ) { + Text( + text = cartResponse.furnitureResponse.name, + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 24.sp + ) + ) + Text( + text = "${cartResponse.furnitureResponse.price} ₽", + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 36.sp, + fontWeight = FontWeight.Bold + ) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = "${cartResponse.furnitureResponse.sale.multiply(BigDecimal(100)).toInt()}% OFF", + modifier = Modifier + .background( + color = Color.Red, + shape = RoundedCornerShape(10.dp) + ) + .padding( + vertical = 4.dp, + horizontal = 6.dp + ), + style = TextStyle( + color = Color.White, + fontWeight = FontWeight.SemiBold, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 16.sp + ) + ) + Text( + text = "Количество: ${cartResponse.count}", + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 24.sp + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/common/FurnitureHorizontalList.kt b/app/src/main/java/com/example/furintiture/ui/common/FurnitureHorizontalList.kt new file mode 100644 index 0000000..8950ded --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/common/FurnitureHorizontalList.kt @@ -0,0 +1,79 @@ +package com.example.furintiture.ui.common + +import androidx.compose.foundation.clickable +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.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.capitalize +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.furintiture.R +import com.example.furintiture.model.response.FurnitureResponse +import com.example.furintiture.model.response.ShopCategoryResponse +import com.example.furintiture.ui.theme.Primary + +@Composable +fun FurnitureHorizontalList( + categoryName: String, categoryFurnitureList: List, + onAllFurnitureClick: () -> Unit, + onFurnitureClick: (FurnitureResponse) -> Unit, +) { + Column( + modifier = Modifier.height(300.dp).fillMaxWidth() + ) { + Row( + modifier = + Modifier.height(30.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.weight(1f), + text = categoryName.replaceFirstChar { it.titlecaseChar() }, + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 20.sp, + fontWeight = FontWeight.Bold) + ) + Text( + modifier = Modifier.clickable { + onAllFurnitureClick() + }, + text = "Больше товаров", + style = TextStyle( + color = Primary, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + ) + } + Spacer(modifier = Modifier.height(16.dp)) + LazyRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(categoryFurnitureList.size){ item -> + FurnitureCard(categoryFurnitureList[item]){ furniture -> + onFurnitureClick(furniture) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/common/FurnitureOrderCard.kt b/app/src/main/java/com/example/furintiture/ui/common/FurnitureOrderCard.kt new file mode 100644 index 0000000..9022b37 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/common/FurnitureOrderCard.kt @@ -0,0 +1,100 @@ +package com.example.furintiture.ui.common + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.furintiture.R +import com.example.furintiture.model.response.OrderResponse +import com.example.furintiture.ui.theme.Primary +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.format +import kotlinx.datetime.format.DateTimeComponents +import kotlinx.datetime.format.DateTimeFormat +import kotlinx.datetime.format.DateTimeFormatBuilder +import kotlinx.datetime.format.byUnicodePattern + +@Composable +fun FurnitureOrderCard(orderResponse: OrderResponse) { + + Column( + modifier = Modifier + .fillMaxWidth() + ) { + Row( + modifier = + Modifier.height(30.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.weight(1f), + text = "Номер заказа: ${orderResponse.id}", + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 20.sp, + fontWeight = FontWeight.Bold) + ) + Text( + text = "Статус заказа: ${orderResponse.orderStatus.name}", + style = TextStyle( + color = Primary, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + ) + } + val format = LocalDateTime.Format { + byUnicodePattern("uuuu-MM-dd HH:mm") + } + Text( + text = "Дата заказа: ${format.format(orderResponse.dateTime)}", + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 14.sp + ) + ) + Text( + text = "Итого: ${orderResponse.orderTotalSum}", + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 14.sp + ) + ) + Text( + text = "По адресу: ${orderResponse.addressResponse.address}", + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 14.sp + ) + ) + LazyRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(orderResponse.orderSet){ orderSet -> + FurnitureCard(orderSet.furnitureResponse) { + + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/FurnitureGlobalNavigation.kt b/app/src/main/java/com/example/furintiture/ui/screen/FurnitureGlobalNavigation.kt new file mode 100644 index 0000000..392ff7e --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/FurnitureGlobalNavigation.kt @@ -0,0 +1,132 @@ +package com.example.furintiture.ui.screen + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.example.furintiture.configure.UuidSerializer +import com.example.furintiture.data.DataStoreSettings +import com.example.furintiture.ui.screen.cart.CartScreen +import com.example.furintiture.ui.screen.detail.DetailScreen +import com.example.furintiture.ui.screen.furniture.FurnitureScreen +import com.example.furintiture.ui.screen.login.LoginScreen +import com.example.furintiture.ui.screen.main.MainScreen +import com.example.furintiture.ui.screen.order.OrderScreen +import com.example.furintiture.ui.screen.registration.RegistrationScreen +import com.example.furintiture.ui.screen.splashscreen.SplashScreen +import kotlinx.serialization.Serializable +import java.util.UUID + +@Composable +fun FurnitureGlobalNavigation( + dataStoreSettings: DataStoreSettings, + navHostController: NavHostController, + onBottomBarVisibleEvent: (Boolean) -> Unit +) { + NavHost( + modifier = Modifier, + navController = navHostController, + startDestination = Splash + ){ + composable { + SplashScreen() + onBottomBarVisibleEvent(false) + } + composable { + RegistrationScreen(dataStoreSettings){ + navHostController.navigate(Login) + } + onBottomBarVisibleEvent(false) + + } + composable { + LoginScreen(dataStoreSettings){ + navHostController.navigate(Registration) + } + onBottomBarVisibleEvent(false) + + } + + composable{ navBackStackEntry -> + val detail: Detail = navBackStackEntry.toRoute() + DetailScreen(detail){ + navHostController.navigateUp() + } + onBottomBarVisibleEvent(false) + + } + composable{navBackStackEntry -> + val furniture: Furniture = navBackStackEntry.toRoute() + + FurnitureScreen(uuid = furniture.uuid, onClosePressed = { + navHostController.navigateUp() + }) { + navHostController.navigate(it) + } + onBottomBarVisibleEvent(false) + } + composable{ navBackStackEntry -> + val cart: Cart = navBackStackEntry.toRoute() + CartScreen(cart.uuid) + } + composable{ navBackStackEntry -> + val order: Order = navBackStackEntry.toRoute() + OrderScreen(order.uuid) + } + composable
{ navBackStackEntry -> + val main: Main = navBackStackEntry.toRoute() + MainScreen( + main.uuid, + onNavigateToFurniture = { furniture -> + navHostController.navigate(furniture) + } + ){ detail -> + navHostController.navigate(detail) + } + onBottomBarVisibleEvent(true) + } + + } +} + + +@Serializable +data class Main( + val uuid: String +) + + + +@Serializable +data class Detail( + val furnitureId: Long, + val uuid: String +) + +@Serializable +data class Cart( + val uuid: String +) + +@Serializable +data class Order( + val uuid: String +) + +@Serializable +object Login + + +@Serializable +data class Furniture( + val uuid: String +) + + +@Serializable +object Registration + +@Serializable +object Splash \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/bottomsheet/FurnitureAddressBottomSheet.kt b/app/src/main/java/com/example/furintiture/ui/screen/bottomsheet/FurnitureAddressBottomSheet.kt new file mode 100644 index 0000000..6975355 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/bottomsheet/FurnitureAddressBottomSheet.kt @@ -0,0 +1,135 @@ +package com.example.furintiture.ui.screen.bottomsheet + +import androidx.compose.foundation.layout.Column +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.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.furintiture.R +import com.example.furintiture.model.request.AddAddressRequest +import com.example.furintiture.model.response.AddressResponse +import com.example.furintiture.ui.common.BasieFurnitureTextField +import com.example.furintiture.ui.common.FurnitureButton +import com.example.furintiture.ui.theme.Neutral_90 +import kotlinx.coroutines.flow.MutableStateFlow + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FurnitureAddressBottomSheet( + addressResponse: AddressResponse?, + viewModel: FurnitureAddressBottomSheetViewModel, + createAddressCallback: (AddAddressRequest) -> Unit, +) { + val state = viewModel.furnitureAddressState.collectAsState() + LaunchedEffect(addressResponse) { + if (addressResponse != null){ + viewModel.setAddress(address = addressResponse.address) + viewModel.setFloor(floor = addressResponse.floor.toString()) + viewModel.setApartment(apartment = addressResponse.apartment.toString()) + viewModel.setComment(comment = addressResponse.comment ?: "") + } + } + ModalBottomSheet( + onDismissRequest = { + createAddressCallback(state.value) + } + ){ + Column( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + BasieFurnitureTextField( + value = state.value.address, + onValueChange = {viewModel.setAddress(it)}, + textColor = Neutral_90, + label = { + Text( + "Адрес", + color = Neutral_90, + style = TextStyle( + color = Neutral_90, + fontSize = 15.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ) + ) + } + ) + Spacer(Modifier.height(16.dp)) + Row( + modifier = Modifier.fillMaxWidth() + ){ + BasieFurnitureTextField( + modifier = Modifier.weight(0.5f), + value = if(state.value.floor == null) "" else state.value.floor.toString(), + onValueChange = {viewModel.setFloor(it)}, + textColor = Neutral_90, + label = { + Text( + "Этаж", + color = Neutral_90, + style = TextStyle( + color = Neutral_90, + fontSize = 15.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ) + ) + } + ) + BasieFurnitureTextField( + modifier = Modifier.weight(0.5f), + value = if(state.value.apartment == null) "" else state.value.apartment.toString(), + onValueChange = {viewModel.setApartment(it)}, + textColor = Neutral_90, + label = { + Text( + "Квартира", + color = Neutral_90, + style = TextStyle( + color = Neutral_90, + fontSize = 15.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ) + ) + } + ) + } + Spacer(Modifier.height(16.dp)) + BasieFurnitureTextField( + value = state.value.comment ?: "", + onValueChange = {viewModel.setComment(it)}, + textColor = Neutral_90, + label = { + Text( + "Комментарий курьеру", + color = Neutral_90, + style = TextStyle( + color = Neutral_90, + fontSize = 15.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ) + ) + } + ) + Spacer(Modifier.height(16.dp)) + val text = if(addressResponse != null) "Изменить" else "Добавить" + FurnitureButton( + text = text + ) { + createAddressCallback(state.value) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/bottomsheet/FurnitureAddressBottomSheetViewModel.kt b/app/src/main/java/com/example/furintiture/ui/screen/bottomsheet/FurnitureAddressBottomSheetViewModel.kt new file mode 100644 index 0000000..16e279e --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/bottomsheet/FurnitureAddressBottomSheetViewModel.kt @@ -0,0 +1,33 @@ +package com.example.furintiture.ui.screen.bottomsheet + +import androidx.lifecycle.ViewModel +import com.example.furintiture.model.request.AddAddressRequest +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update + +class FurnitureAddressBottomSheetViewModel: ViewModel() { + private val _furnitureAddressState = MutableStateFlow(AddAddressRequest()) + val furnitureAddressState = _furnitureAddressState.asStateFlow() + + fun setAddress(address: String){ + _furnitureAddressState.update { + it.copy(address = address) + } + } + fun setFloor(floor: String){ + _furnitureAddressState.update { + it.copy(floor = floor.toIntOrNull()) + } + } + fun setApartment(apartment: String){ + _furnitureAddressState.update { + it.copy(apartment = apartment.toIntOrNull()) + } + } + fun setComment(comment: String){ + _furnitureAddressState.update { + it.copy(comment = comment) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/cart/CartScreen.kt b/app/src/main/java/com/example/furintiture/ui/screen/cart/CartScreen.kt new file mode 100644 index 0000000..6a2c615 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/cart/CartScreen.kt @@ -0,0 +1,118 @@ +package com.example.furintiture.ui.screen.cart + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.furintiture.R +import com.example.furintiture.ui.common.FurnitureButton +import com.example.furintiture.ui.common.FurnitureCartCard +import com.example.furintiture.ui.theme.Neutral_20 +import kotlinx.coroutines.launch +import java.math.BigDecimal + +@Composable +fun CartScreen( + uuid: String +) { + val snackbarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + val viewModel: CartScreenViewModel = viewModel { CartScreenViewModel(uuid) } + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + contentColor = Neutral_20 + ) { paddingValues -> + CartScreenContent(viewModel, paddingValues){ + coroutineScope.launch { + snackbarHostState.showSnackbar(it) + } + } + } +} + +@Composable +fun CartScreenContent( + viewModel: CartScreenViewModel, + paddingValues: PaddingValues, + onErrorCallback : (String) -> Unit +){ + val state = viewModel.cartScreenState.collectAsState() + val totalSum = remember { derivedStateOf { + state.value.cartResponse.sumOf { cart -> + BigDecimal(cart.count) * cart.furnitureResponse.price + } + }} + + LaunchedEffect(state.value.error) { + state.value.error?.let(onErrorCallback) + state.value.error = null + } + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(horizontal = 16.dp) + .padding(bottom = 100.dp) + + + ) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(state.value.cartResponse.size){ item -> + FurnitureCartCard(state.value.cartResponse[item]) + } + if(totalSum.value > BigDecimal(0)){ + item { + Spacer(Modifier.height(16.dp)) + Text( + "Итого: ${totalSum.value}", + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 36.sp, + fontWeight = FontWeight.Bold + ) + ) + } + item { + Spacer(Modifier.height(16.dp)) + FurnitureButton( + modifier = Modifier.height(60.dp), + text = "Оформить заказ" + ) { + viewModel.createOrder() + } + } + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/cart/CartScreenState.kt b/app/src/main/java/com/example/furintiture/ui/screen/cart/CartScreenState.kt new file mode 100644 index 0000000..d0b02ee --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/cart/CartScreenState.kt @@ -0,0 +1,8 @@ +package com.example.furintiture.ui.screen.cart + +import com.example.furintiture.model.response.CartResponse + +data class CartScreenState( + var cartResponse: List = emptyList(), + var error: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/cart/CartScreenViewModel.kt b/app/src/main/java/com/example/furintiture/ui/screen/cart/CartScreenViewModel.kt new file mode 100644 index 0000000..747edc5 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/cart/CartScreenViewModel.kt @@ -0,0 +1,80 @@ +package com.example.furintiture.ui.screen.cart + +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.furintiture.data.RetrofitClient +import com.example.furintiture.model.request.CreateOrderItemRequest +import com.example.furintiture.model.request.CreateOrderRequest +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import java.math.BigDecimal +import java.util.UUID + +class CartScreenViewModel(private val uuid: String): ViewModel() { + private val _cartScreenState = MutableStateFlow(CartScreenState()) + val cartScreenState = _cartScreenState.asStateFlow() + val totalSum = derivedStateOf { cartScreenState.value.cartResponse.sumOf { cart -> + BigDecimal(cart.count) * cart.furnitureResponse.price } + } + init { + try { + init() + } + catch (e: Exception){ + _cartScreenState.update { + it.copy(error = e.message) + } + } + } + fun createOrder(){ + viewModelScope.launch { + try { + val addresId = RetrofitClient.api.getProfileByUuid(UUID.fromString(uuid)).address?.addressId + if (addresId == null){ + _cartScreenState.update { + it.copy(error = "Нельзя сделать заказ без адрес") + } + return@launch + } + val createOrderRequest = CreateOrderRequest( + userUuid = UUID.fromString(uuid), + addressId = addresId.toLong(), + orderStatus = 1, + orderTotalSum = cartScreenState.value.cartResponse.sumOf { cart -> + BigDecimal(cart.count) * cart.furnitureResponse.price + }, + orderSet = cartScreenState.value.cartResponse.map { + CreateOrderItemRequest( + furnitureId = it.furnitureResponse.id, + furniturePrice = it.furnitureResponse.price, + count = it.count + ) + } + ) + RetrofitClient.api.createOrder(createOrderRequest) + _cartScreenState.update { + it.copy(cartResponse = emptyList()) + } + } + catch (e: Exception){ + _cartScreenState.update { + it.copy(error = e.message) + } + } + + } + } + private fun init(){ + + viewModelScope.launch { + val cart = RetrofitClient.api.getCartByUuid(UUID.fromString(uuid)) + _cartScreenState.update { + it.copy(cartResponse = cart) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/detail/DetailScreen.kt b/app/src/main/java/com/example/furintiture/ui/screen/detail/DetailScreen.kt new file mode 100644 index 0000000..956ba9c --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/detail/DetailScreen.kt @@ -0,0 +1,216 @@ +package com.example.furintiture.ui.screen.detail + +import androidx.compose.foundation.background +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Button +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.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade +import com.example.furintiture.R +import com.example.furintiture.ui.common.FurnitureButton +import com.example.furintiture.ui.screen.Detail +import com.example.furintiture.ui.theme.Neutral_10 +import com.example.furintiture.ui.theme.Neutral_20 +import com.example.furintiture.ui.theme.Neutral_40 +import kotlinx.coroutines.launch +import java.math.BigDecimal + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DetailScreen( + detail: Detail, + onClosePressed: () -> Unit +) { + val snackbarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + val viewModel = viewModel { + DetailScreenViewModel(detail.furnitureId, detail.uuid) + } + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + topBar = { + TopAppBar( + title = {}, + navigationIcon = { + IconButton ( + onClick = { + onClosePressed() + } + ){ + Icon( + imageVector = Icons.Default.Close, + contentDescription = null + ) + } + } + ) + }, + bottomBar = { + Column( + modifier = Modifier + .fillMaxWidth() + .background(color = Neutral_20, shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)) + .padding(20.dp) + ) { + FurnitureButton( + modifier = Modifier.height(60.dp), + text = "Добавить в корзину", + ) { + viewModel.addToCart() + } + } + }, + contentColor = Neutral_20 + ) { paddingValues -> + DetailScreenContent(viewModel, paddingValues){ + coroutineScope.launch { + snackbarHostState.showSnackbar(message = it) + } + } + } +} + +@Composable +fun DetailScreenContent( + detailScreenViewModel: DetailScreenViewModel, + paddingValues: PaddingValues, + errorCallback: (String) -> Unit + +){ + val state = detailScreenViewModel.detailScreenState.collectAsState() + LaunchedEffect(state.value.error) { + state.value.error?.let(errorCallback) + } + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .background(color = Neutral_20) + , + verticalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.height(16.dp)) + AsyncImage( + modifier = Modifier + .size(350.dp, 350.dp) + .align(Alignment.CenterHorizontally) + , + model = ImageRequest + .Builder(LocalContext.current) + .data(state.value.furnitureResponse.url) + .crossfade(true) + .build(), + placeholder = painterResource(R.drawable.images), + contentScale = ContentScale.FillWidth, + contentDescription = null + ) + Spacer(modifier = Modifier.height(16.dp)) + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.White, shape = RoundedCornerShape( + topStart = 16.dp, + topEnd = 16.dp) + ) + .weight(1f) + .padding(horizontal = 20.dp, vertical = 10.dp) + ) { + Text( + text = state.value.furnitureResponse.name, + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 28.sp + ) + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${state.value.furnitureResponse.price} ₽", + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 40.sp, + fontWeight = FontWeight.Bold + ) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = "${state.value.furnitureResponse.sale.multiply(BigDecimal(100)).toInt()}% OFF", + modifier = Modifier + .background( + color = Color.Red, + shape = RoundedCornerShape(10.dp) + ) + .padding( + vertical = 4.dp, + horizontal = 6.dp + ), + style = TextStyle( + color = Color.White, + fontWeight = FontWeight.SemiBold, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 20.sp + ) + ) + } + Text( + text = state.value.furnitureResponse.description, + style = TextStyle( + color = com.example.furintiture.ui.theme.Text, + fontWeight = FontWeight.Normal, + fontFamily = FontFamily(Font(R.font.manrope)), + fontSize = 18.sp + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/detail/DetailScreenState.kt b/app/src/main/java/com/example/furintiture/ui/screen/detail/DetailScreenState.kt new file mode 100644 index 0000000..caca45a --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/detail/DetailScreenState.kt @@ -0,0 +1,22 @@ +package com.example.furintiture.ui.screen.detail + +import com.example.furintiture.model.response.FurnitureCategoryResponse +import com.example.furintiture.model.response.FurnitureResponse +import java.math.BigDecimal + +data class DetailScreenState( + val furnitureResponse: FurnitureResponse = FurnitureResponse( + id = 1, + name = "Стул", + price = BigDecimal("1000.5"), + description = "Этот стул выполнен из натурального дуба, что придает ему прочность и долговечность. Спинка украшена резными узорами, напоминающими старинные европейские интерьеры. Сиденье обито мягкой тканью с антикварным орнаментом, обеспечивая комфорт во время сидения. Ножки слегка изогнуты, что добавляет изделию элегантности. Такой стул идеально впишется в гостиную или столовую в классическом стиле, создавая атмосферу уюта и респектабельности.", + sale = BigDecimal("0.40"), + url = "https://laurendanger.com/wp-content/uploads/sites/33/2024/10/IMG_3826.jpeg", + category = FurnitureCategoryResponse( + id = 1, + name = "!@3" + ), + shopCategories = emptyList() + ), + var error: String? = null +) diff --git a/app/src/main/java/com/example/furintiture/ui/screen/detail/DetailScreenViewModel.kt b/app/src/main/java/com/example/furintiture/ui/screen/detail/DetailScreenViewModel.kt new file mode 100644 index 0000000..ced33a5 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/detail/DetailScreenViewModel.kt @@ -0,0 +1,56 @@ +package com.example.furintiture.ui.screen.detail + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.furintiture.data.RetrofitClient +import com.example.furintiture.model.request.AddToCartRequest +import com.example.furintiture.model.request.ChangeCountFromCartRequest +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import java.util.UUID + +class DetailScreenViewModel(private val furnitureId: Long, private val uuidUser: String): ViewModel() { + private val _detailScreenState = MutableStateFlow(DetailScreenState()) + val detailScreenState = _detailScreenState.asStateFlow() + init { + init() + } + + fun addToCart(){ + viewModelScope.launch { + try { + RetrofitClient.api.addToCartByUuid( + AddToCartRequest( + uuid = UUID.fromString(uuidUser), + furnitureId = furnitureId, + count = 1 + ) + ) + _detailScreenState.update { + it.copy(error = "Успешно добавлено") + } + } + catch (e: Exception){ + _detailScreenState.update { + it.copy(error = e.message) + } + } + } + } + + private fun init(){ + viewModelScope.launch { + try { + val result = RetrofitClient.api.getFurnitureById(furnitureId) + _detailScreenState.update { + it.copy(furnitureResponse = result) + } + } + catch (e: Exception){ + println(e.message) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/furniture/FurnitureScreen.kt b/app/src/main/java/com/example/furintiture/ui/screen/furniture/FurnitureScreen.kt new file mode 100644 index 0000000..2b7b83d --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/furniture/FurnitureScreen.kt @@ -0,0 +1,109 @@ +package com.example.furintiture.ui.screen.furniture + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +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.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.furintiture.ui.common.FurnitureCard +import com.example.furintiture.ui.screen.Detail +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun FurnitureScreen( + uuid: String, + onClosePressed: () -> Unit, + onNavigateToDetail: (Detail) -> Unit +) { + val snackbarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + val viewModel = viewModel { + FurnitureScreenViewModel() + } + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + topBar = { + TopAppBar( + title = {}, + navigationIcon = { + IconButton ( + onClick = { + onClosePressed() + } + ){ + Icon( + imageVector = Icons.Default.Close, + contentDescription = null + ) + } + } + ) + }, + ) { paddingValues -> + FurnitureContent(uuid, viewModel, paddingValues, onNavigateToDetail){ + coroutineScope.launch { + snackbarHostState.showSnackbar(message = it) + } + } + } +} + +@Composable +fun FurnitureContent( + uuid: String, + furnitureScreenViewModel: FurnitureScreenViewModel, + paddingValues: PaddingValues, + onNavigateToDetail: (Detail) -> Unit, + onErrorCallback: (String) -> Unit +){ + val state = furnitureScreenViewModel.furnitureScreenState.collectAsState() + + LaunchedEffect(state.value.error) { + state.value.error?.let(onErrorCallback) + state.value.error = null + } + + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + LazyVerticalGrid ( + modifier = Modifier + .fillMaxSize(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + columns = GridCells.Adaptive(minSize = 130.dp) + ){ + items(state.value.furnitureList){ it -> + FurnitureCard(it) { + onNavigateToDetail(Detail(uuid = uuid, furnitureId = it.id)) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/furniture/FurnitureScreenState.kt b/app/src/main/java/com/example/furintiture/ui/screen/furniture/FurnitureScreenState.kt new file mode 100644 index 0000000..3625d35 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/furniture/FurnitureScreenState.kt @@ -0,0 +1,8 @@ +package com.example.furintiture.ui.screen.furniture + +import com.example.furintiture.model.response.FurnitureResponse + +data class FurnitureScreenState( + var furnitureList: List = emptyList(), + var error: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/furniture/FurnitureScreenViewModel.kt b/app/src/main/java/com/example/furintiture/ui/screen/furniture/FurnitureScreenViewModel.kt new file mode 100644 index 0000000..fde52b3 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/furniture/FurnitureScreenViewModel.kt @@ -0,0 +1,34 @@ +package com.example.furintiture.ui.screen.furniture + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.furintiture.data.RetrofitClient +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class FurnitureScreenViewModel: ViewModel(){ + private val _furnitureScreenState = MutableStateFlow(FurnitureScreenState()) + val furnitureScreenState = _furnitureScreenState.asStateFlow() + + init { + try { + init() + } + catch (e: Exception){ + _furnitureScreenState.update { + it.copy(error = e.message) + } + } + } + private fun init(){ + viewModelScope.launch { + val result = RetrofitClient.api.getAllFurniture() + _furnitureScreenState.update { + it.copy(furnitureList = result) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/login/LoginScreen.kt b/app/src/main/java/com/example/furintiture/ui/screen/login/LoginScreen.kt new file mode 100644 index 0000000..4210034 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/login/LoginScreen.kt @@ -0,0 +1,146 @@ +package com.example.furintiture.ui.screen.login + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.furintiture.R +import com.example.furintiture.data.DataStoreSettings +import com.example.furintiture.ui.common.FurnitureButton +import com.example.furintiture.ui.common.FurniturePasswordTextField +import com.example.furintiture.ui.common.FurnitureTextField +import com.example.furintiture.ui.theme.Neutral_20 +import com.example.furintiture.ui.theme.Neutral_70 +import com.example.furintiture.ui.theme.Primary +import kotlinx.coroutines.launch + +@Composable +fun LoginScreen( + dataStoreSettings: DataStoreSettings, + onNavigateToRegistration: () -> Unit +) { + val snackbarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + val viewModel = viewModel { LoginScreenViewModel(dataStoreSettings) } + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + contentColor = Neutral_20 + ) { paddingValues -> + LoginScreenContent(viewModel, paddingValues, onNavigateToRegistration){ + coroutineScope.launch { + snackbarHostState.showSnackbar(message = it) + } + } + } +} + +@Composable +fun LoginScreenContent( + loginScreenViewModel: LoginScreenViewModel, + paddingValues: PaddingValues, + onNavigateToRegistration: () -> Unit, + errorCallback: (String) -> Unit +){ + val state = loginScreenViewModel.loginScreenState.collectAsState() + + LaunchedEffect(state.value.error) { + state.value.error?.let { + errorCallback(it) + } + state.value.error = null + } + + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = "C возвращением!", + style = TextStyle( + fontSize = 32.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope_extrabold)) + ), + color = Color.Black + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Введите свой email, чтобы начать делать покупки и получать выгодные предложения уже сегодня!", + style = TextStyle( + fontSize = 16.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ), + color = Neutral_70 + ) + Spacer(modifier = Modifier.height(32.dp)) + FurnitureTextField( + value = state.value.request.email, + onValueChange = { loginScreenViewModel.setEmail(it) }, + textLabel = "Email", + leadingResourceId = R.drawable.mail_icon + ) + Spacer(modifier = Modifier.height(16.dp)) + FurniturePasswordTextField( + value = state.value.request.password, + onValueChange = { loginScreenViewModel.setPassword(it) } + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "Забыли пароль?", + style = TextStyle( + fontSize = 16.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ), + color = Primary + ) + Spacer(modifier = Modifier.height(16.dp)) + FurnitureButton( + text = "Войти" + ) { + loginScreenViewModel.login() + } + Spacer(modifier = Modifier.height(16.dp)) + Text( + modifier = Modifier.clickable { + onNavigateToRegistration() + }, + text = "У вас нет учетной записи? Зарегистрируйтесь", + style = TextStyle( + fontSize = 16.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ), + color = Neutral_70 + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/login/LoginScreenState.kt b/app/src/main/java/com/example/furintiture/ui/screen/login/LoginScreenState.kt new file mode 100644 index 0000000..4cd59d2 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/login/LoginScreenState.kt @@ -0,0 +1,10 @@ +package com.example.furintiture.ui.screen.login + +import android.util.Log +import androidx.compose.runtime.Composable +import com.example.furintiture.model.request.LoginRequest + +data class LoginScreenState( + var request: LoginRequest = LoginRequest(email = "", password = ""), + var error: String? = null +) diff --git a/app/src/main/java/com/example/furintiture/ui/screen/login/LoginScreenViewModel.kt b/app/src/main/java/com/example/furintiture/ui/screen/login/LoginScreenViewModel.kt new file mode 100644 index 0000000..b6608ae --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/login/LoginScreenViewModel.kt @@ -0,0 +1,55 @@ +package com.example.furintiture.ui.screen.login + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.furintiture.data.DataStoreSettings +import com.example.furintiture.data.RetrofitClient +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class LoginScreenViewModel(private val dataStoreSettings: DataStoreSettings): ViewModel() { + private val _loginScreenState = MutableStateFlow(LoginScreenState()) + val loginScreenState = _loginScreenState.asStateFlow() + + fun setPassword(password: String){ + _loginScreenState.update { + it.copy(request = it.request.copy(password = password)) + } + } + fun setEmail(email: String){ + _loginScreenState.update { + it.copy(request = it.request.copy(email = email)) + } + } + fun login(){ + if(!validate()) return + viewModelScope.launch { + try { + val result = RetrofitClient.api.auth(loginScreenState.value.request) + dataStoreSettings.setUuid(result.userUuid) + } + catch (e: Exception){ + _loginScreenState.update { + it.copy(error = e.message) + } + } + } + } + private fun validate(): Boolean{ + if (_loginScreenState.value.request.email.isEmpty()){ + _loginScreenState.update { + it.copy(error = "Введите email") + } + return false + } + if (_loginScreenState.value.request.password.isEmpty()){ + _loginScreenState.update { + it.copy(error = "Введите password") + } + return false + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/main/MainScreen.kt b/app/src/main/java/com/example/furintiture/ui/screen/main/MainScreen.kt new file mode 100644 index 0000000..b8203c0 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/main/MainScreen.kt @@ -0,0 +1,246 @@ +package com.example.furintiture.ui.screen.main + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.motionEventSpy +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade +import com.example.furintiture.R +import com.example.furintiture.ui.screen.bottomsheet.FurnitureAddressBottomSheet +import com.example.furintiture.ui.common.FurnitureHorizontalList +import com.example.furintiture.ui.screen.Detail +import com.example.furintiture.ui.screen.Furniture +import com.example.furintiture.ui.screen.bottomsheet.FurnitureAddressBottomSheetViewModel +import com.example.furintiture.ui.theme.Neutral_20 +import com.example.furintiture.ui.theme.Neutral_40 +import com.example.furintiture.ui.theme.Primary +import com.example.furintiture.ui.theme.gradientColors +import kotlinx.coroutines.launch + +@Composable +fun MainScreen( + uuid: String, + onNavigateToFurniture: (Furniture) -> Unit, + onNavigateToDetail: (Detail) -> Unit, +) { + val snackbarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + val viewModel: MainScreenViewModel = viewModel { + MainScreenViewModel(uuid) + } + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + contentColor = Neutral_20 + ) { paddingValues -> + MainScreenContent(uuid, viewModel, paddingValues, onNavigateToFurniture, onNavigateToDetail ){ + coroutineScope.launch { + snackbarHostState.showSnackbar(message = it) + } + } + } +} + +@Composable +fun MainScreenContent( + userUuid: String, + mainScreenViewModel: MainScreenViewModel, + paddingValues: PaddingValues, + onNavigateToFurniture: (Furniture) -> Unit, + onNavigateToDetail: (Detail) -> Unit, + errorCallback: (String) -> Unit, +) { + val state = mainScreenViewModel.mainScreenState.collectAsState() + val pagerState = rememberPagerState(pageCount = {state.value.sales.size}) + val showBottomSheet = remember { mutableStateOf(false) } + val viewModel: FurnitureAddressBottomSheetViewModel = viewModel() + LaunchedEffect(state.value.error) { + state.value.error?.let { + errorCallback(it) + } + state.value.error = null + } + + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(bottom = 100.dp,) + ) { + Spacer(modifier = Modifier.height(16.dp)) + state.value.profile?.let { + val text = if(it.address != null) "Доставим по адресу: ${it.address.address}" else "Выберите адрес доставки" + Text( + modifier = + Modifier.padding(horizontal = 16.dp) + .clickable { + showBottomSheet.value = true + } + , + text = text, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = TextStyle( + fontSize = 16.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope_extrabold)), + color = com.example.furintiture.ui.theme.Text + ) + ) + Spacer(modifier = Modifier.height(16.dp)) + } + if(showBottomSheet.value){ + FurnitureAddressBottomSheet( + state.value.profile?.address, viewModel + ) { requestAddress -> + mainScreenViewModel.addAddress(requestAddress) + showBottomSheet.value = false + } + } + HorizontalPager(state = pagerState) { page -> + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + .padding(top = 20.dp) + , + ) { + AsyncImage( + modifier = Modifier + .fillMaxSize() + , + model = ImageRequest + .Builder(LocalContext.current) + .data(state.value.sales[page].url) + .crossfade(true) + .build(), + placeholder = painterResource(R.drawable.images), + contentScale = ContentScale.FillWidth, + contentDescription = null + ) + Box( + modifier = Modifier + .fillMaxSize() + .background(Brush.horizontalGradient(colorStops = gradientColors))) + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start, + modifier = Modifier + .padding(10.dp) + ) { + Text( + text = state.value.sales[page].name, + style = TextStyle( + fontSize = 16.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope_extrabold)) + ), + color = Neutral_20 + ) + Button( + onClick = {}, + colors = ButtonDefaults.buttonColors().copy( + contentColor = Color.White, + disabledContentColor = Color.White, + containerColor = Color.White, + disabledContainerColor = Color.White + ) + ) { + Text( + "Купить сейчас", + style = TextStyle( + fontSize = 16.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope)), + fontWeight = FontWeight.Bold + ), + color = Primary + ) + } + } + + } + + } + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.Center + ) { + repeat(pagerState.pageCount) { iteration -> + val color = if (pagerState.currentPage == iteration) Primary else Neutral_40 + Box( + modifier = Modifier + .padding(2.dp) + .clip(CircleShape) + .background(color) + .size(12.dp) + ) + } + } + Spacer(modifier = Modifier.height(16.dp)) + LazyColumn( + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(16.dp) + ) { + items(state.value.shopCategories.size){ item -> + FurnitureHorizontalList( + categoryName = state.value.shopCategories[item].name, + categoryFurnitureList = state.value.shopCategories[item].furnitureList, + onAllFurnitureClick = { onNavigateToFurniture(Furniture(uuid = userUuid)) } + ){ + onNavigateToDetail(Detail(furnitureId = it.id, uuid = userUuid)) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/main/MainScreenState.kt b/app/src/main/java/com/example/furintiture/ui/screen/main/MainScreenState.kt new file mode 100644 index 0000000..3d7fdc3 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/main/MainScreenState.kt @@ -0,0 +1,16 @@ +package com.example.furintiture.ui.screen.main + +import com.example.furintiture.model.response.AllShopCategoryResponse +import com.example.furintiture.model.response.FurnitureResponse +import com.example.furintiture.model.response.SaleResponse +import com.example.furintiture.model.response.ShopCategoryResponse +import com.example.furintiture.model.response.UserResponse + +data class MainScreenState( + var sales: List = emptyList(), + var shopCategories: List = emptyList(), + var addresses: List = emptyList(), + var cartItems: List = emptyList(), + var error: String? = null, + val profile: UserResponse? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/main/MainScreenViewModel.kt b/app/src/main/java/com/example/furintiture/ui/screen/main/MainScreenViewModel.kt new file mode 100644 index 0000000..89b006f --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/main/MainScreenViewModel.kt @@ -0,0 +1,85 @@ +package com.example.furintiture.ui.screen.main + +import androidx.compose.runtime.saveable.autoSaver +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.furintiture.data.RetrofitClient +import com.example.furintiture.model.request.AddAddressRequest +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import java.util.UUID + +class MainScreenViewModel(private val uuid: String): ViewModel() { + private val _mainScreenState = MutableStateFlow(MainScreenState()) + val mainScreenState = _mainScreenState.asStateFlow() + + init { + try { + init() + } + catch (e: Exception){ + _mainScreenState.update { + it.copy(error = e.message) + } + } + } + + private fun init(){ + viewModelScope.launch { + val sales = RetrofitClient.api.getAllSales() + _mainScreenState.update { + it.copy(sales = sales) + } + val allCategory = RetrofitClient.api.getAllShopCategory() + _mainScreenState.update { + it.copy(shopCategories = allCategory) + } + val profile = RetrofitClient.api.getProfileByUuid(UUID.fromString(uuid)) + _mainScreenState.update { + it.copy(profile = profile) + } + } + } + + fun addAddress(addressRequest: AddAddressRequest){ + viewModelScope.launch { + if (!validate(addressRequest)) return@launch + addressRequest.entrance = 10 + try { + RetrofitClient.api.addAddressByUuid(uuid = UUID.fromString(uuid), addressRequest) + val profile = RetrofitClient.api.getProfileByUuid(UUID.fromString(uuid)) + _mainScreenState.update { + it.copy(profile = profile) + } + } + catch (e: Exception){ + _mainScreenState.update { + it.copy(error = e.message) + } + } + } + } + private fun validate(addressRequest: AddAddressRequest): Boolean{ + if (addressRequest.address.isEmpty()){ + _mainScreenState.update { + it.copy(error = "Адрес не может быть пустым") + } + return false + } + if(addressRequest.apartment == null){ + _mainScreenState.update { + it.copy(error = "Укажите квартиру") + } + return false + } + if(addressRequest.floor == null){ + _mainScreenState.update { + it.copy(error = "Укажите этаж") + } + return false + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/order/OrderScreen.kt b/app/src/main/java/com/example/furintiture/ui/screen/order/OrderScreen.kt new file mode 100644 index 0000000..ec1f1d4 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/order/OrderScreen.kt @@ -0,0 +1,84 @@ +package com.example.furintiture.ui.screen.order + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.furintiture.ui.common.FurnitureOrderCard +import com.example.furintiture.ui.screen.cart.CartScreenContent +import com.example.furintiture.ui.screen.cart.CartScreenViewModel +import com.example.furintiture.ui.theme.Neutral_20 +import kotlinx.coroutines.launch +import java.math.BigDecimal + +@Composable +fun OrderScreen( + uuid: String +) { + val snackbarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + val viewModel: OrderScreenViewModel = viewModel { OrderScreenViewModel(uuid) } + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + contentColor = Neutral_20 + ) { paddingValues -> + OrderScreenContent(viewModel, paddingValues){ + coroutineScope.launch { + snackbarHostState.showSnackbar(it) + } + } + } +} + +@Composable +fun OrderScreenContent( + viewModel: OrderScreenViewModel, + paddingValues: PaddingValues, + onErrorCallback : (String) -> Unit +) { + val state = viewModel.orderScreenState.collectAsState() + + + LaunchedEffect(state.value.error) { + state.value.error?.let(onErrorCallback) + state.value.error = null + } + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(horizontal = 16.dp) + .padding(bottom = 100.dp) + + + ) { + Spacer(modifier = Modifier.height(16.dp)) + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(state.value.orders){ + FurnitureOrderCard(it) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/order/OrderScreenState.kt b/app/src/main/java/com/example/furintiture/ui/screen/order/OrderScreenState.kt new file mode 100644 index 0000000..be73ba4 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/order/OrderScreenState.kt @@ -0,0 +1,8 @@ +package com.example.furintiture.ui.screen.order + +import com.example.furintiture.model.response.OrderResponse + +data class OrderScreenState( + var orders: List = emptyList(), + var error: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/order/OrderScreenViewModel.kt b/app/src/main/java/com/example/furintiture/ui/screen/order/OrderScreenViewModel.kt new file mode 100644 index 0000000..230f2c6 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/order/OrderScreenViewModel.kt @@ -0,0 +1,30 @@ +package com.example.furintiture.ui.screen.order + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.furintiture.data.RetrofitClient +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import java.util.UUID + +class OrderScreenViewModel(private val uuid: String): ViewModel() { + private val _orderScreenState = MutableStateFlow(OrderScreenState()) + val orderScreenState = _orderScreenState.asStateFlow() + + init { + try{ + viewModelScope.launch { + _orderScreenState.update { + it.copy(orders = RetrofitClient.api.getOrdersByUuid(UUID.fromString(uuid))) + } + } + } + catch (e: Exception){ + _orderScreenState.update { + it.copy(error = e.message) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/registration/RegistrationScreen.kt b/app/src/main/java/com/example/furintiture/ui/screen/registration/RegistrationScreen.kt new file mode 100644 index 0000000..e89c68e --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/registration/RegistrationScreen.kt @@ -0,0 +1,160 @@ +package com.example.furintiture.ui.screen.registration + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.furintiture.R +import com.example.furintiture.data.DataStoreSettings +import com.example.furintiture.ui.common.FurnitureButton +import com.example.furintiture.ui.common.FurniturePasswordTextField +import com.example.furintiture.ui.common.FurnitureTextField +import com.example.furintiture.ui.screen.login.LoginScreenContent +import com.example.furintiture.ui.screen.login.LoginScreenViewModel +import com.example.furintiture.ui.theme.Neutral_20 +import com.example.furintiture.ui.theme.Neutral_70 +import com.example.furintiture.ui.theme.Primary +import kotlinx.coroutines.launch + +@Composable +fun RegistrationScreen( + dataStoreSettings: DataStoreSettings, + onNavigateToLogin: () -> Unit +) { + val snackbarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + val viewModel: RegistrationScreenViewModel = viewModel { RegistrationScreenViewModel(dataStoreSettings) } + Scaffold( + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + contentColor = Neutral_20 + ) { paddingValues -> + RegisterScreenContent(viewModel, paddingValues, onNavigateToLogin){ + coroutineScope.launch { + snackbarHostState.showSnackbar(message = it) + } + } + } +} + +@Composable +fun RegisterScreenContent( + registrationScreenViewModel: RegistrationScreenViewModel, + paddingValues: PaddingValues, + onNavigateToLogin: () -> Unit, + errorCallback: (String) -> Unit +){ + val state = registrationScreenViewModel.registrationScreenState.collectAsState() + + LaunchedEffect(state.value.error) { + state.value.error?.let { + errorCallback(it) + } + state.value.error = null + } + + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = "Создать аккаунт", + style = TextStyle( + fontSize = 32.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope_extrabold)) + ), + color = Color.Black + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Введите свои данные ниже, чтобы начать совершать покупки.", + style = TextStyle( + fontSize = 16.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ), + color = Neutral_70 + ) + Spacer(modifier = Modifier.height(32.dp)) + FurnitureTextField( + value = state.value.request.firstName, + onValueChange = { registrationScreenViewModel.setFirstName(it) }, + textLabel = "Имя", + leadingResourceId = R.drawable.user_icon + ) + Spacer(modifier = Modifier.height(16.dp)) + FurnitureTextField( + value = state.value.request.lastName, + onValueChange = { registrationScreenViewModel.setLastName(it) }, + textLabel = "Фамиля", + leadingResourceId = R.drawable.user_icon + ) + Spacer(modifier = Modifier.height(16.dp)) + FurnitureTextField( + value = state.value.request.email, + onValueChange = { registrationScreenViewModel.setEmail(it) }, + textLabel = "Email", + leadingResourceId = R.drawable.mail_icon + ) + Spacer(modifier = Modifier.height(16.dp)) + FurniturePasswordTextField( + value = state.value.request.password, + onValueChange = { registrationScreenViewModel.setPassword(it) } + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "Нажимая кнопку Зарегистрироваться, вы подтверждаете, что ознакомились и согласились с нашими Условиями использования и Политикой конфиденциальности.", + style = TextStyle( + fontSize = 16.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ), + color = Primary + ) + Spacer(modifier = Modifier.height(16.dp)) + FurnitureButton( + text = "Зарегистрироваться" + ) { + registrationScreenViewModel.register() + } + Spacer(modifier = Modifier.height(16.dp)) + Text( + modifier = Modifier.clickable { + onNavigateToLogin() + }, + text = "Войти", + style = TextStyle( + fontSize = 16.sp, + lineHeight = 20.sp, + fontFamily = FontFamily(Font(R.font.manrope)) + ), + color = Neutral_70 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/registration/RegistrationScreenState.kt b/app/src/main/java/com/example/furintiture/ui/screen/registration/RegistrationScreenState.kt new file mode 100644 index 0000000..442bcbc --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/registration/RegistrationScreenState.kt @@ -0,0 +1,13 @@ +package com.example.furintiture.ui.screen.registration + +import com.example.furintiture.model.request.RegisterRequest + +data class RegistrationScreenState( + var error: String? = null, + var request: RegisterRequest = RegisterRequest( + firstName = "", + lastName = "", + email = "", + password = "" + ) +) \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/registration/RegistrationScreenViewModel.kt b/app/src/main/java/com/example/furintiture/ui/screen/registration/RegistrationScreenViewModel.kt new file mode 100644 index 0000000..7b5c562 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/registration/RegistrationScreenViewModel.kt @@ -0,0 +1,79 @@ +package com.example.furintiture.ui.screen.registration + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.furintiture.data.DataStoreSettings +import com.example.furintiture.data.RetrofitClient +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class RegistrationScreenViewModel(private val dataStoreSettings: DataStoreSettings): ViewModel() { + private val _registrationScreenState = MutableStateFlow(RegistrationScreenState()) + val registrationScreenState = _registrationScreenState.asStateFlow() + + fun setPassword(password: String){ + _registrationScreenState.update { + it.copy(request = it.request.copy(password = password)) + } + } + fun setEmail(email: String){ + _registrationScreenState.update { + it.copy(request = it.request.copy(email = email)) + } + } + fun setLastName(lastName: String){ + _registrationScreenState.update { + it.copy(request = it.request.copy(lastName = lastName)) + } + } + fun setFirstName(firstName: String){ + _registrationScreenState.update { + it.copy(request = it.request.copy(firstName = firstName)) + } + } + + fun register(){ + if(!validate()) return + viewModelScope.launch { + try { + val result = RetrofitClient.api.register(_registrationScreenState.value.request) + dataStoreSettings.setUuid(result.userUuid) + } + catch (e: Exception){ + _registrationScreenState.update { + it.copy(error = e.message) + } + } + } + } + + private fun validate(): Boolean{ + if (_registrationScreenState.value.request.email.isEmpty()){ + _registrationScreenState.update { + it.copy(error = "Введите email") + } + return false + } + if (_registrationScreenState.value.request.password.isEmpty()){ + _registrationScreenState.update { + it.copy(error = "Введите password") + } + return false + } + if (_registrationScreenState.value.request.lastName.isEmpty()){ + _registrationScreenState.update { + it.copy(error = "Введите фамилию") + } + return false + } + if (_registrationScreenState.value.request.firstName.isEmpty()){ + _registrationScreenState.update { + it.copy(error = "Введите имя") + } + return false + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/screen/splashscreen/SplashScreen.kt b/app/src/main/java/com/example/furintiture/ui/screen/splashscreen/SplashScreen.kt new file mode 100644 index 0000000..31736ac --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/screen/splashscreen/SplashScreen.kt @@ -0,0 +1,54 @@ +package com.example.furintiture.ui.screen.splashscreen + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material3.Icon +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.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.furintiture.R +import com.example.furintiture.ui.theme.gradientColors + +@Composable +fun SplashScreen() { + Column( + modifier = Modifier + .fillMaxSize() + .background(Brush.verticalGradient(colorStops = gradientColors)) + , + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + modifier = Modifier.size(87.dp), + imageVector = Icons.Default.Home, + contentDescription = null, + tint = Color.White + ) + Text( + text = "Дом мечты", + style = TextStyle( + fontSize = 36.sp, + fontWeight = FontWeight.ExtraBold, + fontFamily = FontFamily(Font(R.font.manrope_extrabold)), + color = Color.White + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/theme/Color.kt b/app/src/main/java/com/example/furintiture/ui/theme/Color.kt new file mode 100644 index 0000000..9bc0dd7 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/theme/Color.kt @@ -0,0 +1,19 @@ +package com.example.furintiture.ui.theme + +import androidx.compose.ui.graphics.Color + +val Primary = Color(0xFF5C6B67) +val Secondary = Color(0xFFEBB65B) +val Text = Color(0xFF404040) +val Neutral_40 = Color(0xFFE0E0E0) +val Green_Linear= Color(0xFF156651) +val Neutral_70 = Color(0xFF757575) +val Neutral_90 = Color(0xFF404040) +val Neutral_20 = Color(0xFFF5F5F5) +val Neutral_10 = Color(0xFFFFFFFF) + +val gradientColors = arrayOf( + 0.0f to Color(0xFF156651).copy(alpha = 0.94f), + 0.47f to Color(0xFF156651).copy(alpha = 0.67f), + 1f to Color(0xFF156651).copy(alpha = 0f) +) diff --git a/app/src/main/java/com/example/furintiture/ui/theme/Theme.kt b/app/src/main/java/com/example/furintiture/ui/theme/Theme.kt new file mode 100644 index 0000000..428cb14 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package com.example.furintiture.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Primary, + secondary = Secondary, + tertiary = Text +) + +private val LightColorScheme = lightColorScheme( + primary = Primary, + secondary = Secondary, + tertiary = Text + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun FurintitureTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/furintiture/ui/theme/Type.kt b/app/src/main/java/com/example/furintiture/ui/theme/Type.kt new file mode 100644 index 0000000..57a8b73 --- /dev/null +++ b/app/src/main/java/com/example/furintiture/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.furintiture.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/drawable/fi_sr_eye_1.xml b/app/src/main/res/drawable/fi_sr_eye_1.xml new file mode 100644 index 0000000..ea4b396 --- /dev/null +++ b/app/src/main/res/drawable/fi_sr_eye_1.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/images.png b/app/src/main/res/drawable/images.png new file mode 100644 index 0000000..85ab7e2 Binary files /dev/null and b/app/src/main/res/drawable/images.png differ diff --git a/app/src/main/res/drawable/lock_icon.xml b/app/src/main/res/drawable/lock_icon.xml new file mode 100644 index 0000000..63af700 --- /dev/null +++ b/app/src/main/res/drawable/lock_icon.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/mail_icon.xml b/app/src/main/res/drawable/mail_icon.xml new file mode 100644 index 0000000..84c9598 --- /dev/null +++ b/app/src/main/res/drawable/mail_icon.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/user_icon.xml b/app/src/main/res/drawable/user_icon.xml new file mode 100644 index 0000000..0bd41e6 --- /dev/null +++ b/app/src/main/res/drawable/user_icon.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/font/manrope.ttf b/app/src/main/res/font/manrope.ttf new file mode 100644 index 0000000..f1795e0 Binary files /dev/null and b/app/src/main/res/font/manrope.ttf differ diff --git a/app/src/main/res/font/manrope_extrabold.ttf b/app/src/main/res/font/manrope_extrabold.ttf new file mode 100644 index 0000000..3dc5b99 Binary files /dev/null and b/app/src/main/res/font/manrope_extrabold.ttf differ diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..2655f4c --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + furintiture + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..d3cf159 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +