326 lines
13 KiB
Kotlin
326 lines
13 KiB
Kotlin
package profiles.presentation
|
||
|
||
import android.net.Uri
|
||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||
import androidx.activity.result.PickVisualMediaRequest
|
||
import androidx.activity.result.contract.ActivityResultContracts
|
||
import androidx.compose.foundation.background
|
||
import androidx.compose.foundation.clickable
|
||
import androidx.compose.foundation.layout.*
|
||
import androidx.compose.foundation.rememberScrollState
|
||
import androidx.compose.foundation.shape.CircleShape
|
||
import androidx.compose.foundation.verticalScroll
|
||
import androidx.compose.material.icons.Icons
|
||
import androidx.compose.material.icons.filled.ArrowBack
|
||
import androidx.compose.material.icons.filled.CalendarToday
|
||
import androidx.compose.material.icons.filled.CameraAlt
|
||
import androidx.compose.material.icons.filled.Check
|
||
import androidx.compose.material.icons.filled.Clear
|
||
import androidx.compose.material3.*
|
||
import androidx.compose.runtime.*
|
||
import androidx.compose.ui.Alignment
|
||
import androidx.compose.ui.Modifier
|
||
import androidx.compose.ui.draw.clip
|
||
import androidx.compose.ui.graphics.Color
|
||
import androidx.compose.ui.layout.ContentScale
|
||
import androidx.compose.ui.res.stringResource
|
||
import androidx.compose.ui.text.font.FontWeight
|
||
import androidx.compose.ui.unit.dp
|
||
import androidx.compose.ui.platform.LocalContext
|
||
import coil.compose.AsyncImage
|
||
import core.presentation.components.AppAvatar
|
||
import core.presentation.theme.SoftSquareShape
|
||
import ru.knot.messager.R
|
||
|
||
import android.app.DatePickerDialog
|
||
import java.text.SimpleDateFormat
|
||
import java.util.*
|
||
|
||
import android.app.Activity
|
||
import android.content.Intent
|
||
import com.yalantis.ucrop.UCrop
|
||
import core.utils.ImageCropper
|
||
import java.io.File
|
||
import java.util.UUID
|
||
|
||
@OptIn(ExperimentalMaterial3Api::class)
|
||
@Composable
|
||
fun EditProfileScreen(
|
||
currentDisplayName: String?,
|
||
currentUsername: String,
|
||
currentBio: String?,
|
||
currentBirthday: String?,
|
||
currentAvatarUrl: String?,
|
||
isSaving: Boolean = false,
|
||
error: String? = null,
|
||
onSave: (displayName: String, bio: String, birthday: String?, avatarUri: Uri?) -> Unit,
|
||
onBack: () -> Unit
|
||
) {
|
||
val context = LocalContext.current
|
||
var displayName by remember(currentDisplayName) { mutableStateOf(currentDisplayName ?: "") }
|
||
var bio by remember(currentBio) { mutableStateOf(currentBio ?: "") }
|
||
var birthday by remember(currentBirthday) { mutableStateOf(currentBirthday ?: "") }
|
||
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
|
||
var showDatePicker by remember { mutableStateOf(false) }
|
||
|
||
// Парсим текущую дату для инициализации DatePicker и отображения
|
||
val calendar = Calendar.getInstance()
|
||
var formattedBirthday by remember { mutableStateOf("") }
|
||
|
||
if (birthday.isNotEmpty()) {
|
||
try {
|
||
// Пробуем распарсить ISO8601 формат (yyyy-MM-dd или yyyy-MM-ddTHH:mm:ss)
|
||
val format = when {
|
||
birthday.contains("T") -> SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
|
||
birthday.length == 10 -> SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||
else -> SimpleDateFormat("dd.MM.yyyy", Locale.getDefault())
|
||
}
|
||
format.parse(birthday)?.let {
|
||
calendar.time = it
|
||
// Форматируем для отображения: ДД.ММ.ГГГГ
|
||
formattedBirthday = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()).format(it)
|
||
}
|
||
} catch (e: Exception) {
|
||
formattedBirthday = birthday
|
||
}
|
||
}
|
||
|
||
// DatePickerDialog
|
||
if (showDatePicker) {
|
||
DatePickerDialog(
|
||
context,
|
||
{ _, year, month, dayOfMonth ->
|
||
val selectedCalendar = Calendar.getInstance()
|
||
selectedCalendar.set(year, month, dayOfMonth)
|
||
// Используем ISO8601 формат для отправки на сервер
|
||
val format = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||
birthday = format.format(selectedCalendar.time)
|
||
},
|
||
calendar.get(Calendar.YEAR),
|
||
calendar.get(Calendar.MONTH),
|
||
calendar.get(Calendar.DAY_OF_MONTH)
|
||
).apply {
|
||
datePicker.maxDate = System.currentTimeMillis()
|
||
show()
|
||
}
|
||
showDatePicker = false
|
||
}
|
||
|
||
// Лаунчер для результата кропа
|
||
val cropLauncher = rememberLauncherForActivityResult(
|
||
contract = ActivityResultContracts.StartActivityForResult(),
|
||
onResult = { result ->
|
||
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
|
||
selectedImageUri = UCrop.getOutput(result.data!!)
|
||
}
|
||
}
|
||
)
|
||
|
||
// Лаунчер для выбора фото из галереи
|
||
val photoPickerLauncher = rememberLauncherForActivityResult(
|
||
contract = ActivityResultContracts.PickVisualMedia(),
|
||
onResult = { uri ->
|
||
if (uri != null) {
|
||
// После выбора фото запускаем UCrop
|
||
val destinationUri = Uri.fromFile(File(context.cacheDir, "${UUID.randomUUID()}.jpg"))
|
||
val cropIntent = ImageCropper.getCropIntent(context, uri, destinationUri).getIntent(context)
|
||
cropLauncher.launch(cropIntent)
|
||
}
|
||
}
|
||
)
|
||
|
||
Scaffold(
|
||
topBar = {
|
||
TopAppBar(
|
||
title = { Text(stringResource(R.string.edit_profile), fontWeight = FontWeight.Bold) },
|
||
navigationIcon = {
|
||
IconButton(onClick = onBack) {
|
||
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(R.string.back))
|
||
}
|
||
},
|
||
actions = {
|
||
TextButton(
|
||
onClick = { onSave(displayName, bio, birthday, selectedImageUri) },
|
||
enabled = !isSaving
|
||
) {
|
||
Text(
|
||
if (isSaving) stringResource(R.string.saving) else stringResource(R.string.save),
|
||
fontWeight = FontWeight.Bold,
|
||
color = if (isSaving) Color.Gray else MaterialTheme.colorScheme.primary
|
||
)
|
||
}
|
||
},
|
||
colors = TopAppBarDefaults.topAppBarColors(
|
||
containerColor = Color.Transparent,
|
||
titleContentColor = Color.White
|
||
)
|
||
)
|
||
}
|
||
) { paddingValues ->
|
||
Column(
|
||
modifier = Modifier
|
||
.fillMaxSize()
|
||
.padding(paddingValues)
|
||
.verticalScroll(rememberScrollState())
|
||
.padding(24.dp),
|
||
horizontalAlignment = Alignment.CenterHorizontally
|
||
) {
|
||
// Смена фото
|
||
Box(
|
||
modifier = Modifier
|
||
.size(120.dp)
|
||
.clip(SoftSquareShape)
|
||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||
.clickable {
|
||
photoPickerLauncher.launch(
|
||
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
|
||
)
|
||
},
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
if (selectedImageUri != null) {
|
||
AsyncImage(
|
||
model = selectedImageUri,
|
||
contentDescription = null,
|
||
modifier = Modifier.fillMaxSize(),
|
||
contentScale = ContentScale.Crop
|
||
)
|
||
} else {
|
||
AppAvatar(
|
||
url = currentAvatarUrl,
|
||
name = currentUsername,
|
||
size = 120.dp
|
||
)
|
||
}
|
||
|
||
// Overlay
|
||
Box(
|
||
modifier = Modifier
|
||
.fillMaxSize()
|
||
.background(Color.Black.copy(alpha = 0.4f)),
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Icon(
|
||
Icons.Default.CameraAlt,
|
||
contentDescription = stringResource(R.string.change_photo),
|
||
tint = Color.White.copy(alpha = 0.8f),
|
||
modifier = Modifier.size(28.dp)
|
||
)
|
||
}
|
||
}
|
||
|
||
TextButton(
|
||
onClick = {
|
||
photoPickerLauncher.launch(
|
||
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
|
||
)
|
||
},
|
||
modifier = Modifier.padding(top = 8.dp)
|
||
) {
|
||
Text(stringResource(R.string.change_photo))
|
||
}
|
||
|
||
Spacer(modifier = Modifier.height(32.dp))
|
||
|
||
// Поля ввода
|
||
OutlinedTextField(
|
||
value = displayName,
|
||
onValueChange = { displayName = it },
|
||
label = { Text(stringResource(R.string.display_name)) },
|
||
modifier = Modifier.fillMaxWidth(),
|
||
singleLine = true,
|
||
shape = SoftSquareShape
|
||
)
|
||
|
||
Spacer(modifier = Modifier.height(16.dp))
|
||
|
||
OutlinedTextField(
|
||
value = "@$currentUsername",
|
||
onValueChange = {},
|
||
label = { Text(stringResource(R.string.username_label)) },
|
||
modifier = Modifier.fillMaxWidth(),
|
||
enabled = false, // Обычно username меняется отдельно или вообще не меняется
|
||
shape = SoftSquareShape,
|
||
colors = OutlinedTextFieldDefaults.colors(
|
||
disabledBorderColor = MaterialTheme.colorScheme.outlineVariant,
|
||
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||
)
|
||
)
|
||
|
||
Spacer(modifier = Modifier.height(16.dp))
|
||
|
||
OutlinedTextField(
|
||
value = bio,
|
||
onValueChange = { bio = it },
|
||
label = { Text(stringResource(R.string.bio)) },
|
||
modifier = Modifier
|
||
.fillMaxWidth()
|
||
.heightIn(min = 100.dp),
|
||
shape = SoftSquareShape,
|
||
maxLines = 5
|
||
)
|
||
|
||
Spacer(modifier = Modifier.height(16.dp))
|
||
|
||
OutlinedTextField(
|
||
value = formattedBirthday,
|
||
onValueChange = { },
|
||
label = { Text("Дата рождения") },
|
||
modifier = Modifier.fillMaxWidth(),
|
||
singleLine = true,
|
||
shape = SoftSquareShape,
|
||
placeholder = { Text("ДД.ММ.ГГГГ") },
|
||
trailingIcon = {
|
||
Row {
|
||
if (birthday.isNotEmpty()) {
|
||
IconButton(onClick = { birthday = "" }) {
|
||
Icon(
|
||
Icons.Default.Clear,
|
||
contentDescription = "Очистить",
|
||
tint = Color.Gray
|
||
)
|
||
}
|
||
}
|
||
IconButton(onClick = { showDatePicker = true }) {
|
||
Icon(
|
||
Icons.Default.CalendarToday,
|
||
contentDescription = "Выбрать дату",
|
||
tint = MaterialTheme.colorScheme.primary
|
||
)
|
||
}
|
||
}
|
||
}
|
||
)
|
||
|
||
if (error != null) {
|
||
Text(
|
||
text = error,
|
||
color = MaterialTheme.colorScheme.error,
|
||
modifier = Modifier.padding(top = 8.dp)
|
||
)
|
||
}
|
||
|
||
Spacer(modifier = Modifier.height(32.dp))
|
||
|
||
Button(
|
||
onClick = { onSave(displayName, bio, birthday, selectedImageUri) },
|
||
modifier = Modifier.fillMaxWidth(),
|
||
shape = SoftSquareShape,
|
||
enabled = !isSaving
|
||
) {
|
||
if (isSaving) {
|
||
CircularProgressIndicator(
|
||
modifier = Modifier.size(24.dp),
|
||
color = Color.White,
|
||
strokeWidth = 2.dp
|
||
)
|
||
Spacer(modifier = Modifier.width(8.dp))
|
||
Text(stringResource(R.string.saving))
|
||
} else {
|
||
Text(stringResource(R.string.save))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|