Приложение

This commit is contained in:
Халимов Рустам
2026-04-14 01:15:54 +03:00
parent 8399d32490
commit 1fb1be47dd
126 changed files with 6145 additions and 0 deletions
@@ -0,0 +1,26 @@
package contacts.data.remote.api
import retrofit2.http.*
interface ContactApi {
@GET("contacts")
suspend fun getContacts(): List<ContactDto>
@POST("contacts/add")
suspend fun addContact(@Body userId: String): ContactDto
@DELETE("contacts/{userId}")
suspend fun removeContact(@Path("userId") userId: String)
@GET("users/search")
suspend fun searchUsers(@Query("query") query: String): List<ContactDto>
}
data class ContactDto(
val id: String,
val username: String,
val displayName: String?,
val avatarUrl: String?,
val isOnline: Boolean,
val lastSeen: String?
)
@@ -0,0 +1,130 @@
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.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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<ContactDto>,
isLoading: Boolean = false,
onContactClick: (String) -> Unit,
onSearchChange: (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)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
)
)
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 = contacts.filter {
if (selectedTab == 1) it.isOnline else true
}
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(filteredContacts) { contact ->
ContactItem(contact = contact, onClick = { onContactClick(contact.id) })
}
}
}
}
}
}
@Composable
fun ContactItem(contact: ContactDto, onClick: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(16.dp, 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
AppAvatar(
url = contact.avatarUrl,
name = contact.username,
size = 56.dp
)
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = contact.displayName ?: contact.username,
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
)
}
}
}
@@ -0,0 +1,26 @@
package contacts.presentation
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import contacts.data.remote.api.ContactDto
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
data class ContactListState(
val contacts: List<ContactDto> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)
@HiltViewModel
class ContactListViewModel @Inject constructor() : ViewModel() {
private val _state = MutableStateFlow(ContactListState())
val state: StateFlow<ContactListState> = _state.asStateFlow()
fun onSearchChange(query: String) {
// Заглушка поиска
}
}