mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 15:27:24 +01:00
Improves padding of the ListItem interfaces
This commit is contained in:
@@ -39,6 +39,7 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.layouts.listItem.SlimListItem
|
||||||
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
|
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
|
||||||
import com.vitorpamplona.amethyst.ui.painterRes
|
import com.vitorpamplona.amethyst.ui.painterRes
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.NewItemsBubble
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.NewItemsBubble
|
||||||
@@ -47,57 +48,86 @@ import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Height4dpModifier
|
import com.vitorpamplona.amethyst.ui.theme.Height4dpModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun ChannelNamePreview() {
|
fun ChannelNamePreview() {
|
||||||
Column {
|
ThemeComparisonColumn {
|
||||||
ChatHeaderLayout(
|
Column {
|
||||||
channelPicture = {
|
ChatHeaderLayout(
|
||||||
Image(
|
channelPicture = {
|
||||||
painter = painterRes(R.drawable.github, 1),
|
Image(
|
||||||
contentDescription = stringRes(id = R.string.profile_banner),
|
painter = painterRes(R.drawable.github, 1),
|
||||||
contentScale = ContentScale.FillWidth,
|
contentDescription = stringRes(id = R.string.profile_banner),
|
||||||
)
|
contentScale = ContentScale.FillWidth,
|
||||||
},
|
)
|
||||||
firstRow = {
|
},
|
||||||
Text("This is my author", Modifier.weight(1f))
|
firstRow = {
|
||||||
TimeAgo(TimeUtils.now())
|
|
||||||
},
|
|
||||||
secondRow = {
|
|
||||||
Text("This is a message from this person", Modifier.weight(1f))
|
|
||||||
NewItemsBubble()
|
|
||||||
},
|
|
||||||
onClick = {},
|
|
||||||
)
|
|
||||||
|
|
||||||
HorizontalDivider(thickness = DividerThickness)
|
|
||||||
|
|
||||||
ListItem(
|
|
||||||
headlineContent = {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Text("This is my author", Modifier.weight(1f))
|
Text("This is my author", Modifier.weight(1f))
|
||||||
TimeAgo(TimeUtils.now())
|
TimeAgo(TimeUtils.now())
|
||||||
}
|
},
|
||||||
},
|
secondRow = {
|
||||||
supportingContent = {
|
|
||||||
Row {
|
|
||||||
Text("This is a message from this person", Modifier.weight(1f))
|
Text("This is a message from this person", Modifier.weight(1f))
|
||||||
|
Spacer(modifier = Height4dpModifier)
|
||||||
NewItemsBubble()
|
NewItemsBubble()
|
||||||
}
|
},
|
||||||
},
|
onClick = {},
|
||||||
leadingContent = {
|
)
|
||||||
Image(
|
|
||||||
painter = painterRes(R.drawable.github, 2),
|
|
||||||
contentDescription = stringRes(id = R.string.profile_banner),
|
|
||||||
contentScale = ContentScale.FillWidth,
|
|
||||||
modifier = Size55Modifier,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
HorizontalDivider(thickness = DividerThickness)
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("This is my author", Modifier.weight(1f))
|
||||||
|
TimeAgo(TimeUtils.now())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Row {
|
||||||
|
Text("This is a message from this person", Modifier.weight(1f))
|
||||||
|
NewItemsBubble()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Image(
|
||||||
|
painter = painterRes(R.drawable.github, 2),
|
||||||
|
contentDescription = stringRes(id = R.string.profile_banner),
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
modifier = Size55Modifier,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
|
||||||
|
SlimListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("This is my author", Modifier.weight(1f))
|
||||||
|
TimeAgo(TimeUtils.now())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("This is a message from this person", Modifier.weight(1f))
|
||||||
|
NewItemsBubble()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Image(
|
||||||
|
painter = painterRes(R.drawable.github, 2),
|
||||||
|
contentDescription = stringRes(id = R.string.profile_banner),
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
modifier = Size55Modifier,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,703 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.layouts.listItem
|
||||||
|
|
||||||
|
import android.view.Surface
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
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.padding
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.ListItemColors
|
||||||
|
import androidx.compose.material3.ListItemDefaults
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
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.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.layout.FirstBaseline
|
||||||
|
import androidx.compose.ui.layout.IntrinsicMeasurable
|
||||||
|
import androidx.compose.ui.layout.IntrinsicMeasureScope
|
||||||
|
import androidx.compose.ui.layout.LastBaseline
|
||||||
|
import androidx.compose.ui.layout.Layout
|
||||||
|
import androidx.compose.ui.layout.Measurable
|
||||||
|
import androidx.compose.ui.layout.MeasureResult
|
||||||
|
import androidx.compose.ui.layout.MeasureScope
|
||||||
|
import androidx.compose.ui.layout.MultiContentMeasurePolicy
|
||||||
|
import androidx.compose.ui.layout.Placeable
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Constraints
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.offset
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.layouts.ChatHeaderLayout
|
||||||
|
import com.vitorpamplona.amethyst.ui.layouts.listItem.ListTokens.ListItemContainerElevation
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
|
||||||
|
import com.vitorpamplona.amethyst.ui.painterRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.NewItemsBubble
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Height4dpModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a copy of Material3's ListItemLayout.kt file, with the only change being the padding change from 16.dp to 10.dp
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
fun ChannelNamePreview() {
|
||||||
|
ThemeComparisonColumn {
|
||||||
|
Column {
|
||||||
|
ChatHeaderLayout(
|
||||||
|
channelPicture = {
|
||||||
|
Image(
|
||||||
|
painter = painterRes(R.drawable.github, 1),
|
||||||
|
contentDescription = stringRes(id = R.string.profile_banner),
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
firstRow = {
|
||||||
|
Text("This is my author", Modifier.weight(1f))
|
||||||
|
TimeAgo(TimeUtils.now())
|
||||||
|
},
|
||||||
|
secondRow = {
|
||||||
|
Text("This is a message from this person", Modifier.weight(1f))
|
||||||
|
NewItemsBubble()
|
||||||
|
},
|
||||||
|
onClick = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
|
||||||
|
SlimListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("This is my author", Modifier.weight(1f))
|
||||||
|
TimeAgo(TimeUtils.now())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("This is a message from this person", Modifier.weight(1f))
|
||||||
|
Spacer(modifier = Height4dpModifier)
|
||||||
|
NewItemsBubble()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Image(
|
||||||
|
painter = painterRes(R.drawable.github, 2),
|
||||||
|
contentDescription = stringRes(id = R.string.profile_banner),
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
modifier = Size55Modifier,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("This is my author", Modifier.weight(1f))
|
||||||
|
TimeAgo(TimeUtils.now())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("This is a message from this person", Modifier.weight(1f))
|
||||||
|
Spacer(modifier = Height4dpModifier)
|
||||||
|
NewItemsBubble()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Image(
|
||||||
|
painter = painterRes(R.drawable.github, 2),
|
||||||
|
contentDescription = stringRes(id = R.string.profile_banner),
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
modifier = Size55Modifier,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SlimListItem(
|
||||||
|
headlineContent: @Composable () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
overlineContent: @Composable (() -> Unit)? = null,
|
||||||
|
supportingContent: @Composable (() -> Unit)? = null,
|
||||||
|
leadingContent: @Composable (() -> Unit)? = null,
|
||||||
|
trailingContent: @Composable (() -> Unit)? = null,
|
||||||
|
colors: ListItemColors = ListItemDefaults.colors(),
|
||||||
|
tonalElevation: Dp = ListItemContainerElevation,
|
||||||
|
shadowElevation: Dp = ListItemContainerElevation,
|
||||||
|
) {
|
||||||
|
val decoratedHeadlineContent: @Composable () -> Unit = {
|
||||||
|
ProvideTextStyleFromToken(
|
||||||
|
colors.headlineColor,
|
||||||
|
MaterialTheme.typography.bodyLarge,
|
||||||
|
headlineContent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val decoratedSupportingContent: @Composable (() -> Unit)? =
|
||||||
|
supportingContent?.let {
|
||||||
|
{
|
||||||
|
Box(SupportingContentTopPadding) {
|
||||||
|
ProvideTextStyleFromToken(
|
||||||
|
colors.supportingTextColor,
|
||||||
|
MaterialTheme.typography.bodyMedium,
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val decoratedOverlineContent: @Composable (() -> Unit)? =
|
||||||
|
overlineContent?.let {
|
||||||
|
{
|
||||||
|
ProvideTextStyleFromToken(
|
||||||
|
colors.overlineColor,
|
||||||
|
MaterialTheme.typography.labelSmall,
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val decoratedLeadingContent: @Composable (() -> Unit)? =
|
||||||
|
leadingContent?.let {
|
||||||
|
{
|
||||||
|
Box(LeadingContentEndPadding) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalContentColor provides colors.leadingIconColor,
|
||||||
|
content = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val decoratedTrailingContent: @Composable (() -> Unit)? =
|
||||||
|
trailingContent?.let {
|
||||||
|
{
|
||||||
|
Box(TrailingContentStartPadding) {
|
||||||
|
ProvideTextStyleFromToken(
|
||||||
|
colors.trailingIconColor,
|
||||||
|
MaterialTheme.typography.labelSmall,
|
||||||
|
content = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.semantics(mergeDescendants = true) {}.then(modifier),
|
||||||
|
shape = ListItemDefaults.shape,
|
||||||
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||||
|
tonalElevation = tonalElevation,
|
||||||
|
shadowElevation = shadowElevation,
|
||||||
|
) {
|
||||||
|
SlimListItemLayout(
|
||||||
|
headline = decoratedHeadlineContent,
|
||||||
|
overline = decoratedOverlineContent,
|
||||||
|
supporting = decoratedSupportingContent,
|
||||||
|
leading = decoratedLeadingContent,
|
||||||
|
trailing = decoratedTrailingContent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SlimListItemLayout(
|
||||||
|
leading: @Composable (() -> Unit)?,
|
||||||
|
trailing: @Composable (() -> Unit)?,
|
||||||
|
headline: @Composable () -> Unit,
|
||||||
|
overline: @Composable (() -> Unit)?,
|
||||||
|
supporting: @Composable (() -> Unit)?,
|
||||||
|
) {
|
||||||
|
val measurePolicy = remember { ListItemMeasurePolicy() }
|
||||||
|
Layout(
|
||||||
|
contents =
|
||||||
|
listOf(headline, overline ?: {}, supporting ?: {}, leading ?: {}, trailing ?: {}),
|
||||||
|
measurePolicy = measurePolicy,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ListItemMeasurePolicy : MultiContentMeasurePolicy {
|
||||||
|
override fun MeasureScope.measure(
|
||||||
|
measurables: List<List<Measurable>>,
|
||||||
|
constraints: Constraints,
|
||||||
|
): MeasureResult {
|
||||||
|
val (
|
||||||
|
headlineMeasurable,
|
||||||
|
overlineMeasurable,
|
||||||
|
supportingMeasurable,
|
||||||
|
leadingMeasurable,
|
||||||
|
trailingMeasurable,
|
||||||
|
) =
|
||||||
|
measurables
|
||||||
|
var currentTotalWidth = 0
|
||||||
|
|
||||||
|
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
|
||||||
|
val startPadding = ListItemStartPadding
|
||||||
|
val endPadding = ListItemEndPadding
|
||||||
|
val horizontalPadding = (startPadding + endPadding).roundToPx()
|
||||||
|
|
||||||
|
// ListItem layout has a cycle in its dependencies which we use
|
||||||
|
// intrinsic measurements to break:
|
||||||
|
// 1. Intrinsic leading/trailing width
|
||||||
|
// 2. Intrinsic supporting height
|
||||||
|
// 3. Intrinsic vertical padding
|
||||||
|
// 4. Actual leading/trailing measurement
|
||||||
|
// 5. Actual supporting measurement
|
||||||
|
// 6. Actual vertical padding
|
||||||
|
val intrinsicLeadingWidth =
|
||||||
|
leadingMeasurable.firstOrNull()?.minIntrinsicWidth(constraints.maxHeight) ?: 0
|
||||||
|
val intrinsicTrailingWidth =
|
||||||
|
trailingMeasurable.firstOrNull()?.minIntrinsicWidth(constraints.maxHeight) ?: 0
|
||||||
|
val intrinsicSupportingWidthConstraint =
|
||||||
|
looseConstraints.maxWidth.subtractConstraintSafely(
|
||||||
|
intrinsicLeadingWidth + intrinsicTrailingWidth + horizontalPadding,
|
||||||
|
)
|
||||||
|
val intrinsicSupportingHeight =
|
||||||
|
supportingMeasurable
|
||||||
|
.firstOrNull()
|
||||||
|
?.minIntrinsicHeight(intrinsicSupportingWidthConstraint) ?: 0
|
||||||
|
val intrinsicIsSupportingMultiline =
|
||||||
|
isSupportingMultilineHeuristic(intrinsicSupportingHeight)
|
||||||
|
val intrinsicListItemType =
|
||||||
|
ListItemType(
|
||||||
|
hasOverline = overlineMeasurable.firstOrNull() != null,
|
||||||
|
hasSupporting = supportingMeasurable.firstOrNull() != null,
|
||||||
|
isSupportingMultiline = intrinsicIsSupportingMultiline,
|
||||||
|
)
|
||||||
|
val intrinsicVerticalPadding = (verticalPadding(intrinsicListItemType) * 2).roundToPx()
|
||||||
|
|
||||||
|
val paddedLooseConstraints =
|
||||||
|
looseConstraints.offset(
|
||||||
|
horizontal = -horizontalPadding,
|
||||||
|
vertical = -intrinsicVerticalPadding,
|
||||||
|
)
|
||||||
|
|
||||||
|
val leadingPlaceable = leadingMeasurable.firstOrNull()?.measure(paddedLooseConstraints)
|
||||||
|
currentTotalWidth += leadingPlaceable.widthOrZero
|
||||||
|
|
||||||
|
val trailingPlaceable =
|
||||||
|
trailingMeasurable
|
||||||
|
.firstOrNull()
|
||||||
|
?.measure(paddedLooseConstraints.offset(horizontal = -currentTotalWidth))
|
||||||
|
currentTotalWidth += trailingPlaceable.widthOrZero
|
||||||
|
|
||||||
|
var currentTotalHeight = 0
|
||||||
|
|
||||||
|
val headlinePlaceable =
|
||||||
|
headlineMeasurable
|
||||||
|
.firstOrNull()
|
||||||
|
?.measure(paddedLooseConstraints.offset(horizontal = -currentTotalWidth))
|
||||||
|
currentTotalHeight += headlinePlaceable.heightOrZero
|
||||||
|
|
||||||
|
val supportingPlaceable =
|
||||||
|
supportingMeasurable
|
||||||
|
.firstOrNull()
|
||||||
|
?.measure(
|
||||||
|
paddedLooseConstraints.offset(
|
||||||
|
horizontal = -currentTotalWidth,
|
||||||
|
vertical = -currentTotalHeight,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
currentTotalHeight += supportingPlaceable.heightOrZero
|
||||||
|
val isSupportingMultiline =
|
||||||
|
supportingPlaceable != null &&
|
||||||
|
(supportingPlaceable[FirstBaseline] != supportingPlaceable[LastBaseline])
|
||||||
|
|
||||||
|
val overlinePlaceable =
|
||||||
|
overlineMeasurable
|
||||||
|
.firstOrNull()
|
||||||
|
?.measure(
|
||||||
|
paddedLooseConstraints.offset(
|
||||||
|
horizontal = -currentTotalWidth,
|
||||||
|
vertical = -currentTotalHeight,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val listItemType =
|
||||||
|
ListItemType(
|
||||||
|
hasOverline = overlinePlaceable != null,
|
||||||
|
hasSupporting = supportingPlaceable != null,
|
||||||
|
isSupportingMultiline = isSupportingMultiline,
|
||||||
|
)
|
||||||
|
val topPadding = verticalPadding(listItemType)
|
||||||
|
val verticalPadding = topPadding * 2
|
||||||
|
|
||||||
|
val width =
|
||||||
|
calculateWidth(
|
||||||
|
leadingWidth = leadingPlaceable.widthOrZero,
|
||||||
|
trailingWidth = trailingPlaceable.widthOrZero,
|
||||||
|
headlineWidth = headlinePlaceable.widthOrZero,
|
||||||
|
overlineWidth = overlinePlaceable.widthOrZero,
|
||||||
|
supportingWidth = supportingPlaceable.widthOrZero,
|
||||||
|
horizontalPadding = horizontalPadding,
|
||||||
|
constraints = constraints,
|
||||||
|
)
|
||||||
|
val height =
|
||||||
|
calculateHeight(
|
||||||
|
leadingHeight = leadingPlaceable.heightOrZero,
|
||||||
|
trailingHeight = trailingPlaceable.heightOrZero,
|
||||||
|
headlineHeight = headlinePlaceable.heightOrZero,
|
||||||
|
overlineHeight = overlinePlaceable.heightOrZero,
|
||||||
|
supportingHeight = supportingPlaceable.heightOrZero,
|
||||||
|
listItemType = listItemType,
|
||||||
|
verticalPadding = verticalPadding.roundToPx(),
|
||||||
|
constraints = constraints,
|
||||||
|
)
|
||||||
|
|
||||||
|
return place(
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
leadingPlaceable = leadingPlaceable,
|
||||||
|
trailingPlaceable = trailingPlaceable,
|
||||||
|
headlinePlaceable = headlinePlaceable,
|
||||||
|
overlinePlaceable = overlinePlaceable,
|
||||||
|
supportingPlaceable = supportingPlaceable,
|
||||||
|
isThreeLine = listItemType == ListItemType.ThreeLine,
|
||||||
|
startPadding = startPadding.roundToPx(),
|
||||||
|
endPadding = endPadding.roundToPx(),
|
||||||
|
topPadding = topPadding.roundToPx(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun IntrinsicMeasureScope.maxIntrinsicHeight(
|
||||||
|
measurables: List<List<IntrinsicMeasurable>>,
|
||||||
|
width: Int,
|
||||||
|
): Int = calculateIntrinsicHeight(measurables, width, IntrinsicMeasurable::maxIntrinsicHeight)
|
||||||
|
|
||||||
|
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
|
||||||
|
measurables: List<List<IntrinsicMeasurable>>,
|
||||||
|
height: Int,
|
||||||
|
): Int = calculateIntrinsicWidth(measurables, height, IntrinsicMeasurable::maxIntrinsicWidth)
|
||||||
|
|
||||||
|
override fun IntrinsicMeasureScope.minIntrinsicHeight(
|
||||||
|
measurables: List<List<IntrinsicMeasurable>>,
|
||||||
|
width: Int,
|
||||||
|
): Int = calculateIntrinsicHeight(measurables, width, IntrinsicMeasurable::minIntrinsicHeight)
|
||||||
|
|
||||||
|
override fun IntrinsicMeasureScope.minIntrinsicWidth(
|
||||||
|
measurables: List<List<IntrinsicMeasurable>>,
|
||||||
|
height: Int,
|
||||||
|
): Int = calculateIntrinsicWidth(measurables, height, IntrinsicMeasurable::minIntrinsicWidth)
|
||||||
|
|
||||||
|
private fun IntrinsicMeasureScope.calculateIntrinsicWidth(
|
||||||
|
measurables: List<List<IntrinsicMeasurable>>,
|
||||||
|
height: Int,
|
||||||
|
intrinsicMeasure: IntrinsicMeasurable.(height: Int) -> Int,
|
||||||
|
): Int {
|
||||||
|
val (
|
||||||
|
headlineMeasurable,
|
||||||
|
overlineMeasurable,
|
||||||
|
supportingMeasurable,
|
||||||
|
leadingMeasurable,
|
||||||
|
trailingMeasurable,
|
||||||
|
) =
|
||||||
|
measurables
|
||||||
|
return calculateWidth(
|
||||||
|
leadingWidth = leadingMeasurable.firstOrNull()?.intrinsicMeasure(height) ?: 0,
|
||||||
|
trailingWidth = trailingMeasurable.firstOrNull()?.intrinsicMeasure(height) ?: 0,
|
||||||
|
headlineWidth = headlineMeasurable.firstOrNull()?.intrinsicMeasure(height) ?: 0,
|
||||||
|
overlineWidth = overlineMeasurable.firstOrNull()?.intrinsicMeasure(height) ?: 0,
|
||||||
|
supportingWidth = supportingMeasurable.firstOrNull()?.intrinsicMeasure(height) ?: 0,
|
||||||
|
horizontalPadding = (ListItemStartPadding + ListItemEndPadding).roundToPx(),
|
||||||
|
constraints = Constraints(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IntrinsicMeasureScope.calculateIntrinsicHeight(
|
||||||
|
measurables: List<List<IntrinsicMeasurable>>,
|
||||||
|
width: Int,
|
||||||
|
intrinsicMeasure: IntrinsicMeasurable.(width: Int) -> Int,
|
||||||
|
): Int {
|
||||||
|
val (
|
||||||
|
headlineMeasurable,
|
||||||
|
overlineMeasurable,
|
||||||
|
supportingMeasurable,
|
||||||
|
leadingMeasurable,
|
||||||
|
trailingMeasurable,
|
||||||
|
) =
|
||||||
|
measurables
|
||||||
|
|
||||||
|
var remainingWidth =
|
||||||
|
width.subtractConstraintSafely((ListItemStartPadding + ListItemEndPadding).roundToPx())
|
||||||
|
val leadingHeight =
|
||||||
|
leadingMeasurable.firstOrNull()?.let {
|
||||||
|
val height = it.intrinsicMeasure(remainingWidth)
|
||||||
|
remainingWidth =
|
||||||
|
remainingWidth.subtractConstraintSafely(
|
||||||
|
it.maxIntrinsicWidth(Constraints.Infinity),
|
||||||
|
)
|
||||||
|
height
|
||||||
|
} ?: 0
|
||||||
|
val trailingHeight =
|
||||||
|
trailingMeasurable.firstOrNull()?.let {
|
||||||
|
val height = it.intrinsicMeasure(remainingWidth)
|
||||||
|
remainingWidth =
|
||||||
|
remainingWidth.subtractConstraintSafely(
|
||||||
|
it.maxIntrinsicWidth(Constraints.Infinity),
|
||||||
|
)
|
||||||
|
height
|
||||||
|
} ?: 0
|
||||||
|
val overlineHeight = overlineMeasurable.firstOrNull()?.intrinsicMeasure(remainingWidth) ?: 0
|
||||||
|
val headlineHeight = headlineMeasurable.firstOrNull()?.intrinsicMeasure(remainingWidth) ?: 0
|
||||||
|
val supportingHeight =
|
||||||
|
supportingMeasurable.firstOrNull()?.intrinsicMeasure(remainingWidth) ?: 0
|
||||||
|
val isSupportingMultiline = isSupportingMultilineHeuristic(supportingHeight)
|
||||||
|
val listItemType =
|
||||||
|
ListItemType(
|
||||||
|
hasOverline = overlineHeight > 0,
|
||||||
|
hasSupporting = supportingHeight > 0,
|
||||||
|
isSupportingMultiline = isSupportingMultiline,
|
||||||
|
)
|
||||||
|
|
||||||
|
return calculateHeight(
|
||||||
|
leadingHeight = leadingHeight,
|
||||||
|
trailingHeight = trailingHeight,
|
||||||
|
headlineHeight = headlineHeight,
|
||||||
|
overlineHeight = overlineHeight,
|
||||||
|
supportingHeight = supportingHeight,
|
||||||
|
listItemType = listItemType,
|
||||||
|
verticalPadding = (verticalPadding(listItemType) * 2).roundToPx(),
|
||||||
|
constraints = Constraints(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IntrinsicMeasureScope.calculateWidth(
|
||||||
|
leadingWidth: Int,
|
||||||
|
trailingWidth: Int,
|
||||||
|
headlineWidth: Int,
|
||||||
|
overlineWidth: Int,
|
||||||
|
supportingWidth: Int,
|
||||||
|
horizontalPadding: Int,
|
||||||
|
constraints: Constraints,
|
||||||
|
): Int {
|
||||||
|
if (constraints.hasBoundedWidth) {
|
||||||
|
return constraints.maxWidth
|
||||||
|
}
|
||||||
|
// Fallback behavior if width constraints are infinite
|
||||||
|
val mainContentWidth = maxOf(headlineWidth, overlineWidth, supportingWidth)
|
||||||
|
return horizontalPadding + leadingWidth + mainContentWidth + trailingWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IntrinsicMeasureScope.calculateHeight(
|
||||||
|
leadingHeight: Int,
|
||||||
|
trailingHeight: Int,
|
||||||
|
headlineHeight: Int,
|
||||||
|
overlineHeight: Int,
|
||||||
|
supportingHeight: Int,
|
||||||
|
listItemType: ListItemType,
|
||||||
|
verticalPadding: Int,
|
||||||
|
constraints: Constraints,
|
||||||
|
): Int {
|
||||||
|
val defaultMinHeight =
|
||||||
|
when (listItemType) {
|
||||||
|
ListItemType.OneLine -> ListTokens.ListItemOneLineContainerHeight
|
||||||
|
ListItemType.TwoLine -> ListTokens.ListItemTwoLineContainerHeight
|
||||||
|
else -> ListTokens.ListItemThreeLineContainerHeight
|
||||||
|
}
|
||||||
|
val minHeight = max(constraints.minHeight, defaultMinHeight.roundToPx())
|
||||||
|
|
||||||
|
val mainContentHeight = headlineHeight + overlineHeight + supportingHeight
|
||||||
|
|
||||||
|
return max(minHeight, verticalPadding + maxOf(leadingHeight, mainContentHeight, trailingHeight))
|
||||||
|
.coerceAtMost(constraints.maxHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MeasureScope.place(
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
|
leadingPlaceable: Placeable?,
|
||||||
|
trailingPlaceable: Placeable?,
|
||||||
|
headlinePlaceable: Placeable?,
|
||||||
|
overlinePlaceable: Placeable?,
|
||||||
|
supportingPlaceable: Placeable?,
|
||||||
|
isThreeLine: Boolean,
|
||||||
|
startPadding: Int,
|
||||||
|
endPadding: Int,
|
||||||
|
topPadding: Int,
|
||||||
|
): MeasureResult =
|
||||||
|
layout(width, height) {
|
||||||
|
leadingPlaceable?.let {
|
||||||
|
it.placeRelative(
|
||||||
|
x = startPadding,
|
||||||
|
y = if (isThreeLine) topPadding else CenterVertically.align(it.height, height),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val mainContentX = startPadding + leadingPlaceable.widthOrZero
|
||||||
|
val mainContentY =
|
||||||
|
if (isThreeLine) {
|
||||||
|
topPadding
|
||||||
|
} else {
|
||||||
|
val totalHeight =
|
||||||
|
headlinePlaceable.heightOrZero +
|
||||||
|
overlinePlaceable.heightOrZero +
|
||||||
|
supportingPlaceable.heightOrZero
|
||||||
|
CenterVertically.align(totalHeight, height)
|
||||||
|
}
|
||||||
|
var currentY = mainContentY
|
||||||
|
|
||||||
|
overlinePlaceable?.placeRelative(mainContentX, currentY)
|
||||||
|
currentY += overlinePlaceable.heightOrZero
|
||||||
|
|
||||||
|
headlinePlaceable?.placeRelative(mainContentX, currentY)
|
||||||
|
currentY += headlinePlaceable.heightOrZero
|
||||||
|
|
||||||
|
supportingPlaceable?.placeRelative(mainContentX, currentY)
|
||||||
|
|
||||||
|
trailingPlaceable?.let {
|
||||||
|
it.placeRelative(
|
||||||
|
x = width - endPadding - it.width,
|
||||||
|
y = if (isThreeLine) topPadding else CenterVertically.align(it.height, height),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val Placeable?.widthOrZero: Int
|
||||||
|
get() = this?.width ?: 0
|
||||||
|
|
||||||
|
internal val Placeable?.heightOrZero: Int
|
||||||
|
get() = this?.height ?: 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts one value from another, where both values represent constraints used in layout.
|
||||||
|
*
|
||||||
|
* Notably:
|
||||||
|
* - if [this] is [Constraints.Infinity], the result stays [Constraints.Infinity]
|
||||||
|
* - the result is coerced to be non-negative
|
||||||
|
*/
|
||||||
|
internal fun Int.subtractConstraintSafely(other: Int): Int {
|
||||||
|
if (this == Constraints.Infinity) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
return (this - other).coerceAtLeast(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the actual layout phase, we can query supporting baselines,
|
||||||
|
// but for an intrinsic measurement pass, we have to estimate.
|
||||||
|
private fun Density.isSupportingMultilineHeuristic(estimatedSupportingHeight: Int): Boolean = estimatedSupportingHeight > 30.sp.roundToPx()
|
||||||
|
|
||||||
|
private fun verticalPadding(listItemType: ListItemType): Dp =
|
||||||
|
when (listItemType) {
|
||||||
|
ListItemType.ThreeLine -> ListItemThreeLineVerticalPadding
|
||||||
|
else -> ListItemVerticalPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
private value class ListItemType private constructor(
|
||||||
|
private val lines: Int,
|
||||||
|
) : Comparable<ListItemType> {
|
||||||
|
override operator fun compareTo(other: ListItemType) = lines.compareTo(other.lines)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** One line list item */
|
||||||
|
val OneLine = ListItemType(1)
|
||||||
|
|
||||||
|
/** Two line list item */
|
||||||
|
val TwoLine = ListItemType(2)
|
||||||
|
|
||||||
|
/** Three line list item */
|
||||||
|
val ThreeLine = ListItemType(3)
|
||||||
|
|
||||||
|
internal operator fun invoke(
|
||||||
|
hasOverline: Boolean,
|
||||||
|
hasSupporting: Boolean,
|
||||||
|
isSupportingMultiline: Boolean,
|
||||||
|
): ListItemType =
|
||||||
|
when {
|
||||||
|
(hasOverline && hasSupporting) || isSupportingMultiline -> ThreeLine
|
||||||
|
hasOverline || hasSupporting -> TwoLine
|
||||||
|
else -> OneLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun ProvideTextStyleFromToken(
|
||||||
|
contentColor: Color,
|
||||||
|
textStyle: TextStyle,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
val mergedStyle = LocalTextStyle.current.merge(textStyle)
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalContentColor provides contentColor,
|
||||||
|
LocalTextStyle provides mergedStyle,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ListTokens {
|
||||||
|
val ListItemContainerElevation = 0.0.dp
|
||||||
|
val ListItemOneLineContainerHeight = 56.0.dp
|
||||||
|
val ListItemThreeLineContainerHeight = 88.0.dp
|
||||||
|
val ListItemTwoLineContainerHeight = 72.0.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container related defaults
|
||||||
|
// TODO: Make sure these values stay up to date until replaced with tokens.
|
||||||
|
@VisibleForTesting internal val ListItemVerticalPadding = 8.dp
|
||||||
|
|
||||||
|
@VisibleForTesting internal val ListItemThreeLineVerticalPadding = 12.dp
|
||||||
|
|
||||||
|
@VisibleForTesting internal val ListItemStartPadding = 10.dp
|
||||||
|
|
||||||
|
@VisibleForTesting internal val ListItemEndPadding = 10.dp
|
||||||
|
|
||||||
|
// Icon related defaults.
|
||||||
|
// TODO: Make sure these values stay up to date until replaced with tokens.
|
||||||
|
@VisibleForTesting internal val LeadingContentEndPadding = Modifier.padding(end = 10.dp)
|
||||||
|
|
||||||
|
// Icon related defaults.
|
||||||
|
// TODO: Make sure these values stay up to date until replaced with tokens.
|
||||||
|
@VisibleForTesting internal val SupportingContentTopPadding = Modifier.padding(top = 2.dp)
|
||||||
|
|
||||||
|
// Trailing related defaults.
|
||||||
|
// TODO: Make sure these values stay up to date until replaced with tokens.
|
||||||
|
@VisibleForTesting internal val TrailingContentStartPadding = Modifier.padding(start = 10.dp)
|
||||||
@@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowing
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowing
|
||||||
|
import com.vitorpamplona.amethyst.ui.layouts.listItem.SlimListItem
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.nav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.nav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||||
@@ -45,33 +46,31 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.UnfollowButton
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ShowUserButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ShowUserButton
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserCompose(
|
fun UserCompose(
|
||||||
baseUser: User,
|
baseUser: User,
|
||||||
modifier: Modifier = StdPadding,
|
modifier: Modifier = Modifier,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
Row(
|
SlimListItem(
|
||||||
modifier = modifier.clickable { nav.nav(routeFor(baseUser)) },
|
modifier = modifier.clickable { nav.nav(routeFor(baseUser)) },
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
leadingContent = {
|
||||||
) {
|
UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav)
|
||||||
UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav)
|
},
|
||||||
|
headlineContent = {
|
||||||
Column(modifier = remember { StdStartPadding.weight(1f) }) {
|
UsernameDisplay(baseUser, accountViewModel = accountViewModel)
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
},
|
||||||
UsernameDisplay(baseUser, accountViewModel = accountViewModel)
|
supportingContent = {
|
||||||
}
|
|
||||||
|
|
||||||
AboutDisplay(baseUser, accountViewModel)
|
AboutDisplay(baseUser, accountViewModel)
|
||||||
}
|
},
|
||||||
|
trailingContent = {
|
||||||
Row(modifier = StdStartPadding) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
UserActionOptions(baseUser, accountViewModel, nav)
|
UserActionOptions(baseUser, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.material3.ListItem
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -37,19 +37,24 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.unit.dp
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserAboutMe
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserAboutMe
|
||||||
|
import com.vitorpamplona.amethyst.ui.layouts.listItem.SlimListItem
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.nav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ZapReqResponse
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ZapReqResponse
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -63,11 +68,11 @@ fun ZapNoteCompose(
|
|||||||
) {
|
) {
|
||||||
val baseNoteRequest by observeNote(baseReqResponse.zapRequest, accountViewModel)
|
val baseNoteRequest by observeNote(baseReqResponse.zapRequest, accountViewModel)
|
||||||
|
|
||||||
var baseAuthor by remember { mutableStateOf<User?>(null) }
|
var baseAuthor by remember { mutableStateOf<User?>(baseReqResponse.zapRequest.author) }
|
||||||
|
|
||||||
LaunchedEffect(baseNoteRequest) {
|
LaunchedEffect(baseNoteRequest) {
|
||||||
baseNoteRequest?.note?.let {
|
accountViewModel.decryptAmountMessage(baseNoteRequest.note, baseReqResponse.zapEvent) {
|
||||||
accountViewModel.decryptAmountMessage(it, baseReqResponse.zapEvent) { baseAuthor = it?.user }
|
baseAuthor = it?.user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,49 +86,95 @@ fun ZapNoteCompose(
|
|||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
) {
|
) {
|
||||||
baseAuthor?.let { RenderZapNote(it, baseReqResponse.zapEvent, nav, accountViewModel) }
|
baseAuthor?.let { RenderZapNoteSlim(it, baseReqResponse.zapEvent, accountViewModel, nav) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun RenderZapNotePreview() {
|
||||||
|
val accountViewModel = mockAccountViewModel()
|
||||||
|
|
||||||
|
val user1: User = LocalCache.getOrCreateUser("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c")
|
||||||
|
val note1: Note = LocalCache.getOrCreateNote("ca89cb11f1c75d5b6622268ff43d2288ea8b2cb5b9aa996ff9ff704fc904b78b")
|
||||||
|
|
||||||
|
ThemeComparisonColumn {
|
||||||
|
RenderZapNote(
|
||||||
|
user1,
|
||||||
|
note1,
|
||||||
|
accountViewModel,
|
||||||
|
EmptyNav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun RenderZapNoteSlimPreview() {
|
||||||
|
val accountViewModel = mockAccountViewModel()
|
||||||
|
|
||||||
|
val user1: User = LocalCache.getOrCreateUser("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c")
|
||||||
|
val note1: Note = LocalCache.getOrCreateNote("ca89cb11f1c75d5b6622268ff43d2288ea8b2cb5b9aa996ff9ff704fc904b78b")
|
||||||
|
|
||||||
|
ThemeComparisonColumn {
|
||||||
|
RenderZapNoteSlim(
|
||||||
|
user1,
|
||||||
|
note1,
|
||||||
|
accountViewModel,
|
||||||
|
EmptyNav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderZapNote(
|
private fun RenderZapNote(
|
||||||
baseAuthor: User,
|
baseAuthor: User,
|
||||||
zapNote: Note,
|
zapNote: Note,
|
||||||
nav: INav,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
Row(
|
ListItem(
|
||||||
modifier =
|
leadingContent = {
|
||||||
remember {
|
UserPicture(baseAuthor, Size55dp, accountViewModel = accountViewModel, nav = nav)
|
||||||
Modifier.padding(
|
},
|
||||||
start = 12.dp,
|
headlineContent = {
|
||||||
end = 12.dp,
|
UsernameDisplay(baseAuthor, accountViewModel = accountViewModel)
|
||||||
top = 10.dp,
|
},
|
||||||
)
|
supportingContent = {
|
||||||
},
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
UserPicture(baseAuthor, Size55dp, accountViewModel = accountViewModel, nav = nav)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = remember { StdStartPadding.weight(1f) },
|
|
||||||
) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseAuthor, accountViewModel = accountViewModel) }
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) { AboutDisplay(baseAuthor, accountViewModel) }
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = StdStartPadding,
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
) {
|
|
||||||
ZapAmount(zapNote, accountViewModel)
|
ZapAmount(zapNote, accountViewModel)
|
||||||
}
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
UserActionOptions(baseAuthor, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Row(modifier = StdStartPadding) {
|
@Composable
|
||||||
UserActionOptions(baseAuthor, accountViewModel, nav)
|
private fun RenderZapNoteSlim(
|
||||||
}
|
baseAuthor: User,
|
||||||
}
|
zapNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
SlimListItem(
|
||||||
|
leadingContent = {
|
||||||
|
UserPicture(baseAuthor, Size55dp, accountViewModel = accountViewModel, nav = nav)
|
||||||
|
},
|
||||||
|
headlineContent = {
|
||||||
|
UsernameDisplay(baseAuthor, accountViewModel = accountViewModel)
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
ZapAmount(zapNote, accountViewModel)
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
UserActionOptions(baseAuthor, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -154,6 +205,20 @@ private fun ZapAmount(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AboutDisplayNoFormat(
|
||||||
|
baseAuthor: User,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
val aboutMe by observeUserAboutMe(baseAuthor, accountViewModel)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
aboutMe,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AboutDisplay(
|
fun AboutDisplay(
|
||||||
baseAuthor: User,
|
baseAuthor: User,
|
||||||
|
|||||||
@@ -20,13 +20,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -36,20 +32,14 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
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.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -76,8 +66,10 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.header.Room
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.LoadEphemeralChatChannel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.LoadEphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.AccountPictureModifier
|
import com.vitorpamplona.amethyst.ui.theme.AccountPictureModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Height4dpModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.grayText
|
import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.newItemBubbleModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||||
@@ -316,6 +308,7 @@ private fun UserRoomCompose(
|
|||||||
|
|
||||||
val lastReadTime by accountViewModel.account.loadLastReadFlow("Room/${room.hashCode()}").collectAsStateWithLifecycle()
|
val lastReadTime by accountViewModel.account.loadLastReadFlow("Room/${room.hashCode()}").collectAsStateWithLifecycle()
|
||||||
if ((lastMessage.createdAt() ?: Long.MIN_VALUE) > lastReadTime) {
|
if ((lastMessage.createdAt() ?: Long.MIN_VALUE) > lastReadTime) {
|
||||||
|
Spacer(modifier = Height4dpModifier)
|
||||||
NewItemsBubble()
|
NewItemsBubble()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -412,6 +405,7 @@ fun ChannelName(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasNewMessages) {
|
if (hasNewMessages) {
|
||||||
|
Spacer(modifier = Height4dpModifier)
|
||||||
NewItemsBubble()
|
NewItemsBubble()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -434,23 +428,5 @@ private fun TimeAgo(channelLastTime: Long?) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewItemsBubble() {
|
fun NewItemsBubble() {
|
||||||
Box(
|
Box(MaterialTheme.colorScheme.newItemBubbleModifier)
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(start = 3.dp)
|
|
||||||
.width(10.dp)
|
|
||||||
.height(10.dp)
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
.background(MaterialTheme.colorScheme.primary),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
"",
|
|
||||||
color = Color.White,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
maxLines = 1,
|
|
||||||
modifier = Modifier.wrapContentHeight().align(Alignment.Center),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,15 +21,12 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps
|
||||||
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
||||||
@@ -82,11 +79,7 @@ private fun LnZapFeedLoaded(
|
|||||||
) {
|
) {
|
||||||
itemsIndexed(items, key = { _, item -> item.zapEvent.idHex }) { _, item ->
|
itemsIndexed(items, key = { _, item -> item.zapEvent.idHex }) { _, item ->
|
||||||
ZapNoteCompose(item, accountViewModel = accountViewModel, nav = nav)
|
ZapNoteCompose(item, accountViewModel = accountViewModel, nav = nav)
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier.padding(top = 10.dp),
|
|
||||||
thickness = DividerThickness,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,8 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -253,18 +251,28 @@ val DarkRelayIconModifier =
|
|||||||
|
|
||||||
val darkLargeProfilePictureModifier =
|
val darkLargeProfilePictureModifier =
|
||||||
Modifier
|
Modifier
|
||||||
.width(120.dp)
|
.size(120.dp)
|
||||||
.height(120.dp)
|
|
||||||
.clip(shape = CircleShape)
|
.clip(shape = CircleShape)
|
||||||
.border(3.dp, DarkColorPalette.onBackground, CircleShape)
|
.border(3.dp, DarkColorPalette.onBackground, CircleShape)
|
||||||
|
|
||||||
val lightLargeProfilePictureModifier =
|
val lightLargeProfilePictureModifier =
|
||||||
Modifier
|
Modifier
|
||||||
.width(120.dp)
|
.size(120.dp)
|
||||||
.height(120.dp)
|
|
||||||
.clip(shape = CircleShape)
|
.clip(shape = CircleShape)
|
||||||
.border(3.dp, LightColorPalette.onBackground, CircleShape)
|
.border(3.dp, LightColorPalette.onBackground, CircleShape)
|
||||||
|
|
||||||
|
val darkNewItemBubbleModifier =
|
||||||
|
Modifier
|
||||||
|
.size(10.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(DarkColorPalette.primary)
|
||||||
|
|
||||||
|
val lightNewItemBubbleModifier =
|
||||||
|
Modifier
|
||||||
|
.size(10.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(LightColorPalette.primary)
|
||||||
|
|
||||||
val RichTextDefaults = RichTextStyle().resolveDefaults()
|
val RichTextDefaults = RichTextStyle().resolveDefaults()
|
||||||
|
|
||||||
val MarkDownStyleOnDark =
|
val MarkDownStyleOnDark =
|
||||||
@@ -462,6 +470,10 @@ val ColorScheme.selectedReactionBoxModifier: Modifier
|
|||||||
val ColorScheme.largeProfilePictureModifier: Modifier
|
val ColorScheme.largeProfilePictureModifier: Modifier
|
||||||
get() = if (isLight) lightLargeProfilePictureModifier else darkLargeProfilePictureModifier
|
get() = if (isLight) lightLargeProfilePictureModifier else darkLargeProfilePictureModifier
|
||||||
|
|
||||||
|
@Suppress("ModifierFactoryExtensionFunction")
|
||||||
|
val ColorScheme.newItemBubbleModifier: Modifier
|
||||||
|
get() = if (isLight) lightNewItemBubbleModifier else darkNewItemBubbleModifier
|
||||||
|
|
||||||
val chartLightColors =
|
val chartLightColors =
|
||||||
VicoTheme(
|
VicoTheme(
|
||||||
candlestickCartesianLayerColors =
|
candlestickCartesianLayerColors =
|
||||||
|
|||||||
Reference in New Issue
Block a user