Continue refinement of RoomList (and remove avatar library)
This commit is contained in:
parent
5984ffc960
commit
f55bb16bfa
22 changed files with 305 additions and 322 deletions
|
|
@ -13,6 +13,7 @@ dependencies {
|
|||
implementation(libs.mavericks.compose)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.datetime)
|
||||
implementation(libs.accompanist.placeholder)
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
|
||||
|
|
|
|||
|
|
@ -2,39 +2,30 @@
|
|||
|
||||
package io.element.android.x.features.roomlist
|
||||
|
||||
import Avatar
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExitToApp
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
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.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.compose.collectAsState
|
||||
import com.airbnb.mvrx.compose.mavericksViewModel
|
||||
import io.element.android.x.core.data.LogCompositions
|
||||
import io.element.android.x.designsystem.ElementXTheme
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.x.features.roomlist.components.RoomItem
|
||||
import io.element.android.x.features.roomlist.components.RoomListTopBar
|
||||
import io.element.android.x.features.roomlist.model.MatrixUser
|
||||
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
||||
import io.element.android.x.features.roomlist.model.RoomListViewState
|
||||
import io.element.android.x.features.roomlist.model.stubbedRoomSummaries
|
||||
import io.element.android.x.matrix.core.RoomId
|
||||
|
||||
@Composable
|
||||
|
|
@ -87,125 +78,29 @@ fun RoomListContent(
|
|||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview
|
||||
@Composable
|
||||
fun RoomListTopBar(
|
||||
matrixUser: MatrixUser?,
|
||||
onLogoutClicked: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior
|
||||
) {
|
||||
LogCompositions(tag = "RoomListScreen", msg = "TopBar")
|
||||
if (matrixUser == null) return
|
||||
MediumTopAppBar(
|
||||
modifier = Modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
title = {
|
||||
Text(
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "All Chats"
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {}) {
|
||||
Avatar(matrixUser.avatarData)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = onLogoutClicked
|
||||
) {
|
||||
Icon(Icons.Default.ExitToApp, contentDescription = "logout")
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomItem(
|
||||
modifier: Modifier = Modifier,
|
||||
room: RoomListRoomSummary,
|
||||
onClick: (RoomId) -> Unit
|
||||
) {
|
||||
if (room.isPlaceholder) {
|
||||
return
|
||||
}
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(72.dp)
|
||||
.clickable(
|
||||
onClick = { onClick(room.roomId) },
|
||||
indication = rememberRipple(),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Avatar(room.avatarData)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(12.dp)
|
||||
.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
text = room.name,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = room.lastMessage?.toString().orEmpty(),
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
fontSize = 15.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Column(
|
||||
) {
|
||||
Text(
|
||||
fontSize = 12.sp,
|
||||
text = room.timestamp ?: "",
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
Spacer(modifier.size(4.dp))
|
||||
val unreadIndicatorColor =
|
||||
if (room.hasUnread) MaterialTheme.colorScheme.primary else Color.Transparent
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.clip(CircleShape)
|
||||
.background(unreadIndicatorColor)
|
||||
.align(Alignment.End),
|
||||
)
|
||||
}
|
||||
}
|
||||
private fun PreviewableRoomListContent() {
|
||||
ElementXTheme(darkTheme = false) {
|
||||
RoomListContent(
|
||||
roomSummaries = stubbedRoomSummaries(),
|
||||
matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
|
||||
onRoomClicked = {},
|
||||
onLogoutClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewableRoomListContent() {
|
||||
val roomSummaries = listOf(
|
||||
RoomListRoomSummary(
|
||||
name = "Room",
|
||||
hasUnread = true,
|
||||
timestamp = "14:18",
|
||||
lastMessage = "A message",
|
||||
avatarData = AvatarData("R"),
|
||||
id = "roomId"
|
||||
private fun PreviewableDarkRoomListContent() {
|
||||
ElementXTheme(darkTheme = true) {
|
||||
RoomListContent(
|
||||
roomSummaries = stubbedRoomSummaries(),
|
||||
matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
|
||||
onRoomClicked = {},
|
||||
onLogoutClicked = {}
|
||||
)
|
||||
)
|
||||
RoomListContent(
|
||||
roomSummaries = roomSummaries,
|
||||
matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
|
||||
onRoomClicked = {},
|
||||
onLogoutClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package io.element.android.x.features.roomlist
|
||||
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MavericksViewModel
|
||||
import com.airbnb.mvrx.Success
|
||||
import io.element.android.x.core.data.parallelMap
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.x.features.roomlist.model.MatrixUser
|
||||
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
||||
import io.element.android.x.features.roomlist.model.RoomListViewState
|
||||
|
|
@ -47,7 +47,7 @@ class RoomListViewModel(initialState: RoomListViewState) :
|
|||
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
|
||||
val userDisplayName = client.loadUserDisplayName().getOrNull()
|
||||
val avatarData =
|
||||
loadAvatarData(client, userDisplayName ?: client.userId().value, userAvatarUrl, 32)
|
||||
loadAvatarData(client, userDisplayName ?: client.userId().value, userAvatarUrl, AvatarSize.SMALL)
|
||||
MatrixUser(
|
||||
username = userDisplayName ?: client.userId().value,
|
||||
avatarUrl = userAvatarUrl,
|
||||
|
|
@ -73,10 +73,7 @@ class RoomListViewModel(initialState: RoomListViewState) :
|
|||
): List<RoomListRoomSummary> {
|
||||
return roomSummaries.parallelMap { roomSummary ->
|
||||
when (roomSummary) {
|
||||
is RoomSummary.Empty -> RoomListRoomSummary(
|
||||
id = roomSummary.identifier,
|
||||
isPlaceholder = true
|
||||
)
|
||||
is RoomSummary.Empty -> RoomListRoomSummary.placeholder(roomSummary.identifier)
|
||||
is RoomSummary.Filled -> {
|
||||
val avatarData = loadAvatarData(
|
||||
client,
|
||||
|
|
@ -100,17 +97,17 @@ class RoomListViewModel(initialState: RoomListViewState) :
|
|||
client: MatrixClient,
|
||||
name: String,
|
||||
url: String?,
|
||||
size: Long = 48
|
||||
size: AvatarSize = AvatarSize.MEDIUM
|
||||
): AvatarData {
|
||||
val mediaContent = url?.let {
|
||||
val mediaSource = mediaSourceFromUrl(it)
|
||||
client.loadMediaThumbnailForSource(mediaSource, size, size)
|
||||
client.loadMediaThumbnailForSource(mediaSource, size.value.toLong(), size.value.toLong())
|
||||
}
|
||||
return mediaContent?.fold(
|
||||
{ it },
|
||||
{ null }
|
||||
).let { model ->
|
||||
AvatarData(name.first().toString(), model, size.toInt())
|
||||
AvatarData(name.first().uppercase(), model, size)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
package io.element.android.x.features.roomlist.components
|
||||
|
||||
import Avatar
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.accompanist.placeholder.material.placeholder
|
||||
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
||||
import io.element.android.x.matrix.core.RoomId
|
||||
|
||||
@Composable
|
||||
internal fun RoomItem(
|
||||
modifier: Modifier = Modifier,
|
||||
room: RoomListRoomSummary,
|
||||
onClick: (RoomId) -> Unit
|
||||
) {
|
||||
if (room.isPlaceholder) {
|
||||
return PlaceholderRoomItem(modifier = modifier, room = room)
|
||||
}
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClick = { onClick(room.roomId) },
|
||||
indication = rememberRipple(),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.height(IntrinsicSize.Min),
|
||||
verticalAlignment = CenterVertically
|
||||
) {
|
||||
Avatar(room.avatarData)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp, end = 4.dp, top = 12.dp, bottom = 12.dp)
|
||||
.alignByBaseline()
|
||||
.weight(1f)
|
||||
) {
|
||||
// Name
|
||||
Text(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
text = room.name,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
// Last Message
|
||||
Text(
|
||||
text = room.lastMessage?.toString().orEmpty(),
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
lineHeight = 20.sp,
|
||||
fontSize = 15.sp,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
// Timestamp and Unread
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.alignByBaseline(),
|
||||
) {
|
||||
Text(
|
||||
fontSize = 12.sp,
|
||||
text = room.timestamp ?: "",
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
Spacer(modifier.size(4.dp))
|
||||
val unreadIndicatorColor =
|
||||
if (room.hasUnread) MaterialTheme.colorScheme.primary else Color.Transparent
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.clip(CircleShape)
|
||||
.background(unreadIndicatorColor)
|
||||
.align(Alignment.End),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun PlaceholderRoomItem(
|
||||
modifier: Modifier = Modifier,
|
||||
room: RoomListRoomSummary,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.size(room.avatarData.size.dp)
|
||||
.clip(CircleShape)
|
||||
.placeholder(true),
|
||||
text = ""
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp, end = 4.dp, top = 12.dp, bottom = 12.dp)
|
||||
.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.size(width = 80.dp, height = 12.dp)
|
||||
.placeholder(visible = true),
|
||||
text = "",
|
||||
)
|
||||
Spacer(modifier = Modifier.size(4.dp))
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.size(width = 160.dp, height = 12.dp)
|
||||
.placeholder(visible = true),
|
||||
text = "",
|
||||
)
|
||||
}
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.size(width = 24.dp, height = 12.dp)
|
||||
.placeholder(visible = true),
|
||||
text = "",
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
Spacer(Modifier.size(4.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.Transparent)
|
||||
.align(Alignment.End),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.x.features.roomlist.components
|
||||
|
||||
import Avatar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExitToApp
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import io.element.android.x.core.data.LogCompositions
|
||||
import io.element.android.x.features.roomlist.model.MatrixUser
|
||||
|
||||
@Composable
|
||||
fun RoomListTopBar(
|
||||
matrixUser: MatrixUser?,
|
||||
onLogoutClicked: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior
|
||||
) {
|
||||
LogCompositions(tag = "RoomListScreen", msg = "TopBar")
|
||||
MediumTopAppBar(
|
||||
modifier = Modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
title = {
|
||||
Text(
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "All Chats"
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
if (matrixUser != null) {
|
||||
IconButton(onClick = {}) {
|
||||
Avatar(matrixUser.avatarData)
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = onLogoutClicked
|
||||
) {
|
||||
Icon(Icons.Default.ExitToApp, contentDescription = "logout")
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
}
|
||||
|
|
@ -12,4 +12,18 @@ data class RoomListRoomSummary(
|
|||
val lastMessage: CharSequence? = null,
|
||||
val avatarData: AvatarData = AvatarData(),
|
||||
val isPlaceholder: Boolean = false,
|
||||
)
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun placeholder(id: String): RoomListRoomSummary {
|
||||
return RoomListRoomSummary(
|
||||
id = id,
|
||||
isPlaceholder = true,
|
||||
name = "Short name",
|
||||
timestamp = "hh:mm",
|
||||
lastMessage = "Last message for placeholder",
|
||||
avatarData = AvatarData("S")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package io.element.android.x.features.roomlist.model
|
||||
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
|
||||
internal fun stubbedRoomSummaries(): List<RoomListRoomSummary> {
|
||||
return listOf(
|
||||
RoomListRoomSummary(
|
||||
name = "Room",
|
||||
hasUnread = true,
|
||||
timestamp = "14:18",
|
||||
lastMessage = "A very very very very long message which suites on two lines",
|
||||
avatarData = AvatarData("R"),
|
||||
id = "roomId"
|
||||
),
|
||||
RoomListRoomSummary(
|
||||
name = "Room#2",
|
||||
hasUnread = false,
|
||||
timestamp = "14:16",
|
||||
lastMessage = "A short message",
|
||||
avatarData = AvatarData("Z"),
|
||||
id = "roomId2"
|
||||
),
|
||||
RoomListRoomSummary.placeholder("roomId2")
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue