Refine UI

This commit is contained in:
ganfra 2022-11-03 12:57:49 +01:00
parent c66b7a699c
commit 4551c9307e
8 changed files with 82 additions and 41 deletions

View file

@ -1,3 +1,5 @@
@file:OptIn(ExperimentalMaterial3Api::class)
package io.element.android.x.features.roomlist package io.element.android.x.features.roomlist
import Avatar import Avatar
@ -19,6 +21,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color 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.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -63,40 +66,48 @@ fun RoomListContent(
onRoomClicked: (RoomId) -> Unit, onRoomClicked: (RoomId) -> Unit,
onLogoutClicked: () -> Unit, onLogoutClicked: () -> Unit,
) { ) {
val appBarState = rememberTopAppBarState()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState)
LogCompositions(tag = "RoomListScreen", msg = "Content") LogCompositions(tag = "RoomListScreen", msg = "Content")
Surface(color = MaterialTheme.colorScheme.background) { Scaffold(
Column( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
modifier = Modifier.fillMaxSize() topBar = {
) { RoomListTopBar(matrixUser, onLogoutClicked, scrollBehavior)
RoomListTopBar( },
matrixUser = matrixUser, content = { padding ->
onLogoutClicked = onLogoutClicked LazyColumn(modifier = Modifier.padding(padding)) {
) items(roomSummaries) { room ->
LazyColumn {
items(roomSummaries, key = { it.id }) { room ->
RoomItem(room = room) { RoomItem(room = room) {
onRoomClicked(it) onRoomClicked(it)
} }
} }
} }
} }
} )
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun RoomListTopBar(matrixUser: MatrixUser?, onLogoutClicked: () -> Unit) { fun RoomListTopBar(
matrixUser: MatrixUser?,
onLogoutClicked: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior
) {
LogCompositions(tag = "RoomListScreen", msg = "TopBar") LogCompositions(tag = "RoomListScreen", msg = "TopBar")
if (matrixUser == null) return if (matrixUser == null) return
TopAppBar( MediumTopAppBar(
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
title = { title = {
Row( Text(
modifier = Modifier.fillMaxWidth(), fontWeight = FontWeight.Bold,
verticalAlignment = Alignment.CenterVertically text = "All Chats"
) { )
},
navigationIcon = {
IconButton(onClick = {}) {
Avatar(matrixUser.avatarData) Avatar(matrixUser.avatarData)
Spacer(modifier = Modifier.width(8.dp))
Text("${matrixUser.username}")
} }
}, },
actions = { actions = {
@ -105,7 +116,8 @@ fun RoomListTopBar(matrixUser: MatrixUser?, onLogoutClicked: () -> Unit) {
) { ) {
Icon(Icons.Default.ExitToApp, contentDescription = "logout") Icon(Icons.Default.ExitToApp, contentDescription = "logout")
} }
} },
scrollBehavior = scrollBehavior,
) )
} }
@ -121,6 +133,7 @@ private fun RoomItem(
Column( Column(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.height(72.dp)
.clickable( .clickable(
onClick = { onClick(room.roomId) }, onClick = { onClick(room.roomId) },
indication = rememberRipple(), indication = rememberRipple(),
@ -141,7 +154,7 @@ private fun RoomItem(
) { ) {
Text( Text(
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.SemiBold,
text = room.name, text = room.name,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@ -149,6 +162,7 @@ private fun RoomItem(
Text( Text(
text = room.lastMessage?.toString().orEmpty(), text = room.lastMessage?.toString().orEmpty(),
color = MaterialTheme.colorScheme.secondary, color = MaterialTheme.colorScheme.secondary,
fontSize = 15.sp,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )

View file

@ -1,5 +1,6 @@
package io.element.android.x.features.roomlist package io.element.android.x.features.roomlist
import androidx.compose.ui.unit.dp
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModel import com.airbnb.mvrx.MavericksViewModel
@ -46,7 +47,7 @@ class RoomListViewModel(initialState: RoomListViewState) :
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull() val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
val userDisplayName = client.loadUserDisplayName().getOrNull() val userDisplayName = client.loadUserDisplayName().getOrNull()
val avatarData = val avatarData =
loadAvatarData(client, userDisplayName ?: client.userId().value, userAvatarUrl) loadAvatarData(client, userDisplayName ?: client.userId().value, userAvatarUrl, 32)
MatrixUser( MatrixUser(
username = userDisplayName ?: client.userId().value, username = userDisplayName ?: client.userId().value,
avatarUrl = userAvatarUrl, avatarUrl = userAvatarUrl,

View file

@ -3,12 +3,12 @@ package io.element.android.x.features.roomlist.model
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import io.element.android.x.features.roomlist.model.MatrixUser import io.element.android.x.matrix.core.RoomId
import io.element.android.x.matrix.room.RoomSummary
data class RoomListViewState( data class RoomListViewState(
val user: Async<MatrixUser> = Uninitialized, val user: Async<MatrixUser> = Uninitialized,
val rooms: Async<List<RoomListRoomSummary>> = Uninitialized, val rooms: Async<List<RoomListRoomSummary>> = Uninitialized,
val canLoadMore: Boolean = false, val canLoadMore: Boolean = false,
val logoutAction: Async<Unit> = Uninitialized, val logoutAction: Async<Unit> = Uninitialized,
val roomsById: Map<RoomId, RoomListRoomSummary> = emptyMap()
) : MavericksState ) : MavericksState

View file

@ -56,6 +56,7 @@ coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve
accompanist_animation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanist" } accompanist_animation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanist" }
accompanist_permission = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" } accompanist_permission = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
accompanist_material = { module = "com.google.accompanist:accompanist-navigation-material", version.ref = "accompanist" } accompanist_material = { module = "com.google.accompanist:accompanist-navigation-material", version.ref = "accompanist" }
accompanist_systemui = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
# Test # Test
test_junit = { module = "junit:junit", version.ref = "test_junit" } test_junit = { module = "junit:junit", version.ref = "test_junit" }

View file

@ -8,5 +8,6 @@ android {
dependencies { dependencies {
// Should not be there, but this is a POC // Should not be there, but this is a POC
implementation("io.coil-kt:coil-compose:2.2.1") implementation("io.coil-kt:coil-compose:2.2.1")
implementation(libs.accompanist.systemui)
} }
} }

View file

@ -11,6 +11,7 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import com.google.accompanist.systemuicontroller.rememberSystemUiController
private val DarkColorScheme = darkColorScheme( private val DarkColorScheme = darkColorScheme(
primary = Color.White, primary = Color.White,
@ -58,13 +59,11 @@ fun ElementXTheme(
darkTheme -> DarkColorScheme darkTheme -> DarkColorScheme
else -> LightColorScheme else -> LightColorScheme
} }
val view = LocalView.current val systemUiController = rememberSystemUiController()
if (!view.isInEditMode) { SideEffect {
SideEffect { systemUiController.setStatusBarColor(
val window = (view.context as Activity).window color = colorScheme.background
window.statusBarColor = colorScheme.primary.toArgb() )
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
} }
MaterialTheme( MaterialTheme(

View file

@ -8,13 +8,22 @@ import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with // Set of Material typography styles to start with
val Typography = Typography( val Typography = Typography(
bodyLarge = TextStyle( bodyLarge = TextStyle(
fontFamily = FontFamily.Default, fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 16.sp, fontSize = 16.sp,
lineHeight = 24.sp, lineHeight = 24.sp,
letterSpacing = 0.5.sp letterSpacing = 0.5.sp
),
headlineSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 32.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
) )
/* Other default text styles to override /* Other default text styles to override
titleLarge = TextStyle( titleLarge = TextStyle(
fontFamily = FontFamily.Default, fontFamily = FontFamily.Default,

View file

@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage import coil.compose.AsyncImage
@ -18,28 +19,43 @@ import io.element.android.x.designsystem.AvatarGradientStart
import io.element.android.x.designsystem.components.avatar.AvatarData import io.element.android.x.designsystem.components.avatar.AvatarData
@Composable @Composable
fun Avatar(avatarData: AvatarData) { fun Avatar(avatarData: AvatarData, modifier: Modifier = Modifier) {
if (avatarData.model == null) { if (avatarData.model == null) {
InitialsAvatar( InitialsAvatar(
modifier = Modifier modifier = modifier
.size(avatarData.size.dp) .size(avatarData.size.dp)
.clip(CircleShape), .clip(CircleShape),
initials = avatarData.initials initials = avatarData.initials
) )
} else { } else {
AsyncImage( ImageAvatar(
model = avatarData.model, modifier = modifier
onError = {
Log.e("TAG", "Error $it\n${it.result}", it.result.throwable)
},
contentDescription = null,
modifier = Modifier
.size(avatarData.size.dp) .size(avatarData.size.dp)
.clip(CircleShape) .clip(CircleShape),
avatarData = avatarData
) )
} }
} }
@Composable
private fun ImageAvatar(
avatarData: AvatarData,
modifier: Modifier = Modifier,
) {
AsyncImage(
model = avatarData.model,
onError = {
Log.e("TAG", "Error $it\n${it.result}", it.result.throwable)
},
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = modifier
.size(avatarData.size.dp)
.clip(CircleShape)
)
}
@Composable @Composable
private fun InitialsAvatar( private fun InitialsAvatar(
initials: String, initials: String,