Fix async chat actions and channel sync
This commit is contained in:
@@ -328,21 +328,35 @@ private enum class ChatBulkUiAction {
|
||||
Delete
|
||||
}
|
||||
|
||||
private enum class MainTab {
|
||||
Chats,
|
||||
Contacts,
|
||||
Channels,
|
||||
Settings
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ChatListScreen(vm: QMaxViewModel) {
|
||||
val state by vm.state
|
||||
var activeTab by rememberSaveable { mutableStateOf(MainTab.Chats) }
|
||||
var menuOpen by remember { mutableStateOf(false) }
|
||||
var selectionMenuOpen by remember { mutableStateOf(false) }
|
||||
var pendingBulkAction by remember { mutableStateOf<ChatBulkUiAction?>(null) }
|
||||
var searchMode by remember { mutableStateOf(false) }
|
||||
var newChatOpen by remember { mutableStateOf(false) }
|
||||
val filteredChats = remember(state.chats, state.searchQuery) {
|
||||
val filteredChats = remember(state.chats, state.searchQuery, activeTab) {
|
||||
val query = state.searchQuery.trim()
|
||||
val source = when (activeTab) {
|
||||
MainTab.Chats -> state.chats.filterNot { it.isChannel() }
|
||||
MainTab.Channels -> state.chats.filter { it.isChannel() }
|
||||
MainTab.Contacts,
|
||||
MainTab.Settings -> emptyList()
|
||||
}
|
||||
if (query.isBlank()) {
|
||||
state.chats
|
||||
source
|
||||
} else {
|
||||
state.chats.filter {
|
||||
source.filter {
|
||||
it.title.contains(query, ignoreCase = true) ||
|
||||
(it.lastMessagePreview?.contains(query, ignoreCase = true) == true)
|
||||
}
|
||||
@@ -352,12 +366,18 @@ private fun ChatListScreen(vm: QMaxViewModel) {
|
||||
TelegramChatListContent(
|
||||
state = state,
|
||||
filteredChats = filteredChats,
|
||||
activeTab = activeTab,
|
||||
menuOpen = menuOpen,
|
||||
onMenuOpenChange = { menuOpen = it },
|
||||
onRefresh = vm::loadChats,
|
||||
onCheckUpdates = vm::checkArgusUpdate,
|
||||
onLogout = vm::logout,
|
||||
onSearch = vm::updateSearchQuery,
|
||||
onTabSelected = { tab ->
|
||||
activeTab = tab
|
||||
vm.updateSearchQuery("")
|
||||
vm.clearChatSelection()
|
||||
},
|
||||
onOpenChat = vm::openChat,
|
||||
onNewChat = { newChatOpen = true },
|
||||
onCacheAvatar = vm::cacheAvatar,
|
||||
@@ -580,12 +600,14 @@ private fun ChatListScreen(vm: QMaxViewModel) {
|
||||
private fun TelegramChatListContent(
|
||||
state: QMaxUiState,
|
||||
filteredChats: List<ChatDto>,
|
||||
activeTab: MainTab,
|
||||
menuOpen: Boolean,
|
||||
onMenuOpenChange: (Boolean) -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
onCheckUpdates: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
onTabSelected: (MainTab) -> Unit,
|
||||
onOpenChat: (ChatDto) -> Unit,
|
||||
onNewChat: () -> Unit,
|
||||
onCacheAvatar: (String) -> Unit,
|
||||
@@ -598,6 +620,19 @@ private fun TelegramChatListContent(
|
||||
onDeleteSelectedChats: () -> Unit
|
||||
) {
|
||||
val selectedCount = state.selectedChatIds.size
|
||||
val listTab = activeTab != MainTab.Settings
|
||||
val headerTitle = when (activeTab) {
|
||||
MainTab.Chats -> if (state.chatListConnectionIssue) "Соединение..." else "QMAX"
|
||||
MainTab.Contacts -> "Контакты"
|
||||
MainTab.Channels -> "Каналы"
|
||||
MainTab.Settings -> "Настройки"
|
||||
}
|
||||
val emptyText = when (activeTab) {
|
||||
MainTab.Chats -> null
|
||||
MainTab.Contacts -> "Контактов пока нет"
|
||||
MainTab.Channels -> "Каналов пока нет"
|
||||
MainTab.Settings -> null
|
||||
}
|
||||
Box(Modifier.fillMaxSize().background(Color.White)) {
|
||||
Column(
|
||||
Modifier
|
||||
@@ -616,30 +651,31 @@ private fun TelegramChatListContent(
|
||||
)
|
||||
} else {
|
||||
TelegramLikeHeader(
|
||||
title = if (state.chatListConnectionIssue) "Соединение..." else "QMAX",
|
||||
title = headerTitle,
|
||||
menuOpen = menuOpen,
|
||||
onMenuOpenChange = onMenuOpenChange,
|
||||
onRefresh = onRefresh,
|
||||
onCheckUpdates = onCheckUpdates,
|
||||
onLogout = onLogout
|
||||
)
|
||||
}
|
||||
ChatListSearchBar(query = state.searchQuery, onQuery = onSearch)
|
||||
state.updateMessage?.let {
|
||||
Text(
|
||||
it,
|
||||
color = QMaxMuted,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 28.dp, vertical = 6.dp)
|
||||
)
|
||||
}
|
||||
ErrorLine(state.error)
|
||||
if (filteredChats.isEmpty()) {
|
||||
if (activeTab == MainTab.Settings) {
|
||||
SettingsTabContent(
|
||||
state = state,
|
||||
onCheckUpdates = onCheckUpdates,
|
||||
onRefresh = onRefresh,
|
||||
onLogout = onLogout
|
||||
)
|
||||
} else {
|
||||
ChatListSearchBar(query = state.searchQuery, onQuery = onSearch)
|
||||
}
|
||||
if (listTab && filteredChats.isEmpty()) {
|
||||
if (state.searchQuery.isNotBlank()) {
|
||||
EmptyState("Ничего не найдено")
|
||||
} else {
|
||||
Spacer(Modifier.fillMaxSize())
|
||||
emptyText?.let { EmptyState(it) } ?: Spacer(Modifier.fillMaxSize())
|
||||
}
|
||||
} else {
|
||||
} else if (listTab) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 118.dp)
|
||||
@@ -667,19 +703,24 @@ private fun TelegramChatListContent(
|
||||
}
|
||||
}
|
||||
|
||||
ChatListFloatingActions(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.navigationBarsPadding()
|
||||
.padding(end = 22.dp, bottom = 96.dp),
|
||||
onNewChat = onNewChat
|
||||
)
|
||||
if (activeTab == MainTab.Chats) {
|
||||
ChatListFloatingActions(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.navigationBarsPadding()
|
||||
.padding(end = 22.dp, bottom = 96.dp),
|
||||
onNewChat = onNewChat
|
||||
)
|
||||
}
|
||||
TelegramBottomNavigation(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.navigationBarsPadding()
|
||||
.padding(horizontal = 28.dp, vertical = 10.dp),
|
||||
unreadCount = state.chats.sumOf { it.unreadCount }
|
||||
activeTab = activeTab,
|
||||
chatUnreadCount = state.chats.filterNot { it.isChannel() }.sumOf { it.unreadCount },
|
||||
channelUnreadCount = state.chats.filter { it.isChannel() }.sumOf { it.unreadCount },
|
||||
onTabSelected = onTabSelected
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -690,7 +731,6 @@ private fun TelegramLikeHeader(
|
||||
menuOpen: Boolean,
|
||||
onMenuOpenChange: (Boolean) -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
onCheckUpdates: () -> Unit,
|
||||
onLogout: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
@@ -723,13 +763,6 @@ private fun TelegramLikeHeader(
|
||||
onRefresh()
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Проверить обновления") },
|
||||
onClick = {
|
||||
onMenuOpenChange(false)
|
||||
onCheckUpdates()
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Выйти") },
|
||||
onClick = {
|
||||
@@ -1033,7 +1066,64 @@ private fun ChatListFloatingActions(modifier: Modifier = Modifier, onNewChat: ()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TelegramBottomNavigation(modifier: Modifier = Modifier, unreadCount: Int) {
|
||||
private fun SettingsTabContent(
|
||||
state: QMaxUiState,
|
||||
onCheckUpdates: () -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
onLogout: () -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(start = 28.dp, top = 14.dp, end = 28.dp, bottom = 118.dp)
|
||||
) {
|
||||
item {
|
||||
Text("Версия приложения", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = QMaxText)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(BuildConfig.VERSION_NAME, style = MaterialTheme.typography.bodyLarge, color = QMaxMuted)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Button(onClick = onCheckUpdates, enabled = !state.loading) {
|
||||
Text(if (state.loading) "Проверяю..." else "Проверить обновление")
|
||||
}
|
||||
state.updateMessage?.let {
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Text(it, color = QMaxMuted, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
Spacer(Modifier.height(22.dp))
|
||||
HorizontalDivider(color = Color(0xFFE7EEF3))
|
||||
}
|
||||
item {
|
||||
Spacer(Modifier.height(22.dp))
|
||||
Text("MAX", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = QMaxText)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
state.maxStatus?.status ?: "Статус неизвестен",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = QMaxMuted
|
||||
)
|
||||
state.maxStatus?.lastError?.takeIf { it.isNotBlank() }?.let {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(it, style = MaterialTheme.typography.bodyMedium, color = Color(0xFFC62828))
|
||||
}
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Button(onClick = onRefresh, enabled = !state.loading) {
|
||||
Text("Обновить чаты")
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
TextButton(onClick = onLogout) {
|
||||
Text("Выйти")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TelegramBottomNavigation(
|
||||
modifier: Modifier = Modifier,
|
||||
activeTab: MainTab,
|
||||
chatUnreadCount: Int,
|
||||
channelUnreadCount: Int,
|
||||
onTabSelected: (MainTab) -> Unit
|
||||
) {
|
||||
Surface(
|
||||
color = Color.White,
|
||||
shape = RoundedCornerShape(30.dp),
|
||||
@@ -1046,24 +1136,29 @@ private fun TelegramBottomNavigation(modifier: Modifier = Modifier, unreadCount:
|
||||
) {
|
||||
TelegramBottomNavItem(
|
||||
label = "Чаты",
|
||||
selected = true,
|
||||
selected = activeTab == MainTab.Chats,
|
||||
icon = { Icon(Icons.Filled.Chat, contentDescription = null) },
|
||||
badge = unreadCount.takeIf { it > 0 }
|
||||
badge = chatUnreadCount.takeIf { it > 0 },
|
||||
onClick = { onTabSelected(MainTab.Chats) }
|
||||
)
|
||||
TelegramBottomNavItem(
|
||||
label = "Контакты",
|
||||
selected = false,
|
||||
icon = { Icon(Icons.Filled.Person, contentDescription = null) }
|
||||
selected = activeTab == MainTab.Contacts,
|
||||
icon = { Icon(Icons.Filled.Person, contentDescription = null) },
|
||||
onClick = { onTabSelected(MainTab.Contacts) }
|
||||
)
|
||||
TelegramBottomNavItem(
|
||||
label = "Каналы",
|
||||
selected = false,
|
||||
icon = { Icon(Icons.Filled.Campaign, contentDescription = null) }
|
||||
selected = activeTab == MainTab.Channels,
|
||||
icon = { Icon(Icons.Filled.Campaign, contentDescription = null) },
|
||||
badge = channelUnreadCount.takeIf { it > 0 },
|
||||
onClick = { onTabSelected(MainTab.Channels) }
|
||||
)
|
||||
TelegramBottomNavItem(
|
||||
label = "Настройки",
|
||||
selected = false,
|
||||
icon = { Icon(Icons.Filled.Settings, contentDescription = null) }
|
||||
selected = activeTab == MainTab.Settings,
|
||||
icon = { Icon(Icons.Filled.Settings, contentDescription = null) },
|
||||
onClick = { onTabSelected(MainTab.Settings) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1074,13 +1169,15 @@ private fun RowScope.TelegramBottomNavItem(
|
||||
label: String,
|
||||
selected: Boolean,
|
||||
icon: @Composable () -> Unit,
|
||||
badge: Int? = null
|
||||
badge: Int? = null,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val tint = if (selected) Color(0xFF20BFA9) else QMaxText
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clip(RoundedCornerShape(26.dp))
|
||||
.clickable(onClick = onClick)
|
||||
.background(if (selected) Color(0xFFE6FAF5) else Color.Transparent)
|
||||
.padding(vertical = 6.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
@@ -1116,6 +1213,10 @@ private fun RowScope.TelegramBottomNavItem(
|
||||
}
|
||||
}
|
||||
|
||||
private fun ChatDto.isChannel(): Boolean {
|
||||
return kind.equals("Channel", ignoreCase = true)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NewChatDialog(vm: QMaxViewModel, onDismiss: () -> Unit, onCreate: () -> Unit) {
|
||||
val state by vm.state
|
||||
|
||||
Reference in New Issue
Block a user