Files
forkmessager/client-mobile/profiles/presentation/ProfileScreen.kt
Халимов Рустам 1fb1be47dd Приложение
2026-04-14 01:15:54 +03:00

305 lines
11 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 androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.lazy.grid.*
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
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.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.AsyncImage
import core.presentation.components.AppAvatar
import core.presentation.theme.SoftSquareShape
import kotlinx.coroutines.launch
import profiles.presentation.viewmodel.ProfileViewModel
import ru.knot.messager.R
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun ProfileScreen(
profileId: String? = null, // null for own profile
viewModel: ProfileViewModel = hiltViewModel(),
onEditProfile: () -> Unit = {},
onSendMessage: (String) -> Unit = {},
onCall: (String) -> Unit = {},
onBack: () -> Unit = {}
) {
val state by viewModel.state.collectAsState()
LaunchedEffect(profileId) {
viewModel.loadProfile(profileId)
}
val profile = state.profile
val isOwnProfile = profileId == null
val mediaTabs = listOf(
stringResource(R.string.media),
"GIF",
"Файлы",
"Ссылки"
)
val pagerState = rememberPagerState(pageCount = { mediaTabs.size })
val coroutineScope = rememberCoroutineScope()
if (state.isLoading) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
return
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(if (isOwnProfile) stringResource(R.string.profile) else stringResource(R.string.profile)) },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(R.string.back))
}
},
actions = {
if (isOwnProfile) {
IconButton(onClick = onEditProfile) {
Icon(Icons.Default.Edit, contentDescription = stringResource(R.string.edit_profile))
}
}
}
)
}
) { paddingValues ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
item {
ProfileHeader(
username = profile?.username ?: "",
displayName = profile?.displayName,
avatarUrl = profile?.avatarUrl,
bio = profile?.bio,
isOwnProfile = isOwnProfile,
isCallsEnabled = true,
onSendMessage = { profile?.id?.let { id -> onSendMessage(id) } },
onCall = { profile?.id?.let { id -> onCall(id) } }
)
}
stickyHeader {
Surface(modifier = Modifier.fillMaxWidth()) {
ScrollableTabRow(
selectedTabIndex = pagerState.currentPage,
edgePadding = 16.dp,
containerColor = MaterialTheme.colorScheme.surface,
divider = {}
) {
mediaTabs.forEachIndexed { index, title ->
Tab(
selected = pagerState.currentPage == index,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
},
text = { Text(title) }
)
}
}
}
}
// Using item with fillParentMaxSize for Pager inside LazyColumn
// is tricky, but here we render content directly in LazyColumn
// to maintain scrolling, or use a fixed height.
// For gestures, we implement HorizontalPager for the content:
}
// Alternative: Use HorizontalPager for the whole content area below header
// But to keep header scrolling, we need nested scroll or a different approach.
// Let's implement HorizontalPager for media content items specifically.
}
}
@Composable
fun ProfileHeader(
username: String,
displayName: String?,
avatarUrl: String?,
bio: String?,
isOwnProfile: Boolean,
isCallsEnabled: Boolean,
onSendMessage: () -> Unit,
onCall: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(contentAlignment = Alignment.BottomEnd) {
AppAvatar(
url = avatarUrl,
name = username,
size = 120.dp
)
if (isOwnProfile) {
FilledIconButton(
onClick = { /* Выбор фото */ },
modifier = Modifier.size(32.dp).offset(x = 4.dp, y = 4.dp),
shape = CircleShape,
colors = IconButtonDefaults.filledIconButtonColors(containerColor = MaterialTheme.colorScheme.primary)
) {
Icon(Icons.Default.CameraAlt, contentDescription = null, modifier = Modifier.size(16.dp))
}
}
}
Spacer(modifier = Modifier.height(16.dp))
Text(text = displayName ?: username, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(text = "@$username", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
if (!isOwnProfile) {
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
ActionCircleButton(Icons.Default.Message, stringResource(R.string.message), onSendMessage)
if (isCallsEnabled) {
ActionCircleButton(Icons.Default.Call, stringResource(R.string.call), onCall)
}
ActionCircleButton(Icons.Default.Notifications, stringResource(R.string.notifications), {})
}
}
if (bio != null) {
Spacer(modifier = Modifier.height(24.dp))
Column(modifier = Modifier.fillMaxWidth()) {
Text(text = stringResource(R.string.bio), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary)
Text(text = bio, style = MaterialTheme.typography.bodyMedium)
}
}
}
}
fun LazyListScope.renderMediaContent(tabIndex: Int) {
// В реальном приложении данные приходят из ViewModel
// Группировка по месяцам
val months = listOf("Октябрь 2023", "Сентябрь 2023")
months.forEach { month ->
item {
Text(
text = month,
modifier = Modifier.padding(16.dp, 8.dp),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
when (tabIndex) {
0 -> renderMediaGrid() // Медиа (сетка 3x3)
1 -> renderGifs() // GIF
2 -> renderFiles() // Файлы
3 -> renderLinks() // Ссылки
}
}
}
// Вспомогательные функции для рендеринга контента...
fun LazyListScope.renderMediaGrid() {
item {
// Упрощенная сетка внутри LazyColumn
Row(modifier = Modifier.padding(horizontal = 16.dp)) {
repeat(3) {
Box(
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
.padding(2.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color.Gray.copy(alpha = 0.2f))
)
}
}
}
}
fun LazyListScope.renderFiles() {
items(3) {
ListItem(
headlineContent = { Text("Document.pdf") },
supportingContent = { Text("2.4 MB • 12.10.23") },
leadingContent = { Icon(Icons.Default.Description, contentDescription = null) }
)
}
}
fun LazyListScope.renderLinks() {
items(2) {
ListItem(
headlineContent = { Text("https://github.com/forkmessager") },
supportingContent = { Text("GitHub - ForkMessager Project") },
leadingContent = { Icon(Icons.Default.Link, contentDescription = null) }
)
}
}
fun LazyListScope.renderGifs() {
item {
Row(modifier = Modifier.padding(horizontal = 16.dp)) {
repeat(2) {
Box(
modifier = Modifier
.weight(1f)
.height(120.dp)
.padding(2.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color.Gray.copy(alpha = 0.1f)),
contentAlignment = Alignment.Center
) {
Text("GIF", style = MaterialTheme.typography.labelSmall)
}
}
}
}
}
@Composable
fun ActionCircleButton(
icon: androidx.compose.ui.graphics.vector.ImageVector,
label: String,
onClick: () -> Unit,
color: Color = MaterialTheme.colorScheme.primary
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
FilledIconButton(
onClick = onClick,
modifier = Modifier.size(56.dp),
colors = IconButtonDefaults.filledIconButtonColors(containerColor = color.copy(alpha = 0.1f))
) {
Icon(icon, contentDescription = label, tint = color)
}
Spacer(modifier = Modifier.height(4.dp))
Text(text = label, style = MaterialTheme.typography.labelSmall)
}
}