diff --git a/app/src/main/java/com/huanchengfly/tieba/post/Extensions.kt b/app/src/main/java/com/huanchengfly/tieba/post/Extensions.kt index c4fe729a..9d4d91d4 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/Extensions.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/Extensions.kt @@ -33,9 +33,15 @@ fun Float.dpToPxFloat(): Float = fun Float.spToPx(): Int = (this * App.INSTANCE.resources.displayMetrics.scaledDensity + 0.5f).roundToInt() +fun Float.spToPxFloat(): Float = + this * App.INSTANCE.resources.displayMetrics.scaledDensity + 0.5f + fun Float.pxToDp(): Int = (this / App.ScreenInfo.DENSITY + 0.5f).roundToInt() +fun Float.pxToDpFloat(): Float = + this / App.ScreenInfo.DENSITY + 0.5f + fun Float.pxToSp(): Int = (this / App.INSTANCE.resources.displayMetrics.scaledDensity + 0.5f).roundToInt() @@ -47,6 +53,13 @@ fun Int.pxToDp(): Int = this.toFloat().pxToDp() fun Int.pxToSp(): Int = this.toFloat().pxToSp() +fun Float.pxToSpFloat(): Float = this / App.INSTANCE.resources.displayMetrics.scaledDensity + 0.5f + +fun Int.pxToSpFloat(): Float = this.toFloat().pxToSpFloat() + +fun Int.pxToDpFloat(): Float = + this.toFloat().pxToDpFloat() + inline fun String.fromJson(): Data { val type = object : TypeToken() {}.type return GsonUtil.getGson().fromJson(this, type) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt index 254f02ab..86d8cda0 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt @@ -28,11 +28,11 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.appendInlineContent import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.ButtonDefaults import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon -import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue @@ -77,6 +77,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight @@ -90,6 +91,7 @@ import com.airbnb.lottie.compose.LottieConstants import com.airbnb.lottie.compose.rememberLottieComposition import com.huanchengfly.tieba.post.App import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.activities.UserActivity import com.huanchengfly.tieba.post.api.TiebaApi import com.huanchengfly.tieba.post.api.booleanToString import com.huanchengfly.tieba.post.api.models.protos.Post @@ -129,6 +131,7 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.TitleCentredToolbar import com.huanchengfly.tieba.post.ui.widgets.compose.UserHeader import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalGrid +import com.huanchengfly.tieba.post.ui.widgets.compose.buildChipInlineContent import com.huanchengfly.tieba.post.ui.widgets.compose.rememberDialogState import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen import com.huanchengfly.tieba.post.utils.DateTimeUtils.getRelativeTimeString @@ -137,6 +140,7 @@ import com.huanchengfly.tieba.post.utils.StringUtil import com.huanchengfly.tieba.post.utils.StringUtil.getShortNumString import com.huanchengfly.tieba.post.utils.ThemeUtil import com.huanchengfly.tieba.post.utils.TiebaUtil +import com.huanchengfly.tieba.post.utils.Util.getIconColorByLevel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import kotlinx.collections.immutable.ImmutableList @@ -145,6 +149,7 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch import kotlinx.serialization.Serializable +import kotlin.math.max private fun getDescText( time: Long?, @@ -944,7 +949,7 @@ fun ThreadPage( } } item(key = "LoadPreviousBtn") { - if (currentPageMin > 1) { + if (hasPrevious) { Row( modifier = Modifier .fillMaxWidth() @@ -952,7 +957,7 @@ fun ThreadPage( viewModel.send( ThreadUiIntent.LoadPrevious( threadId, - currentPageMax - 1, + max(currentPageMax - 1, 1), forumId, postId = data .first() @@ -989,8 +994,9 @@ fun ThreadPage( PostCard( postHolder = item, contentRenders = contentRenders[index], - immersiveMode = isImmersiveMode, + threadAuthorId = author?.get { id } ?: 0L, blocked = blocked, + immersiveMode = isImmersiveMode, onAgree = { val postHasAgreed = item.get { agree?.hasAgree == 1 } @@ -1201,6 +1207,7 @@ private fun BottomBar( fun PostCard( postHolder: ImmutableHolder, contentRenders: ImmutableList, + threadAuthorId: Long = 0L, blocked: Boolean = false, immersiveMode: Boolean = false, onAgree: () -> Unit = {}, @@ -1279,17 +1286,21 @@ fun PostCard( ) }, name = { - Text( - text = StringUtil.getUsernameAnnotatedString( + UserNameText( + userName = StringUtil.getUsernameAnnotatedString( LocalContext.current, author.name, - author.nameShow, - LocalContentColor.current - ) + author.nameShow + ), + userLevel = author.level_id, + isLz = author.id == threadAuthorId ) }, desc = { Text(text = getDescText(post.time.toLong(), post.floor, author.ip_address)) + }, + onClick = { + UserActivity.launch(context, author.id.toString()) } ) { if (post.floor > 1) { @@ -1360,6 +1371,46 @@ fun PostCard( ) } +@Composable +private fun UserNameText( + userName: AnnotatedString, + userLevel: Int, + modifier: Modifier = Modifier, + isLz: Boolean = false, + bawuType: String? = null, +) { + val text = buildAnnotatedString { + append(userName) + append(" ") + appendInlineContent("Level", alternateText = "$userLevel") + if (!bawuType.isNullOrBlank()) { + append(" ") + appendInlineContent("Bawu", alternateText = bawuType) + } + if (isLz) { + append(" ") + appendInlineContent("Lz") + } + } + Text( + text = text, + inlineContent = mapOf( + "Level" to buildChipInlineContent( + "18", + color = Color(getIconColorByLevel("$userLevel")), + backgroundColor = Color(getIconColorByLevel("$userLevel")).copy(alpha = 0.25f) + ), + "Bawu" to buildChipInlineContent( + bawuType ?: "", + color = ExtendedTheme.colors.accent, + backgroundColor = ExtendedTheme.colors.accent.copy(alpha = 0.25f) + ), + "Lz" to buildChipInlineContent(stringResource(id = R.string.tip_lz)), + ), + modifier = modifier + ) +} + @Composable private fun ThreadMenu( isSeeLz: Boolean, diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/threadstore/ThreadStorePage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/threadstore/ThreadStorePage.kt index e2dd0ee5..236d860a 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/threadstore/ThreadStorePage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/threadstore/ThreadStorePage.kt @@ -269,11 +269,13 @@ private fun StoreItem( }, onClick = onUserClick, ) - val title = buildAnnotatedString { - append(info.title) - if (hasUpdate) { - append(" ") - appendInlineContent("Update", info.postNo) + val title = remember(info, hasUpdate) { + buildAnnotatedString { + append(info.title) + if (hasUpdate) { + append(" ") + appendInlineContent("Update", info.postNo) + } } } val updateTip = stringResource( diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Texts.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Texts.kt index 55b4173b..38f176e1 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Texts.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Texts.kt @@ -1,7 +1,16 @@ package com.huanchengfly.tieba.post.ui.widgets.compose +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.InlineTextContent import androidx.compose.material.Icon import androidx.compose.material.LocalContentAlpha @@ -14,9 +23,12 @@ import androidx.compose.material.icons.rounded.Link import androidx.compose.material.icons.rounded.SlowMotionVideo import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.ExperimentalTextApi @@ -28,6 +40,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow @@ -35,8 +48,11 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.dpToPxFloat import com.huanchengfly.tieba.post.pxToDp import com.huanchengfly.tieba.post.pxToSp +import com.huanchengfly.tieba.post.pxToSpFloat +import com.huanchengfly.tieba.post.spToPxFloat import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme import com.huanchengfly.tieba.post.utils.EmoticonManager import com.huanchengfly.tieba.post.utils.EmoticonUtil.emoticonString @@ -320,4 +336,62 @@ fun TextWithMinWidth( onTextLayout = onTextLayout, style = style ) +} + +@OptIn(ExperimentalTextApi::class) +@Composable +fun buildChipInlineContent( + text: String, + padding: PaddingValues = PaddingValues(vertical = 2.dp, horizontal = 4.dp), + textStyle: TextStyle = LocalTextStyle.current, + chipTextStyle: TextStyle = LocalTextStyle.current, + backgroundColor: Color = ExtendedTheme.colors.chip, + color: Color = ExtendedTheme.colors.onChip +): InlineTextContent { + val textMeasurer = rememberTextMeasurer() + val textSize = remember(text, textStyle) { textMeasurer.measure(text, textStyle).size } + val heightPx = textSize.height + val heightSp = heightPx.pxToSpFloat().sp + val textHeightPx = textStyle.fontSize.value.spToPxFloat() - + padding.calculateTopPadding().value.dpToPxFloat() - + padding.calculateBottomPadding().value.dpToPxFloat() + val fontSize = textHeightPx.pxToSpFloat().sp + val textWidthPx = textSize.width + val widthPx = textWidthPx + + padding.calculateStartPadding(LocalLayoutDirection.current).value.dpToPxFloat() + + padding.calculateEndPadding(LocalLayoutDirection.current).value.dpToPxFloat() + val widthSp = widthPx.pxToSpFloat().sp + return InlineTextContent( + placeholder = Placeholder( + width = widthSp, + height = heightSp, + placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter + ), + children = { + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + text = it.takeIf { it.isNotBlank() && it != "\uFFFD" } ?: text, + style = chipTextStyle.copy( + fontSize = fontSize, + lineHeight = fontSize, + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.Both + ) + ), + textAlign = TextAlign.Center, + color = color, + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(100)) + .background(backgroundColor) + .padding(padding) + ) + } + } + ) } \ No newline at end of file diff --git a/app/src/main/protos/User.proto b/app/src/main/protos/User.proto index 29df3e8a..5b260fdf 100644 --- a/app/src/main/protos/User.proto +++ b/app/src/main/protos/User.proto @@ -15,6 +15,7 @@ message User { int32 type = 7; int32 userhide = 9; int32 is_manager = 11; + int32 level_id = 23; int32 is_bawu = 25; string bawu_type = 26; string portraith = 27; @@ -34,5 +35,6 @@ message User { int32 priv_thread = 92; int32 isDefaultAvatar = 106; uint32 total_agree_num = 118; + string level_name = 125; string ip_address = 127; } \ No newline at end of file