feat: Compose Widgets
This commit is contained in:
parent
9be00b177a
commit
f1b5c083ef
|
|
@ -132,8 +132,6 @@ import kotlinx.coroutines.launch
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
private val TabWidth = 100.dp
|
|
||||||
|
|
||||||
fun getSortType(
|
fun getSortType(
|
||||||
context: Context,
|
context: Context,
|
||||||
forumName: String
|
forumName: String
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,8 @@ fun HotPage(
|
||||||
item(key = "TopicList") {
|
item(key = "TopicList") {
|
||||||
VerticalGrid(
|
VerticalGrid(
|
||||||
column = 2,
|
column = 2,
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
items = topicList,
|
items = topicList,
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,8 @@ fun Dislike(
|
||||||
bottom.linkTo(parent.bottom)
|
bottom.linkTo(parent.bottom)
|
||||||
}
|
}
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = dislikeResource,
|
items = dislikeResource,
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ import com.huanchengfly.tieba.post.ui.page.destinations.SettingsPageDestination
|
||||||
import com.huanchengfly.tieba.post.ui.page.destinations.ThreadStorePageDestination
|
import com.huanchengfly.tieba.post.ui.page.destinations.ThreadStorePageDestination
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.HorizontalDivider
|
import com.huanchengfly.tieba.post.ui.widgets.compose.HorizontalDivider
|
||||||
|
import com.huanchengfly.tieba.post.ui.widgets.compose.ListMenuItem
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Switch
|
import com.huanchengfly.tieba.post.ui.widgets.compose.Switch
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
|
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
|
||||||
|
|
@ -377,36 +378,3 @@ fun UserPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ListMenuItem(
|
|
||||||
icon: ImageVector,
|
|
||||||
text: String,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onClick: (() -> Unit)? = null,
|
|
||||||
customContent: @Composable (RowScope.() -> Unit)? = null,
|
|
||||||
) {
|
|
||||||
val menuModifier = if (onClick != null) {
|
|
||||||
Modifier.clickable(onClick = onClick)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = modifier
|
|
||||||
.then(menuModifier)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
|
||||||
) {
|
|
||||||
Icon(imageVector = icon, contentDescription = text, tint = ExtendedTheme.colors.accent)
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
maxLines = 1,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
customContent?.invoke(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -108,6 +108,7 @@ private fun calcRows(column: Int, items: List<GridScopeImpl.Item>): List<List<Gr
|
||||||
fun VerticalGrid(
|
fun VerticalGrid(
|
||||||
column: Int,
|
column: Int,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
rowModifier: Modifier = Modifier,
|
||||||
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
||||||
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
|
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
|
||||||
content: GridScope.() -> Unit
|
content: GridScope.() -> Unit
|
||||||
|
|
@ -118,7 +119,8 @@ fun VerticalGrid(
|
||||||
rows.forEach {
|
rows.forEach {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = horizontalArrangement
|
horizontalArrangement = horizontalArrangement,
|
||||||
|
modifier = rowModifier
|
||||||
) {
|
) {
|
||||||
it.forEach {
|
it.forEach {
|
||||||
Box(
|
Box(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.huanchengfly.tieba.post.ui.widgets.compose
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ListMenuItem(
|
||||||
|
icon: ImageVector,
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
iconColor: Color = ExtendedTheme.colors.accent,
|
||||||
|
onClick: (() -> Unit)? = null,
|
||||||
|
customContent: @Composable (RowScope.() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val menuModifier = if (onClick != null) {
|
||||||
|
Modifier.clickable(onClick = onClick)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = modifier
|
||||||
|
.then(menuModifier)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
) {
|
||||||
|
Icon(imageVector = icon, contentDescription = text, tint = iconColor)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
customContent?.invoke(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.huanchengfly.tieba.post.ui.widgets.compose
|
package com.huanchengfly.tieba.post.ui.widgets.compose
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.text.InlineTextContent
|
import androidx.compose.foundation.text.InlineTextContent
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
|
|
@ -10,6 +11,7 @@ import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.AccountCircle
|
import androidx.compose.material.icons.rounded.AccountCircle
|
||||||
import androidx.compose.material.icons.rounded.Link
|
import androidx.compose.material.icons.rounded.Link
|
||||||
|
import androidx.compose.material.icons.rounded.SlowMotionVideo
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -17,6 +19,7 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.takeOrElse
|
import androidx.compose.ui.graphics.takeOrElse
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.ExperimentalTextApi
|
||||||
import androidx.compose.ui.text.Placeholder
|
import androidx.compose.ui.text.Placeholder
|
||||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||||
import androidx.compose.ui.text.TextLayoutResult
|
import androidx.compose.ui.text.TextLayoutResult
|
||||||
|
|
@ -24,6 +27,7 @@ import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
|
@ -38,7 +42,6 @@ import com.huanchengfly.tieba.post.utils.EmoticonManager
|
||||||
import com.huanchengfly.tieba.post.utils.EmoticonUtil.emoticonString
|
import com.huanchengfly.tieba.post.utils.EmoticonUtil.emoticonString
|
||||||
import com.huanchengfly.tieba.post.utils.getEmoticonHeightPx
|
import com.huanchengfly.tieba.post.utils.getEmoticonHeightPx
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EmoticonText(
|
fun EmoticonText(
|
||||||
text: String,
|
text: String,
|
||||||
|
|
@ -100,6 +103,7 @@ fun EmoticonText(
|
||||||
softWrap: Boolean = true,
|
softWrap: Boolean = true,
|
||||||
maxLines: Int = Int.MAX_VALUE,
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
minLines: Int = 1,
|
minLines: Int = 1,
|
||||||
|
emoticonSize: Float = 0.9f,
|
||||||
inlineContent: Map<String, InlineTextContent> = emptyMap(),
|
inlineContent: Map<String, InlineTextContent> = emptyMap(),
|
||||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||||
style: TextStyle = LocalTextStyle.current
|
style: TextStyle = LocalTextStyle.current
|
||||||
|
|
@ -122,7 +126,7 @@ fun EmoticonText(
|
||||||
letterSpacing = letterSpacing
|
letterSpacing = letterSpacing
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val sizePx = getEmoticonHeightPx(mergedStyle)
|
val sizePx = getEmoticonHeightPx(mergedStyle) * emoticonSize
|
||||||
val emoticonInlineContent =
|
val emoticonInlineContent =
|
||||||
remember(sizePx) { EmoticonManager.getEmoticonInlineContent(sizePx) }
|
remember(sizePx) { EmoticonManager.getEmoticonInlineContent(sizePx) }
|
||||||
IconText(
|
IconText(
|
||||||
|
|
@ -203,7 +207,22 @@ fun IconText(
|
||||||
Icons.Rounded.Link,
|
Icons.Rounded.Link,
|
||||||
contentDescription = stringResource(id = R.string.link),
|
contentDescription = stringResource(id = R.string.link),
|
||||||
modifier = Modifier.size(sizeDp),
|
modifier = Modifier.size(sizeDp),
|
||||||
tint = ExtendedTheme.colors.accent,
|
tint = ExtendedTheme.colors.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"video_icon" to InlineTextContent(
|
||||||
|
placeholder = Placeholder(
|
||||||
|
width = sizeSp,
|
||||||
|
height = sizeSp,
|
||||||
|
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter
|
||||||
|
),
|
||||||
|
children = {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.SlowMotionVideo,
|
||||||
|
contentDescription = stringResource(id = R.string.desc_video),
|
||||||
|
modifier = Modifier.size(sizeDp),
|
||||||
|
tint = ExtendedTheme.colors.primary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
@ -218,7 +237,7 @@ fun IconText(
|
||||||
Icons.Rounded.AccountCircle,
|
Icons.Rounded.AccountCircle,
|
||||||
contentDescription = stringResource(id = R.string.user),
|
contentDescription = stringResource(id = R.string.user),
|
||||||
modifier = Modifier.size(sizeDp),
|
modifier = Modifier.size(sizeDp),
|
||||||
tint = ExtendedTheme.colors.accent,
|
tint = ExtendedTheme.colors.primary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
@ -245,3 +264,60 @@ fun IconText(
|
||||||
style
|
style
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Text] composable that supports setting its minimum width to width of the specified number of Chinese characters
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalTextApi::class)
|
||||||
|
@Composable
|
||||||
|
fun TextWithMinWidth(
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
color: Color = Color.Unspecified,
|
||||||
|
fontSize: TextUnit = TextUnit.Unspecified,
|
||||||
|
fontStyle: FontStyle? = null,
|
||||||
|
fontWeight: FontWeight? = null,
|
||||||
|
fontFamily: FontFamily? = null,
|
||||||
|
letterSpacing: TextUnit = TextUnit.Unspecified,
|
||||||
|
textDecoration: TextDecoration? = null,
|
||||||
|
textAlign: TextAlign? = null,
|
||||||
|
lineHeight: TextUnit = TextUnit.Unspecified,
|
||||||
|
overflow: TextOverflow = TextOverflow.Clip,
|
||||||
|
softWrap: Boolean = true,
|
||||||
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
|
minLines: Int = 1,
|
||||||
|
minLength: Int = 0,
|
||||||
|
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||||
|
style: TextStyle = LocalTextStyle.current,
|
||||||
|
) {
|
||||||
|
val textMeasurer = rememberTextMeasurer()
|
||||||
|
val singleChar = stringResource(id = R.string.single_chinese_char)
|
||||||
|
val singleCharWidth = remember(style) {
|
||||||
|
textMeasurer.measure(
|
||||||
|
text = singleChar,
|
||||||
|
style = style
|
||||||
|
).size.width.pxToDp().dp
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
modifier = Modifier
|
||||||
|
.defaultMinSize(minWidth = singleCharWidth * minLength)
|
||||||
|
.then(modifier),
|
||||||
|
color = color,
|
||||||
|
fontSize = fontSize,
|
||||||
|
fontStyle = fontStyle,
|
||||||
|
fontWeight = fontWeight,
|
||||||
|
fontFamily = fontFamily,
|
||||||
|
letterSpacing = letterSpacing,
|
||||||
|
textDecoration = textDecoration,
|
||||||
|
textAlign = textAlign,
|
||||||
|
lineHeight = lineHeight,
|
||||||
|
overflow = overflow,
|
||||||
|
softWrap = softWrap,
|
||||||
|
maxLines = maxLines,
|
||||||
|
minLines = minLines,
|
||||||
|
onTextLayout = onTextLayout,
|
||||||
|
style = style
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -231,6 +231,58 @@ fun TitleCentredToolbar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TitleCentredToolbar(
|
||||||
|
title: @Composable () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
navigationIcon: @Composable (() -> Unit)? = null,
|
||||||
|
actions: @Composable (RowScope.() -> Unit) = {},
|
||||||
|
content: @Composable (ColumnScope.() -> Unit) = {},
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(ExtendedTheme.colors.topBar)
|
||||||
|
.then(modifier)
|
||||||
|
) {
|
||||||
|
TopAppBar(
|
||||||
|
backgroundColor = ExtendedTheme.colors.topBar,
|
||||||
|
contentColor = ExtendedTheme.colors.onTopBar,
|
||||||
|
elevation = 0.dp
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxHeight()
|
||||||
|
) {
|
||||||
|
navigationIcon?.invoke()
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
actions()
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.align(Alignment.Center),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
title()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(color = ExtendedTheme.colors.topBar)
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Toolbar(
|
fun Toolbar(
|
||||||
title: String,
|
title: String,
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ object EmoticonManager {
|
||||||
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
fun getEmoticonInlineContent(
|
fun getEmoticonInlineContent(
|
||||||
sizePx: Int
|
sizePx: Float
|
||||||
): Map<String, InlineTextContent> {
|
): Map<String, InlineTextContent> {
|
||||||
return emoticonIds.associate { id ->
|
return emoticonIds.associate { id ->
|
||||||
"Emoticon#$id" to InlineTextContent(
|
"Emoticon#$id" to InlineTextContent(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue