android: update chats list header and archive flow to match reference
This commit is contained in:
@@ -509,3 +509,8 @@
|
|||||||
### Step 81 - Chats bottom gap fix when tabs bar hidden
|
### Step 81 - Chats bottom gap fix when tabs bar hidden
|
||||||
- Fixed blank gap at the bottom of chats list when global tabs bar auto-hides on scroll.
|
- Fixed blank gap at the bottom of chats list when global tabs bar auto-hides on scroll.
|
||||||
- Chats screen bottom padding is now dynamic and applied only while tabs bar is visible.
|
- Chats screen bottom padding is now dynamic and applied only while tabs bar is visible.
|
||||||
|
|
||||||
|
### Step 82 - Chats list header closer to Telegram reference
|
||||||
|
- Removed `Archived` top tab from chats list UI.
|
||||||
|
- Added search action in top app bar and unified single search field with leading search icon.
|
||||||
|
- Kept archive as dedicated row inside chats list; opening archive now happens from that row and back navigation appears in app bar while archive is active.
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Tab
|
|
||||||
import androidx.compose.material3.TabRow
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
@@ -53,8 +51,10 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
||||||
@@ -134,6 +134,7 @@ fun ChatListScreen(
|
|||||||
onUnbanMember: (Long, Long) -> Unit,
|
onUnbanMember: (Long, Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
var managementExpanded by remember { mutableStateOf(false) }
|
var managementExpanded by remember { mutableStateOf(false) }
|
||||||
|
var searchExpanded by remember { mutableStateOf(true) }
|
||||||
var createTitle by remember { mutableStateOf("") }
|
var createTitle by remember { mutableStateOf("") }
|
||||||
var createMemberIds by remember { mutableStateOf("") }
|
var createMemberIds by remember { mutableStateOf("") }
|
||||||
var createHandle by remember { mutableStateOf("") }
|
var createHandle by remember { mutableStateOf("") }
|
||||||
@@ -178,49 +179,54 @@ fun ChatListScreen(
|
|||||||
.padding(bottom = if (isMainBarVisible) 92.dp else 0.dp),
|
.padding(bottom = if (isMainBarVisible) 92.dp else 0.dp),
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Chats") },
|
navigationIcon = {
|
||||||
|
if (state.selectedTab == ChatTab.ARCHIVED) {
|
||||||
|
IconButton(onClick = { onTabSelected(ChatTab.ALL) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = "Back to chats",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(if (state.selectedTab == ChatTab.ARCHIVED) "Archived" else "Chats")
|
||||||
|
},
|
||||||
actions = {
|
actions = {
|
||||||
|
IconButton(onClick = { searchExpanded = !searchExpanded }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Search,
|
||||||
|
contentDescription = if (searchExpanded) "Hide search" else "Show search",
|
||||||
|
)
|
||||||
|
}
|
||||||
IconButton(onClick = { managementExpanded = !managementExpanded }) {
|
IconButton(onClick = { managementExpanded = !managementExpanded }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (managementExpanded) Icons.Filled.Close else Icons.Filled.Add,
|
imageVector = if (managementExpanded) Icons.Filled.Close else Icons.Filled.MoreVert,
|
||||||
contentDescription = if (managementExpanded) "Close management" else "Open management",
|
contentDescription = if (managementExpanded) "Close menu" else "Open menu",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
TabRow(
|
if (searchExpanded) {
|
||||||
selectedTabIndex = if (state.selectedTab == ChatTab.ALL) 0 else 1,
|
|
||||||
) {
|
|
||||||
Tab(
|
|
||||||
selected = state.selectedTab == ChatTab.ALL,
|
|
||||||
onClick = { onTabSelected(ChatTab.ALL) },
|
|
||||||
text = { Text(text = "All") },
|
|
||||||
)
|
|
||||||
Tab(
|
|
||||||
selected = state.selectedTab == ChatTab.ARCHIVED,
|
|
||||||
onClick = { onTabSelected(ChatTab.ARCHIVED) },
|
|
||||||
text = { Text(text = "Archived") },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.searchQuery,
|
value = state.searchQuery,
|
||||||
onValueChange = onSearchChanged,
|
onValueChange = {
|
||||||
label = { Text(text = "Search title / username / handle") },
|
onSearchChanged(it)
|
||||||
|
onGlobalSearchChanged(it)
|
||||||
|
},
|
||||||
|
label = { Text(text = "Search chats") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Search,
|
||||||
|
contentDescription = "Search",
|
||||||
|
)
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
}
|
||||||
value = state.globalSearchQuery,
|
|
||||||
onValueChange = onGlobalSearchChanged,
|
|
||||||
label = { Text(text = "Global search users/chats/messages") },
|
|
||||||
singleLine = true,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
|
||||||
)
|
|
||||||
if (state.globalSearchQuery.trim().length >= 2) {
|
if (state.globalSearchQuery.trim().length >= 2) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
- [ ] Chat row: avatar + title + preview + time + unread badge.
|
- [ ] Chat row: avatar + title + preview + time + unread badge.
|
||||||
- [ ] Состояния pinned/muted и mention/read indicator в preview-строке.
|
- [ ] Состояния pinned/muted и mention/read indicator в preview-строке.
|
||||||
- [ ] FAB справа снизу (compose/add contact).
|
- [ ] FAB справа снизу (compose/add contact).
|
||||||
|
- [x] Архив вынесен из верхних tab и отображается отдельной строкой списка.
|
||||||
|
|
||||||
## P1 — Privacy/Data/Notifications Screens
|
## P1 — Privacy/Data/Notifications Screens
|
||||||
- [ ] Privacy screen: value справа фиолетовым текстом ("Все", "Контакты").
|
- [ ] Privacy screen: value справа фиолетовым текстом ("Все", "Контакты").
|
||||||
|
|||||||
Reference in New Issue
Block a user