Files
forkmessager/client-mobile/profiles/presentation/EditProfileScreen.kt
2026-04-16 15:41:22 +03:00

326 lines
13 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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))
}
}
}
}
}