Forráskód Böngészése

app: adding the ability to update a profile, upload an avatar, and validate fields

Bobarik31p 3 napja
szülő
commit
fd6431f6e6

+ 0 - 329
App/.idea/other.xml

@@ -1,329 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="direct_access_persist.xml">
-    <option name="deviceSelectionList">
-      <list>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="27" />
-          <option name="brand" value="DOCOMO" />
-          <option name="codename" value="F01L" />
-          <option name="id" value="F01L" />
-          <option name="manufacturer" value="FUJITSU" />
-          <option name="name" value="F-01L" />
-          <option name="screenDensity" value="360" />
-          <option name="screenX" value="720" />
-          <option name="screenY" value="1280" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="28" />
-          <option name="brand" value="DOCOMO" />
-          <option name="codename" value="SH-01L" />
-          <option name="id" value="SH-01L" />
-          <option name="manufacturer" value="SHARP" />
-          <option name="name" value="AQUOS sense2 SH-01L" />
-          <option name="screenDensity" value="480" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2160" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="Lenovo" />
-          <option name="codename" value="TB370FU" />
-          <option name="id" value="TB370FU" />
-          <option name="manufacturer" value="Lenovo" />
-          <option name="name" value="Tab P12" />
-          <option name="screenDensity" value="340" />
-          <option name="screenX" value="1840" />
-          <option name="screenY" value="2944" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="31" />
-          <option name="brand" value="samsung" />
-          <option name="codename" value="a51" />
-          <option name="id" value="a51" />
-          <option name="manufacturer" value="Samsung" />
-          <option name="name" value="Galaxy A51" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2400" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="google" />
-          <option name="codename" value="akita" />
-          <option name="id" value="akita" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 8a" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2400" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="33" />
-          <option name="brand" value="samsung" />
-          <option name="codename" value="b0q" />
-          <option name="id" value="b0q" />
-          <option name="manufacturer" value="Samsung" />
-          <option name="name" value="Galaxy S22 Ultra" />
-          <option name="screenDensity" value="600" />
-          <option name="screenX" value="1440" />
-          <option name="screenY" value="3088" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="32" />
-          <option name="brand" value="google" />
-          <option name="codename" value="bluejay" />
-          <option name="id" value="bluejay" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 6a" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2400" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="google" />
-          <option name="codename" value="caiman" />
-          <option name="id" value="caiman" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 9 Pro" />
-          <option name="screenDensity" value="360" />
-          <option name="screenX" value="960" />
-          <option name="screenY" value="2142" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="google" />
-          <option name="codename" value="comet" />
-          <option name="id" value="comet" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 9 Pro Fold" />
-          <option name="screenDensity" value="390" />
-          <option name="screenX" value="2076" />
-          <option name="screenY" value="2152" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="29" />
-          <option name="brand" value="samsung" />
-          <option name="codename" value="crownqlteue" />
-          <option name="id" value="crownqlteue" />
-          <option name="manufacturer" value="Samsung" />
-          <option name="name" value="Galaxy Note9" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="2220" />
-          <option name="screenY" value="1080" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="samsung" />
-          <option name="codename" value="dm3q" />
-          <option name="id" value="dm3q" />
-          <option name="manufacturer" value="Samsung" />
-          <option name="name" value="Galaxy S23 Ultra" />
-          <option name="screenDensity" value="600" />
-          <option name="screenX" value="1440" />
-          <option name="screenY" value="3088" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="samsung" />
-          <option name="codename" value="e1q" />
-          <option name="id" value="e1q" />
-          <option name="manufacturer" value="Samsung" />
-          <option name="name" value="Galaxy S24" />
-          <option name="screenDensity" value="480" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2340" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="33" />
-          <option name="brand" value="google" />
-          <option name="codename" value="felix" />
-          <option name="id" value="felix" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel Fold" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="2208" />
-          <option name="screenY" value="1840" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="google" />
-          <option name="codename" value="felix" />
-          <option name="id" value="felix" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel Fold" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="2208" />
-          <option name="screenY" value="1840" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="33" />
-          <option name="brand" value="google" />
-          <option name="codename" value="felix_camera" />
-          <option name="id" value="felix_camera" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel Fold (Camera-enabled)" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="2208" />
-          <option name="screenY" value="1840" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="33" />
-          <option name="brand" value="samsung" />
-          <option name="codename" value="gts8uwifi" />
-          <option name="id" value="gts8uwifi" />
-          <option name="manufacturer" value="Samsung" />
-          <option name="name" value="Galaxy Tab S8 Ultra" />
-          <option name="screenDensity" value="320" />
-          <option name="screenX" value="1848" />
-          <option name="screenY" value="2960" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="google" />
-          <option name="codename" value="husky" />
-          <option name="id" value="husky" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 8 Pro" />
-          <option name="screenDensity" value="390" />
-          <option name="screenX" value="1008" />
-          <option name="screenY" value="2244" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="30" />
-          <option name="brand" value="motorola" />
-          <option name="codename" value="java" />
-          <option name="id" value="java" />
-          <option name="manufacturer" value="Motorola" />
-          <option name="name" value="G20" />
-          <option name="screenDensity" value="280" />
-          <option name="screenX" value="720" />
-          <option name="screenY" value="1600" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="google" />
-          <option name="codename" value="komodo" />
-          <option name="id" value="komodo" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 9 Pro XL" />
-          <option name="screenDensity" value="360" />
-          <option name="screenX" value="1008" />
-          <option name="screenY" value="2244" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="33" />
-          <option name="brand" value="google" />
-          <option name="codename" value="lynx" />
-          <option name="id" value="lynx" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 7a" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2400" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="31" />
-          <option name="brand" value="google" />
-          <option name="codename" value="oriole" />
-          <option name="id" value="oriole" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 6" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2400" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="33" />
-          <option name="brand" value="google" />
-          <option name="codename" value="panther" />
-          <option name="id" value="panther" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 7" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2400" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="samsung" />
-          <option name="codename" value="q5q" />
-          <option name="id" value="q5q" />
-          <option name="manufacturer" value="Samsung" />
-          <option name="name" value="Galaxy Z Fold5" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="1812" />
-          <option name="screenY" value="2176" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="samsung" />
-          <option name="codename" value="q6q" />
-          <option name="id" value="q6q" />
-          <option name="manufacturer" value="Samsung" />
-          <option name="name" value="Galaxy Z Fold6" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="1856" />
-          <option name="screenY" value="2160" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="30" />
-          <option name="brand" value="google" />
-          <option name="codename" value="r11" />
-          <option name="id" value="r11" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel Watch" />
-          <option name="screenDensity" value="320" />
-          <option name="screenX" value="384" />
-          <option name="screenY" value="384" />
-          <option name="type" value="WEAR_OS" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="30" />
-          <option name="brand" value="google" />
-          <option name="codename" value="redfin" />
-          <option name="id" value="redfin" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 5" />
-          <option name="screenDensity" value="440" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2340" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="google" />
-          <option name="codename" value="shiba" />
-          <option name="id" value="shiba" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 8" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2400" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="33" />
-          <option name="brand" value="google" />
-          <option name="codename" value="tangorpro" />
-          <option name="id" value="tangorpro" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel Tablet" />
-          <option name="screenDensity" value="320" />
-          <option name="screenX" value="1600" />
-          <option name="screenY" value="2560" />
-        </PersistentDeviceSelectionData>
-        <PersistentDeviceSelectionData>
-          <option name="api" value="34" />
-          <option name="brand" value="google" />
-          <option name="codename" value="tokay" />
-          <option name="id" value="tokay" />
-          <option name="manufacturer" value="Google" />
-          <option name="name" value="Pixel 9" />
-          <option name="screenDensity" value="420" />
-          <option name="screenX" value="1080" />
-          <option name="screenY" value="2424" />
-        </PersistentDeviceSelectionData>
-      </list>
-    </option>
-  </component>
-</project>

+ 1 - 0
App/app/build.gradle.kts

@@ -87,6 +87,7 @@ dependencies {
     implementation (libs.kotlinx.coroutines.android)
     implementation (libs.gson)
     implementation (libs.jackson.module.kotlin)
+    implementation(libs.coil.compose)
 
 
 }

+ 332 - 40
App/app/src/main/java/com/example/mystictale/Screen/Profile.kt

@@ -1,7 +1,15 @@
 package com.example.mystictale.Screen
 
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.os.Build
+import android.widget.Toast
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.annotation.RequiresApi
 import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.foundation.Image
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Arrangement
@@ -17,9 +25,11 @@ import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonColors
+import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.Icon
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -27,8 +37,11 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.paint
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.LocalSoftwareKeyboardController
 import androidx.compose.ui.res.painterResource
@@ -37,43 +50,101 @@ import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.navigation.NavController
+import coil.compose.AsyncImagePainter
+import coil.compose.rememberAsyncImagePainter
+import coil.request.ImageRequest
 import com.example.mystictale.R
 import com.example.mystictale.ViewModels.AuthViewModel
+import com.example.mystictale.models.UserState
 import com.example.mystictale.resources.components.ChooseGenderForProfile
+import com.example.mystictale.resources.components.ImageForBitmap
+import com.example.mystictale.resources.components.ImagePainter
 import com.example.mystictale.resources.components.Loading
 import com.example.mystictale.resources.components.ProfileTextField
+import com.example.mystictale.resources.components.dateComponents.DateOfBirthTextFieldForProfile
+import com.example.mystictale.resources.components.dateComponents.FormattingDateForDatabase
+import com.example.mystictale.resources.components.dateComponents.FormattingDateForProfile
+import com.example.mystictale.resources.components.dateComponents.ValidationDate
+import com.example.mystictale.resources.components.dateComponents.isDateValid
 import com.example.mystictale.ui.theme.DarkPurple
 import com.example.mystictale.ui.theme.Grey
 import com.example.mystictale.ui.theme.OpenSans
+import com.vanpra.composematerialdialogs.MaterialDialog
+import com.vanpra.composematerialdialogs.datetime.date.datepicker
+import com.vanpra.composematerialdialogs.rememberMaterialDialogState
+import java.io.ByteArrayOutputStream
+import java.text.SimpleDateFormat
+import java.time.LocalDate
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+import java.util.Locale
 
+@RequiresApi(Build.VERSION_CODES.O)
 @Composable
 fun Profile(navController: NavController, viewModel: AuthViewModel) {
 
     val user by viewModel.user
+    val userState by viewModel.userState
     val focusManager = LocalFocusManager.current
+    val context = LocalContext.current
     val keyboardController = LocalSoftwareKeyboardController.current
     val isEnabled = remember { mutableStateOf(false) }
     val flagName = remember { mutableStateOf(false) }
     val flag = remember { mutableStateOf(true) }
+    val flag1 = remember { mutableStateOf(false) }
+    val flag2 = remember { mutableStateOf(false) }
+    val nameFlag = remember { mutableStateOf(false) }
+    val dateOfBirthFlag = remember { mutableStateOf(false) }
+
+    val dateDialogState = rememberMaterialDialogState()
+
+
     if (flag.value) {
         viewModel.selectProfile()
         flag.value = false
     }
     if (user.isNotEmpty()) {
-
-
-        viewModel.name = user.last().name
-        viewModel.dateOfBirth = user.last().date_of_birth
-        viewModel.gender = user.last().id_gender
+        val newAvatar = remember { mutableStateOf(byteArrayOf()) }
+        val avatarFrombase = rememberAsyncImagePainter(
+            model = ImageRequest.Builder(LocalContext.current).data(user.last().avatar)
+                .size(100, 100).build()
+        ).state
+        val newAvatarBitmap = remember { mutableStateOf<ImageBitmap?>(null) }
+        val name = remember { mutableStateOf(user.last().name) }
+        val dateOfBirth =
+            remember { mutableStateOf(FormattingDateForProfile(user.last().date_of_birth)) }
+        val formatForScreen = SimpleDateFormat("ddMMyyyy", Locale.getDefault())
+        val formatter = DateTimeFormatter.ofPattern("ddMMyyyy")
+        val date =
+            FormattingDateForProfile(user.last().date_of_birth)!!.let { formatForScreen.parse(it) }
+        val dateForDatePicker = remember {
+            mutableStateOf(
+                date!!.toInstant()
+                    .atZone(ZoneId.systemDefault())
+                    .toLocalDate()
+            )
+        }
+        val formattedDate by remember {
+            derivedStateOf {
+                DateTimeFormatter.ofPattern("ddMMyyyy").format(dateForDatePicker.value)
+            }
+        }
+        val dateForBase = FormattingDateForDatabase(dateOfBirth.value!!)
         val gender = remember {
             mutableStateOf(
-                if (viewModel.gender == 1) {
+                if (user.last().id_gender == 1) {
                     "Женский"
                 } else {
                     "Мужской"
                 }
             )
         }
+        val launcher =
+            rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
+                if (uri == null) return@rememberLauncherForActivityResult
+                newAvatar.value = bitmapToByteArray(context, uri)
+
+            }
         Column(
             Modifier
                 .fillMaxSize()
@@ -88,7 +159,7 @@ fun Profile(navController: NavController, viewModel: AuthViewModel) {
                     })
                 }
                 .padding(10.dp)
-                .padding(top = 40.dp),
+                .padding(top = 50.dp),
             verticalArrangement = Arrangement.Top,
             horizontalAlignment = Alignment.CenterHorizontally
         ) {
@@ -114,14 +185,15 @@ fun Profile(navController: NavController, viewModel: AuthViewModel) {
                     Icon(
                         painter = painterResource(id = R.drawable.update),
                         contentDescription = "update your profile",
-                        tint = Grey,
+                        tint = if (!isEnabled.value) Grey else Color.Transparent,
+
                         modifier = Modifier
                             .padding(end = 5.dp)
                             .height(21.dp)
                             .width(21.dp)
                             .clickable {
-                                isEnabled.value = !isEnabled.value
                                 if (!isEnabled.value) {
+                                    isEnabled.value = !isEnabled.value
                                     if (viewModel.gender == 1) {
                                         gender.value = "Женский"
                                     } else {
@@ -129,12 +201,56 @@ fun Profile(navController: NavController, viewModel: AuthViewModel) {
                                     }
                                 }
                             }
+
                     )
                 }
             }
             Spacer(modifier = Modifier.height(20.dp))
-            Image(painter = painterResource(id = R.drawable.girl), contentDescription = "")
-            Spacer(modifier = Modifier.height(20.dp))
+            if (newAvatar.value.isEmpty()) {
+                if (avatarFrombase is AsyncImagePainter.State.Success) {
+                    ImagePainter(
+                        isEnabled = isEnabled.value,
+                        image = avatarFrombase.painter,
+                        launcher = launcher
+                    )
+                }
+                if (avatarFrombase is AsyncImagePainter.State.Loading) {
+                    Box(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .height(200.dp),
+                        contentAlignment = Alignment.Center
+                    ) {
+                        CircularProgressIndicator()
+                    }
+                }
+                if (avatarFrombase is AsyncImagePainter.State.Error) {
+
+                    if (user.last().id_gender == 1) {
+                        ImagePainter(
+                            isEnabled = isEnabled.value,
+                            image = painterResource(R.drawable.girl),
+                            launcher = launcher
+                        )
+                    } else {
+                        ImagePainter(
+                            isEnabled = isEnabled.value,
+                            image = painterResource(R.drawable.girl),
+                            launcher = launcher
+                        )
+                    }
+
+                }
+            } else {
+                newAvatarBitmap.value = ByteArrayImage(newAvatar.value)
+                ImageForBitmap(
+                    isEnabled = isEnabled.value,
+                    image = newAvatarBitmap.value!!,
+                    launcher = launcher
+                )
+
+            }
+            Spacer(modifier = Modifier.height(10.dp))
             Column(
                 Modifier
                     .fillMaxWidth()
@@ -147,16 +263,29 @@ fun Profile(navController: NavController, viewModel: AuthViewModel) {
                     fontSize = 14.sp,
                     color = Grey
                 )
-                Spacer(modifier = Modifier.height(10.dp))
+                Spacer(modifier = Modifier.height(5.dp))
                 ProfileTextField(
-                    value = viewModel.name!!,
-                    onValue = { viewModel.name = it }, placeholder = "Имя",
+                    value = name.value,
+                    onValue = {
+                        name.value = it
+                        nameFlag.value = false
+                    }, placeholder = "Имя",
                     keyboardType = KeyboardType.Text,
                     isEnable = isEnabled.value,
-                    flag = flagName.value
+                    flag = flagName.value,
+                    date = false
                 )
+                if (nameFlag.value) {
+                    Text(
+                        "Введите имя",
+                        fontWeight = FontWeight.Light,
+                        fontFamily = OpenSans,
+                        fontSize = 13.sp,
+                        color = Color.Red
+                    )
+                }
             }
-            Spacer(modifier = Modifier.height(15.dp))
+            Spacer(modifier = Modifier.height(10.dp))
             Column(
                 Modifier
                     .fillMaxWidth()
@@ -169,17 +298,56 @@ fun Profile(navController: NavController, viewModel: AuthViewModel) {
                     fontSize = 14.sp,
                     color = Grey
                 )
-                Spacer(modifier = Modifier.height(10.dp))
-                ProfileTextField(
-                    value = viewModel.dateOfBirth!!,
-                    onValue = { viewModel.dateOfBirth = it },
-                    placeholder = "Дата рождения",
-                    keyboardType = KeyboardType.Text,
-                    isEnable = isEnabled.value,
-                    flag = flagName.value
+                Spacer(modifier = Modifier.height(5.dp))
+
+                DateOfBirthTextFieldForProfile(
+                    dateOfBirth = dateOfBirth.value!!,
+                    onValue = { dob ->
+                        dateOfBirth.value = dob.filter { it.isDigit() }.take(8)
+                        dateOfBirthFlag.value = false
+
+                    },
+                    dateDialogState = dateDialogState,
+                    flag = dateOfBirthFlag.value,
+                    enabled = isEnabled.value
                 )
+                if (dateOfBirthFlag.value) {
+                    if (dateOfBirth.value!!.isEmpty()) {
+                        Text(
+                            "Введите дату рождения",
+                            fontWeight = FontWeight.Light,
+                            fontFamily = OpenSans,
+                            fontSize = 13.sp,
+                            color = Color.Red
+                        )
+                    } else if (dateOfBirth.value!!.length < 8) {
+                        Text(
+                            "Неверная дата рождения",
+                            fontWeight = FontWeight.Light,
+                            fontFamily = OpenSans,
+                            fontSize = 13.sp,
+                            color = Color.Red
+                        )
+                    } else if (!isDateValid(dateOfBirth.value!!)) {
+                        Text(
+                            "Неверная дата рождения",
+                            fontWeight = FontWeight.Light,
+                            fontFamily = OpenSans,
+                            fontSize = 13.sp,
+                            color = Color.Red
+                        )
+                    } else if (LocalDate.parse(dateOfBirth.value, formatter) > LocalDate.now()) {
+                        Text(
+                            "Неверная дата рождения",
+                            fontWeight = FontWeight.Light,
+                            fontFamily = OpenSans,
+                            fontSize = 13.sp,
+                            color = Color.Red
+                        )
+                    }
+                }
             }
-            Spacer(modifier = Modifier.height(15.dp))
+            Spacer(modifier = Modifier.height(5.dp))
             Column(
                 Modifier
                     .fillMaxWidth()
@@ -192,24 +360,44 @@ fun Profile(navController: NavController, viewModel: AuthViewModel) {
                     fontSize = 14.sp,
                     color = Grey
                 )
-                Spacer(modifier = Modifier.height(10.dp))
-                if (isEnabled.value) {
-                    ChooseGenderForProfile(viewModel = viewModel, gender = gender.value)
-                } else {
-                    ProfileTextField(
-                        value = gender.value,
-                        onValue = { gender.value = it },
-                        placeholder = "Дата рождения",
-                        keyboardType = KeyboardType.Text,
-                        isEnable = isEnabled.value,
-                        flag = flagName.value
-                    )
-                }
+                Spacer(modifier = Modifier.height(5.dp))
+
+                ChooseGenderForProfile(
+                    viewModel = viewModel,
+                    gender = gender.value,
+                    isEnabled = isEnabled.value
+                )
+
+
             }
 
             AnimatedVisibility(visible = isEnabled.value, Modifier.padding(40.dp)) {
                 Button(
                     onClick = {
+                        if (ValidationDate(
+                                dateOfBirth.value!!,
+                                formatter
+                            ) || name.value.isEmpty()
+                        ) {
+                            if (ValidationDate(dateOfBirth.value!!, formatter)) {
+                                dateOfBirthFlag.value = true
+
+                            }
+                            if (name.value.isEmpty()) {
+                                nameFlag.value = true
+                            }
+                        } else {
+                            viewModel.name = name.value
+                            viewModel.dateOfBirth = dateForBase
+                            viewModel.gender = if (gender.value == "Женский") {
+                                1
+                            } else {
+                                2
+                            }
+                            viewModel.addAvatar(newAvatar.value)
+                            flag1.value = true
+                        }
+
                     },
                     modifier = Modifier
                         .fillMaxWidth()
@@ -223,13 +411,33 @@ fun Profile(navController: NavController, viewModel: AuthViewModel) {
                         disabledContainerColor = DarkPurple
                     )
                 ) {
-                    Text("Далее", fontSize = 20.sp, fontWeight = FontWeight.Bold)
+                    Text("Сохранить", fontSize = 20.sp, fontWeight = FontWeight.Bold)
 
                 }
             }
 
 
         }
+        MaterialDialog(
+            dialogState = dateDialogState,
+            buttons = {
+                positiveButton(text = "Ok")
+                negativeButton("cancel")
+            })
+        {
+            datepicker(
+                initialDate = dateForDatePicker.value,
+                allowedDateValidator = {
+                    it <= LocalDate.now()
+                }
+
+
+            ) {
+                dateForDatePicker.value = it
+                dateOfBirth.value = formattedDate.toString()
+
+            }
+        }
     } else {
         Box(
             Modifier
@@ -242,5 +450,89 @@ fun Profile(navController: NavController, viewModel: AuthViewModel) {
             Loading()
         }
     }
+    if (flag1.value) {
+        when (userState) {
+            is UserState.Loading -> {
+                Loading()
+            }
+
+            is UserState.Success -> {
+                val message = (userState as UserState.Success).message
+                viewModel.updateProfile()
+                flag1.value = false
+                flag2.value = true
+            }
+
+            is UserState.Error -> {
+                val message = (userState as UserState.Error).message
+                Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
+                flag1.value = false
+            }
+        }
+    }
+    if (flag2.value) {
+        when (userState) {
+            is UserState.Loading -> {
+                Loading()
+            }
+
+            is UserState.Success -> {
+                Toast.makeText(context, "Изменения сохранены", Toast.LENGTH_SHORT).show()
+                isEnabled.value = false
+                flag2.value = false
+            }
+
+            is UserState.Error -> {
+                val message = (userState as UserState.Error).message
+                Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
+                flag2.value = false
+            }
+        }
+    }
 
+
+}
+
+fun bitmapToByteArray(context: Context, uri: Uri): ByteArray {
+    val inputStream = context.contentResolver.openInputStream(uri)
+    val bitmap = BitmapFactory.decodeStream(inputStream)
+    val baos = ByteArrayOutputStream()
+    bitmap.compress(Bitmap.CompressFormat.JPEG, 50, baos)
+    return baos.toByteArray()
+}
+
+
+fun ByteArrayImage(byteArray: ByteArray): ImageBitmap {
+    // Конвертируем массив байтов в Bitmap с уменьшением масштаба
+    val options = BitmapFactory.Options().apply {
+        inJustDecodeBounds = true // Сначала считываем только размеры изображения
+    }
+    BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
+
+    // Рассчитываем inSampleSize для уменьшения изображения
+    options.inSampleSize = calculateInSampleSize(options, reqWidth = 300, reqHeight = 200)
+    options.inJustDecodeBounds = false // Теперь декодируем само изображение
+
+    // Декодируем уменьшенное изображение
+    val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
+
+    // Преобразуем Bitmap в ImageBitmap
+    return bitmap.asImageBitmap()
+
+
+}
+
+fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
+    val (height: Int, width: Int) = options.outHeight to options.outWidth
+    var inSampleSize = 1
+
+    if (height > reqHeight || width > reqWidth) {
+        val halfHeight: Int = height / 2
+        val halfWidth: Int = width / 2
+
+        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
+            inSampleSize *= 2
+        }
+    }
+    return inSampleSize
 }

+ 0 - 1
App/app/src/main/java/com/example/mystictale/Screen/registration/RegistrationDateOfBirth.kt

@@ -71,7 +71,6 @@ fun RegistrationDateOfBirth(navController: NavController, viewModel: AuthViewMod
     }
     val dateFlag = remember { mutableStateOf(false) }
     val formatter = DateTimeFormatter.ofPattern("ddMMyyyy")
-    val formatterFoeBase = DateTimeFormatter.ofPattern("yyyy-MM-dd")
     Column(
         Modifier
             .fillMaxSize()

+ 1 - 0
App/app/src/main/java/com/example/mystictale/Screen/signIn/SignIn.kt

@@ -2,6 +2,7 @@ package com.example.mystictale.Screen.signIn
 
 import android.util.Patterns
 import android.widget.Toast
+import androidx.activity.compose.BackHandler
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.detectTapGestures

+ 81 - 1
App/app/src/main/java/com/example/mystictale/ViewModels/AuthViewModel.kt

@@ -15,12 +15,15 @@ import com.example.mystictale.resources.SharedPreferenceHelper
 import io.github.jan.supabase.auth.auth
 import io.github.jan.supabase.auth.providers.builtin.Email
 import io.github.jan.supabase.postgrest.from
+import io.github.jan.supabase.storage.storage
+import io.github.jan.supabase.storage.update
 import kotlinx.coroutines.launch
 
 class AuthViewModel : ViewModel() {
     private val _userState = mutableStateOf<UserState>(UserState.Loading)
     val userState: State<UserState> = _userState
     var uid by mutableStateOf<String?>(null)
+    var avatar by mutableStateOf<String?>(null)
     var emailUser by mutableStateOf<String?>(null)
     var passwordUser by mutableStateOf<String?>(null)
     var dateOfBirth by mutableStateOf<String?>(null)
@@ -97,7 +100,7 @@ class AuthViewModel : ViewModel() {
         viewModelScope.launch {
             try {
                 _userState.value = UserState.Loading
-                _user.value =  SupabaseConnect.supabase.from("Users").select {
+                _user.value = SupabaseConnect.supabase.from("Users").select {
                     filter {
                         Users::UID eq SupabaseConnect.supabase.auth.currentUserOrNull()!!.id
                     }
@@ -113,6 +116,83 @@ class AuthViewModel : ViewModel() {
         }
     }
 
+    fun updateProfile() {
+        viewModelScope.launch {
+            try {
+
+                _userState.value = UserState.Loading
+                val basket = SupabaseConnect.supabase.storage["avatars"]
+                SupabaseConnect.supabase.from("Users").update(
+                    {
+                        Users::name setTo name
+                        Users::date_of_birth setTo dateOfBirth
+                        Users::id_gender setTo gender
+                    }
+                ) {
+                    filter {
+                        Users::UID eq SupabaseConnect.supabase.auth.currentUserOrNull()!!.id
+                    }
+                }
+
+                _userState.value = UserState.Success("Success update profile")
+                Log.d("my_tag", "Success update profile")
+
+            } catch (e: Exception) {
+                _userState.value = UserState.Error("Error: ${e.message}")
+                Log.d("my_tag", e.message!!)
+            }
+        }
+    }
+
+    fun addAvatar(byteArray: ByteArray) {
+        viewModelScope.launch {
+            try {
+
+                _userState.value = UserState.Loading
+                val basket = SupabaseConnect.supabase.storage["Avatars"]
+               if(byteArray.isNotEmpty()){
+                   try {
+                       basket.upload(
+                           "${SupabaseConnect.supabase.auth.currentUserOrNull()!!.email}.jpg",
+                           byteArray,
+                           options = { }
+                       )
+                       SupabaseConnect.supabase.from("Users").update(
+                           {
+                               Users::avatar setTo basket.publicUrl("${SupabaseConnect.supabase.auth.currentUserOrNull()!!.email}.jpg")
+
+                           }
+                       ) {
+                           filter {
+                               Users::UID eq SupabaseConnect.supabase.auth.currentUserOrNull()!!.id
+                           }
+                       }
+                   }catch (e: Exception){
+                       basket.update(
+                           "${SupabaseConnect.supabase.auth.currentUserOrNull()!!.email}.jpg",
+                           byteArray,
+                           options = { }
+                       )
+                       SupabaseConnect.supabase.from("Users").update(
+                           {
+                               Users::avatar setTo basket.publicUrl("${SupabaseConnect.supabase.auth.currentUserOrNull()!!.email}.jpg")
+
+                           }
+                       ) {
+                           filter {
+                               Users::UID eq SupabaseConnect.supabase.auth.currentUserOrNull()!!.id
+                           }
+                       }
+                   }
+               }
+                _userState.value = UserState.Success("The avatar has been successfully added")
+            } catch (e: Exception) {
+                _userState.value = UserState.Error("Error: ${e.message}")
+                Log.d("my_tag", e.message!!)
+            }
+        }
+    }
+
     fun resetPassword(passwordUser: String) {
         viewModelScope.launch {
             try {

+ 4 - 0
App/app/src/main/java/com/example/mystictale/navigation/Navigation.kt

@@ -1,6 +1,8 @@
 package com.example.mystictale.navigation
 
 import android.os.Build
+import android.util.Log
+import androidx.activity.compose.BackHandler
 import androidx.annotation.RequiresApi
 import androidx.compose.runtime.Composable
 import androidx.lifecycle.viewmodel.compose.viewModel
@@ -64,6 +66,8 @@ fun Navigation() {
         }
         composable(Screens.Profile.route)
         {
+            BackHandler(true) {
+            }
             Profile(navController,viewModel)
         }
 

+ 5 - 2
App/app/src/main/java/com/example/mystictale/resources/components/ChooseGenderForProfile.kt

@@ -36,7 +36,7 @@ import com.example.mystictale.ui.theme.OpenSans
 import com.example.mystictale.ui.theme.Purple
 
 @Composable
-fun ChooseGenderForProfile(viewModel: AuthViewModel, gender:String) {
+fun ChooseGenderForProfile(viewModel: AuthViewModel, gender:String, isEnabled:Boolean) {
     var expanded by remember { mutableStateOf(false) }
     val list = listOf("Мужской", "Женский")
     var selectedItem by remember { mutableStateOf(gender) }
@@ -59,8 +59,11 @@ fun ChooseGenderForProfile(viewModel: AuthViewModel, gender:String) {
             shape = RoundedCornerShape(12.dp),
             colors = ButtonDefaults.buttonColors(
                 containerColor = Purple,
-                contentColor = Color.White
+                contentColor = Color.White,
+                disabledContainerColor = DarkPurple,
+                disabledContentColor = Color.White,
             ),
+            enabled = isEnabled,
             contentPadding = PaddingValues(start = 16.dp, end = 16.dp)
         ) {
             Row(horizontalArrangement = Arrangement.Start, modifier = Modifier.fillMaxWidth()) {

+ 58 - 0
App/app/src/main/java/com/example/mystictale/resources/components/ImageForBitmap.kt

@@ -0,0 +1,58 @@
+package com.example.mystictale.resources.components
+
+import android.net.Uri
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.result.PickVisualMediaRequest
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+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.graphics.ImageBitmap
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun ImageForBitmap(isEnabled:Boolean, image:ImageBitmap, launcher: ManagedActivityResultLauncher<PickVisualMediaRequest, Uri?>) {
+    Box(
+        modifier = Modifier
+            .width(100.dp)
+            .height(100.dp)
+            .clip(RoundedCornerShape(50.dp))
+    ) {
+        Button(
+            onClick = {
+                launcher.launch(
+                    PickVisualMediaRequest(
+                        ActivityResultContracts.PickVisualMedia.ImageOnly
+                    )
+                )
+            },
+            colors = ButtonDefaults.buttonColors(
+                containerColor = Color.White,
+                disabledContainerColor = Color.White
+            ), modifier = Modifier
+                .width(100.dp)
+                .height(100.dp),
+            enabled = isEnabled,
+            contentPadding = PaddingValues(0.dp)
+        ) {
+            Image(
+                modifier = Modifier
+                    .height(100.dp)
+                    .width(100.dp),
+                bitmap = image,
+                contentDescription = "",
+                contentScale = ContentScale.Crop
+            )
+        }
+    }
+}

+ 58 - 0
App/app/src/main/java/com/example/mystictale/resources/components/ImagePainter.kt

@@ -0,0 +1,58 @@
+package com.example.mystictale.resources.components
+
+import android.net.Uri
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.result.PickVisualMediaRequest
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+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.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun ImagePainter(isEnabled:Boolean, image:Painter, launcher: ManagedActivityResultLauncher<PickVisualMediaRequest, Uri?>) {
+    Box(
+        modifier = Modifier
+            .width(100.dp)
+            .height(100.dp)
+            .clip(RoundedCornerShape(50.dp))
+    ) {
+        Button(
+            onClick = {
+                launcher.launch(
+                    PickVisualMediaRequest(
+                        ActivityResultContracts.PickVisualMedia.ImageOnly
+                    )
+                )
+            },
+            colors = ButtonDefaults.buttonColors(
+                containerColor = Color.White,
+                disabledContainerColor = Color.White
+            ), modifier = Modifier
+                .width(100.dp)
+                .height(100.dp),
+            enabled = isEnabled,
+            contentPadding = PaddingValues(0.dp)
+        ) {
+            Image(
+                modifier = Modifier
+                    .height(100.dp)
+                    .width(100.dp),
+                painter = image,
+                contentDescription = "",
+                contentScale = ContentScale.Crop
+            )
+        }
+    }
+}

+ 8 - 1
App/app/src/main/java/com/example/mystictale/resources/components/ProfileTextField.kt

@@ -14,8 +14,10 @@ import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.VisualTransformation
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import com.example.mystictale.resources.components.dateComponents.dateOfBirthVisualTransformation
 import com.example.mystictale.ui.theme.DarkPurple
 import com.example.mystictale.ui.theme.Grey
 import com.example.mystictale.ui.theme.OpenSans
@@ -28,7 +30,8 @@ fun ProfileTextField(
     placeholder: String,
     keyboardType: KeyboardType,
     flag: Boolean,
-    isEnable: Boolean
+    isEnable: Boolean,
+    date:Boolean
 ) {
     OutlinedTextField(
         value = value,
@@ -56,6 +59,10 @@ fun ProfileTextField(
             disabledPlaceholderColor = Color.White,
         ),
         isError = flag,
+        visualTransformation = if(date){
+            dateOfBirthVisualTransformation()
+        }else{
+            VisualTransformation.None},
 
         placeholder = {
             Text(

+ 92 - 0
App/app/src/main/java/com/example/mystictale/resources/components/dateComponents/DateOfBirthTextFieldForProfile.kt

@@ -0,0 +1,92 @@
+package com.example.mystictale.resources.components.dateComponents
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.DateRange
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+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.FontWeight
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.mystictale.ui.theme.DarkPurple
+import com.example.mystictale.ui.theme.Grey
+import com.example.mystictale.ui.theme.OpenSans
+import com.example.mystictale.ui.theme.Purple
+import com.vanpra.composematerialdialogs.MaterialDialogState
+
+@Composable
+fun DateOfBirthTextFieldForProfile(
+    dateOfBirth: String,
+    onValue: (String) -> Unit,
+    dateDialogState: MaterialDialogState,
+    flag: Boolean,
+    enabled: Boolean
+) {
+    val padding = 10.dp;
+    TextField(
+        value = dateOfBirth,
+        onValueChange = onValue,
+        Modifier
+            .fillMaxWidth()
+            .clip(RoundedCornerShape(10.dp)),
+        keyboardOptions = KeyboardOptions(
+            keyboardType = KeyboardType.Number
+        ),
+
+        colors = TextFieldDefaults.colors(
+            unfocusedTextColor = Color.White,
+            unfocusedPlaceholderColor = Grey,
+            unfocusedContainerColor = Purple,
+            focusedTextColor = Color.White,
+            focusedContainerColor = Purple,
+            focusedPlaceholderColor = Color.White,
+            focusedIndicatorColor = Color.Transparent,
+            unfocusedIndicatorColor = Color.Transparent,
+            errorContainerColor = Purple    ,
+            errorTextColor = Color.White,
+            errorIndicatorColor = Color.Transparent,
+            errorPlaceholderColor = Color.White,
+            disabledTextColor = Color.White,
+            disabledContainerColor = DarkPurple,
+            disabledPlaceholderColor = Color.White,
+        ),
+        isError = flag,
+        enabled = enabled,
+        trailingIcon = {
+            Icon(
+                Icons.Filled.DateRange,
+                contentDescription = "Дата рождения",
+                tint = if (enabled) {
+                    Color.White
+                } else {
+                    DarkPurple
+                },
+                modifier = Modifier
+                    .offset(x = -padding)
+                    .padding(end = 2.dp)
+                    .clickable { dateDialogState.show() }
+            )
+        },
+        visualTransformation = dateOfBirthVisualTransformation(),
+        placeholder = { Text("Дата рождения") },
+        maxLines = 1,
+        textStyle = TextStyle(
+            fontFamily = OpenSans,
+            fontWeight = FontWeight.Light,
+            fontSize = 15.sp
+        ),
+    )
+}

+ 22 - 0
App/app/src/main/java/com/example/mystictale/resources/components/dateComponents/FormattingDateForProfile.kt

@@ -0,0 +1,22 @@
+package com.example.mystictale.resources.components.dateComponents
+
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+
+fun FormattingDateForProfile(inputDate: String): String? {
+    return try {
+        // Формат входной даты
+        val inputFormat = SimpleDateFormat("ddMMyyyy", Locale.getDefault())
+        // Формат выходной даты
+        val outputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+
+        // Парсинг входной строки в дату
+        val date = outputFormat.parse(inputDate)
+        // Преобразование даты в строку нужного формата
+        inputFormat.format(date!!)
+    } catch (e: Exception) {
+        // Если возникнет ошибка, возвращаем null
+        null
+    }
+}

+ 2 - 0
App/gradle/libs.versions.toml

@@ -1,6 +1,7 @@
 [versions]
 agp = "8.5.1"
 bom = "3.0.2"
+coilCompose = "2.4.0"
 fragmentKtx = "1.8.5"
 gson = "2.10.1"
 jacksonModuleKotlin = "2.12.1"
@@ -27,6 +28,7 @@ androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-
 androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
 auth-kt = { module = "io.github.jan-tennert.supabase:auth-kt" }
 bom = { module = "io.github.jan-tennert.supabase:bom", version.ref = "bom" }
+coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
 gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
 jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jacksonModuleKotlin" }
 junit = { group = "junit", name = "junit", version.ref = "junit" }