feat: redesign app with modern Material UI and glassmorphism
Redesigned the application's UI to align with modern Material 3 principles, featuring: - Integration of the 'haze' library for glassmorphism (frosted glass) effects on TopAppBars and NavigationBars. - Updated theme logic to prioritize dynamic color schemes on supported devices. - Refined message layout in ChannelScreen with improved spacing for tail messages. - Redesigned message input field in ChannelScreen as a modern, rounded pill. - Modernized list items and removed dividers in ConversationsScreen for a cleaner, more minimal look. - Polished card designs and spacing in OverviewScreen. - Verified build integrity and fixed several layout and compilation issues. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
parent
4ba9b59127
commit
eecbceabb8
|
|
@ -254,7 +254,9 @@ fun Message(
|
|||
|
||||
Column(modifier.animateContentSize()) {
|
||||
if (message.tail == false) {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
} else {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
}
|
||||
|
||||
if (authorIsBlocked) {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import androidx.compose.runtime.setValue
|
|||
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.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
|
|
@ -260,7 +261,7 @@ fun MessageField(
|
|||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
modifier = modifier.background(Color.Transparent)
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = autocompleteSuggestions.isNotEmpty(),
|
||||
|
|
@ -491,7 +492,9 @@ fun MessageField(
|
|||
}
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer),
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
.clip(MaterialTheme.shapes.extraLarge)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerHigh),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
|
|
|||
|
|
@ -413,8 +413,8 @@ fun OverviewScreenLink(
|
|||
modifier = Modifier
|
||||
.clip(MaterialTheme.shapes.extraLarge)
|
||||
.then(if (clickable) Modifier.clickable(onClick = onClick) else Modifier)
|
||||
.background(backgroundColour)
|
||||
.padding(vertical = 32.dp)
|
||||
.background(backgroundColour.copy(alpha = 0.8f))
|
||||
.padding(vertical = 24.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
CompositionLocalProvider(LocalContentColor provides foregroundColour) {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ import androidx.compose.runtime.snapshotFlow
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
|
@ -147,6 +148,10 @@ import chat.stoat.screens.chat.LocalIsConnected
|
|||
import chat.stoat.sheets.ChannelInfoSheet
|
||||
import chat.stoat.sheets.MessageContextSheet
|
||||
import chat.stoat.sheets.ReactSheet
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
import com.valentinilk.shimmer.ShimmerBounds
|
||||
import com.valentinilk.shimmer.rememberShimmer
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
|
|
@ -203,6 +208,7 @@ fun ChannelScreen(
|
|||
// <editor-fold desc="State and effects">
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val hazeState = remember { HazeState() }
|
||||
val config = LocalConfiguration.current
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
@ -524,7 +530,8 @@ fun ChannelScreen(
|
|||
TopAppBar(
|
||||
modifier = Modifier.clickable {
|
||||
channelInfoSheetShown = true
|
||||
},
|
||||
}.hazeEffect(state = hazeState, style = HazeMaterials.thin(MaterialTheme.colorScheme.surface)),
|
||||
colors = androidx.compose.material3.TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
@ -675,7 +682,7 @@ fun ChannelScreen(
|
|||
.padding(pv)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f),
|
||||
modifier = Modifier.weight(1f).hazeSource(state = hazeState),
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
LazyColumn(
|
||||
|
|
@ -963,7 +970,8 @@ fun ChannelScreen(
|
|||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
.hazeEffect(state = hazeState, style = HazeMaterials.thin(MaterialTheme.colorScheme.surfaceContainer))
|
||||
.background(Color.Transparent)
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
|
|
|
|||
|
|
@ -1,20 +1,25 @@
|
|||
package chat.stoat.screens.main
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.exclude
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBarDefaults
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
|
|
@ -26,6 +31,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
|
|
@ -78,52 +84,95 @@ fun ConversationsScreen(navController: NavController) {
|
|||
}
|
||||
|
||||
if (notesChannel != null) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(stringResource(R.string.channel_notes))
|
||||
},
|
||||
supportingContent = {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
navController.navigate("main/conversation/${notesChannel.id}")
|
||||
}
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(contentAlignment = Alignment.TopEnd) {
|
||||
StoatAPI.userCache[StoatAPI.selfId]?.let {
|
||||
UserAvatar(
|
||||
username = it.username.toString(),
|
||||
avatar = it.avatar,
|
||||
userId = it.id.toString(),
|
||||
shape = RoundedCornerShape(LoadedSettings.avatarRadius)
|
||||
)
|
||||
}
|
||||
Badge {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_keep_24dp),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
stringResource(R.string.channel_notes),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
if (preview.isNotBlank()) {
|
||||
Text(
|
||||
preview,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
},
|
||||
leadingContent = {
|
||||
Box(contentAlignment = Alignment.TopEnd) {
|
||||
StoatAPI.userCache[StoatAPI.selfId]?.let {
|
||||
UserAvatar(
|
||||
username = it.username.toString(),
|
||||
avatar = it.avatar,
|
||||
userId = it.id.toString(),
|
||||
shape = RoundedCornerShape(LoadedSettings.avatarRadius)
|
||||
)
|
||||
}
|
||||
Badge {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_keep_24dp),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.clickable {
|
||||
navController.navigate("main/conversation/${notesChannel.id}")
|
||||
}
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
items(1000) {
|
||||
Text(
|
||||
"Conversation $it", modifier = Modifier
|
||||
items(100) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
navController.navigate("main/conversation/${it}")
|
||||
}
|
||||
.fillMaxWidth())
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(LoadedSettings.avatarRadius.dp * 2)
|
||||
.clip(RoundedCornerShape(LoadedSettings.avatarRadius))
|
||||
.background(MaterialTheme.colorScheme.primaryContainer),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = it.toString().take(1),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
"Conversation $it",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Text(
|
||||
"Last message preview $it",
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,31 @@
|
|||
package chat.stoat.screens.main
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.NavController
|
||||
import chat.stoat.R
|
||||
import chat.stoat.screens.chat.views.OverviewScreen
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
|
||||
enum class MainScreenTab {
|
||||
Communities,
|
||||
|
|
@ -28,10 +36,14 @@ enum class MainScreenTab {
|
|||
@Composable
|
||||
fun MainScreen(navController: NavController) {
|
||||
var currentTab by rememberSaveable { mutableStateOf(MainScreenTab.Communities) }
|
||||
val hazeState = remember { HazeState() }
|
||||
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
NavigationBar {
|
||||
NavigationBar(
|
||||
containerColor = Color.Transparent,
|
||||
modifier = Modifier.hazeEffect(state = hazeState, style = HazeMaterials.thin(MaterialTheme.colorScheme.surfaceContainer))
|
||||
) {
|
||||
NavigationBarItem(
|
||||
selected = currentTab == MainScreenTab.Communities,
|
||||
onClick = { currentTab = MainScreenTab.Communities },
|
||||
|
|
@ -88,22 +100,28 @@ fun MainScreen(navController: NavController) {
|
|||
}
|
||||
},
|
||||
) { pv ->
|
||||
Box(Modifier.padding(pv)) {
|
||||
when (currentTab) {
|
||||
MainScreenTab.Communities -> {}
|
||||
MainScreenTab.Conversations -> {
|
||||
ConversationsScreen(
|
||||
navController
|
||||
)
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.hazeSource(state = hazeState)
|
||||
) {
|
||||
Box(Modifier.padding(pv)) {
|
||||
when (currentTab) {
|
||||
MainScreenTab.Communities -> {}
|
||||
MainScreenTab.Conversations -> {
|
||||
ConversationsScreen(
|
||||
navController
|
||||
)
|
||||
}
|
||||
|
||||
MainScreenTab.Overview -> {
|
||||
OverviewScreen(
|
||||
navController,
|
||||
useDrawer = false,
|
||||
onDrawerClicked = {},
|
||||
includePadding = false
|
||||
)
|
||||
MainScreenTab.Overview -> {
|
||||
OverviewScreen(
|
||||
navController,
|
||||
useDrawer = false,
|
||||
onDrawerClicked = {},
|
||||
includePadding = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package chat.stoat.ui.theme
|
|||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
object Colour {
|
||||
val RevoltUltraPink = Color(0xFFFF005C)
|
||||
val RevoltUltraPink = Color(0xFFFF3377)
|
||||
|
||||
val PrimaryLight = Color(0xFFBD0042)
|
||||
val PrimaryLight = Color(0xFFB90041)
|
||||
val OnPrimaryLight = Color(0xFFFFFFFF)
|
||||
val PrimaryContainerLight = Color(0xFFFFD9DC)
|
||||
val OnPrimaryContainerLight = Color(0xFF910031)
|
||||
|
|
@ -41,10 +41,10 @@ object Colour {
|
|||
val SurfaceContainerHighLight = Color(0xFFE4E8F8)
|
||||
val SurfaceContainerHighestLight = Color(0xFFDEE2F2)
|
||||
|
||||
val PrimaryDark = Color(0xFFFFB2BA)
|
||||
val OnPrimaryDark = Color(0xFF670020)
|
||||
val PrimaryContainerDark = Color(0xFF910031)
|
||||
val OnPrimaryContainerDark = Color(0xFFFFD9DC)
|
||||
val PrimaryDark = Color(0xFFFFB2BB)
|
||||
val OnPrimaryDark = Color(0xFF67001F)
|
||||
val PrimaryContainerDark = Color(0xFF910030)
|
||||
val OnPrimaryContainerDark = Color(0xFFFFD9DD)
|
||||
val SecondaryDark = Color(0xFFFFB3AE)
|
||||
val OnSecondaryDark = Color(0xFF68000C)
|
||||
val SecondaryContainerDark = Color(0xFF930015)
|
||||
|
|
|
|||
|
|
@ -129,11 +129,11 @@ fun getColorScheme(
|
|||
val m3Supported = systemSupportsDynamicColors()
|
||||
|
||||
val colorScheme = when {
|
||||
m3Supported && requestedTheme == Theme.M3Dynamic && systemInDarkTheme -> dynamicDarkColorScheme(
|
||||
m3Supported && (requestedTheme == Theme.M3Dynamic || requestedTheme == Theme.None) && systemInDarkTheme -> dynamicDarkColorScheme(
|
||||
context
|
||||
)
|
||||
|
||||
m3Supported && requestedTheme == Theme.M3Dynamic && !systemInDarkTheme -> dynamicLightColorScheme(
|
||||
m3Supported && (requestedTheme == Theme.M3Dynamic || requestedTheme == Theme.None) && !systemInDarkTheme -> dynamicLightColorScheme(
|
||||
context
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue