254 lines
9.6 KiB
Kotlin
254 lines
9.6 KiB
Kotlin
package contacts.presentation
|
|
|
|
import androidx.compose.foundation.clickable
|
|
import androidx.compose.foundation.layout.*
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
import androidx.compose.foundation.lazy.items
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.filled.Search
|
|
import androidx.compose.material.icons.filled.PersonAdd
|
|
import androidx.compose.material.icons.filled.Chat
|
|
import androidx.compose.material.icons.filled.CheckCircle
|
|
import androidx.compose.material.icons.filled.Cancel
|
|
import androidx.compose.material3.*
|
|
import androidx.compose.runtime.*
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.compose.ui.res.stringResource
|
|
import contacts.data.remote.api.ContactDto
|
|
import core.presentation.components.AppAvatar
|
|
import core.presentation.theme.SoftSquareShape
|
|
import ru.knot.messager.R
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
@Composable
|
|
fun ContactListScreen(
|
|
contacts: List<contacts.data.remote.api.ContactDto>,
|
|
requests: List<contacts.data.remote.api.FriendRequestDto> = emptyList(),
|
|
isLoading: Boolean = false,
|
|
onContactClick: (String) -> Unit,
|
|
onSearchChange: (String) -> Unit,
|
|
onAddContact: (String) -> Unit,
|
|
onStartChat: (String) -> Unit,
|
|
onAcceptRequest: (String) -> Unit,
|
|
onDeclineRequest: (String) -> Unit
|
|
) {
|
|
var searchQuery by remember { mutableStateOf("") }
|
|
var selectedTab by remember { mutableIntStateOf(0) }
|
|
val tabs = listOf(
|
|
stringResource(R.string.all),
|
|
stringResource(R.string.online_tab)
|
|
)
|
|
|
|
Scaffold(
|
|
topBar = {
|
|
Column {
|
|
TopAppBar(
|
|
title = { Text(stringResource(R.string.contacts_title), fontWeight = androidx.compose.ui.text.font.FontWeight.Bold) },
|
|
colors = TopAppBarDefaults.topAppBarColors(
|
|
containerColor = Color.Transparent,
|
|
titleContentColor = Color.White
|
|
)
|
|
)
|
|
TabRow(selectedTabIndex = selectedTab) {
|
|
tabs.forEachIndexed { index, title ->
|
|
Tab(
|
|
selected = selectedTab == index,
|
|
onClick = { selectedTab = index },
|
|
text = { Text(title) }
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
) { paddingValues ->
|
|
Column(
|
|
modifier = Modifier
|
|
.fillMaxSize()
|
|
.padding(paddingValues)
|
|
) {
|
|
// Поисковая строка
|
|
OutlinedTextField(
|
|
value = searchQuery,
|
|
onValueChange = {
|
|
searchQuery = it
|
|
onSearchChange(it)
|
|
},
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
|
placeholder = { Text(stringResource(R.string.search_hint)) },
|
|
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
|
|
shape = SoftSquareShape
|
|
)
|
|
|
|
if (isLoading) {
|
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
CircularProgressIndicator()
|
|
}
|
|
} else {
|
|
val filteredContacts = if (searchQuery.isNotEmpty()) {
|
|
contacts
|
|
} else {
|
|
contacts.filter {
|
|
if (selectedTab == 1) it.isOnline else true
|
|
}
|
|
}
|
|
|
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
|
// Contact Requests Section
|
|
if (searchQuery.isEmpty() && requests.isNotEmpty()) {
|
|
item {
|
|
Text(
|
|
text = "Заявки в контакты",
|
|
style = MaterialTheme.typography.labelSmall,
|
|
color = MaterialTheme.colorScheme.primary,
|
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
)
|
|
}
|
|
items(requests) { request ->
|
|
ContactRequestItem(
|
|
request = request,
|
|
onAccept = { onAcceptRequest(request.id) },
|
|
onDecline = { onDeclineRequest(request.id) }
|
|
)
|
|
}
|
|
item {
|
|
Divider(modifier = Modifier.padding(vertical = 8.dp), color = MaterialTheme.colorScheme.outlineVariant)
|
|
}
|
|
}
|
|
|
|
if (filteredContacts.isEmpty() && requests.isEmpty()) {
|
|
item {
|
|
Box(modifier = Modifier.fillParentMaxSize(), contentAlignment = Alignment.Center) {
|
|
Text(
|
|
text = if (searchQuery.isNotEmpty()) "Пользователи не найдены" else "Список контактов пуст",
|
|
style = MaterialTheme.typography.bodyMedium,
|
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
items(filteredContacts) { contact ->
|
|
ContactItem(
|
|
contact = contact,
|
|
onClick = { onContactClick(contact.id) },
|
|
onAddClick = { onAddContact(contact.id) },
|
|
onChatClick = { onStartChat(contact.id) },
|
|
isSearchMode = searchQuery.isNotEmpty()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun ContactRequestItem(
|
|
request: contacts.data.remote.api.FriendRequestDto,
|
|
onAccept: () -> Unit,
|
|
onDecline: () -> Unit
|
|
) {
|
|
Row(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.padding(16.dp, 8.dp),
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
AppAvatar(
|
|
url = request.user.effectiveAvatarUrl,
|
|
name = request.user.effectiveUsername,
|
|
size = 48.dp
|
|
)
|
|
Spacer(modifier = Modifier.width(12.dp))
|
|
Column(modifier = Modifier.weight(1f)) {
|
|
Text(
|
|
text = request.user.displayName ?: request.user.effectiveUsername,
|
|
style = MaterialTheme.typography.titleSmall,
|
|
color = MaterialTheme.colorScheme.onSurface
|
|
)
|
|
Text(
|
|
text = if (request.isOutgoing) "Исходящий запрос" else "Входящий запрос",
|
|
style = MaterialTheme.typography.bodySmall,
|
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
)
|
|
}
|
|
Row {
|
|
if (!request.isOutgoing) {
|
|
IconButton(onClick = onAccept) {
|
|
Icon(Icons.Default.CheckCircle, contentDescription = "Accept", tint = Color(0xFF4CAF50))
|
|
}
|
|
}
|
|
IconButton(onClick = onDecline) {
|
|
Icon(Icons.Default.Cancel, contentDescription = "Decline", tint = MaterialTheme.colorScheme.error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun ContactItem(
|
|
contact: contacts.data.remote.api.ContactDto,
|
|
onClick: () -> Unit,
|
|
onAddClick: () -> Unit,
|
|
onChatClick: () -> Unit,
|
|
isSearchMode: Boolean
|
|
) {
|
|
Row(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.clickable(onClick = onClick)
|
|
.padding(16.dp, 8.dp),
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
AppAvatar(
|
|
url = contact.effectiveAvatarUrl,
|
|
name = contact.effectiveUsername,
|
|
size = 56.dp
|
|
)
|
|
|
|
Spacer(modifier = Modifier.width(16.dp))
|
|
|
|
Column(modifier = Modifier.weight(1f)) {
|
|
Text(
|
|
text = contact.displayName ?: contact.effectiveUsername,
|
|
style = MaterialTheme.typography.titleMedium,
|
|
color = MaterialTheme.colorScheme.onSurface
|
|
)
|
|
val statusText = if (contact.isOnline) {
|
|
stringResource(R.string.online)
|
|
} else {
|
|
stringResource(R.string.last_seen, contact.lastSeen ?: stringResource(R.string.last_seen_recently))
|
|
}
|
|
Text(
|
|
text = statusText,
|
|
style = MaterialTheme.typography.bodySmall,
|
|
color = if (contact.isOnline) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant
|
|
)
|
|
}
|
|
|
|
Row {
|
|
if (isSearchMode) {
|
|
IconButton(onClick = onAddClick) {
|
|
Icon(
|
|
imageVector = Icons.Default.PersonAdd,
|
|
contentDescription = "Add Contact",
|
|
tint = MaterialTheme.colorScheme.primary
|
|
)
|
|
}
|
|
}
|
|
IconButton(onClick = onChatClick) {
|
|
Icon(
|
|
imageVector = Icons.Default.Chat,
|
|
contentDescription = "Start Chat",
|
|
tint = MaterialTheme.colorScheme.primary
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|