feat:超管显示功能

This commit is contained in:
amos
2025-12-19 11:20:37 +08:00
parent d375c967e7
commit 9d580f5a18
3 changed files with 235 additions and 112 deletions

View File

@@ -28,7 +28,16 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.Canvas
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.LinearEasing
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateMap
@@ -42,6 +51,7 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.material3.LocalTextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
@@ -565,6 +575,12 @@ fun UserInfoDialog(
}
}
// 超级管理员标签
if (isSuperAdmin) {
Spacer(modifier = Modifier.height(6.dp))
SuperAdminLabel()
}
Spacer(modifier = Modifier.height(2.dp))
Text(
@@ -699,12 +715,26 @@ private fun UserStatItem(
}
}
// 彩虹渐变盾牌徽章
// 创建盾牌路径的辅助函数
private fun createShieldPath(w: Float, h: Float): androidx.compose.ui.graphics.Path {
return androidx.compose.ui.graphics.Path().apply {
moveTo(w * 0.5f, h * 0.02f)
lineTo(w * 0.95f, h * 0.15f)
lineTo(w * 0.95f, h * 0.45f)
quadraticBezierTo(w * 0.95f, h * 0.8f, w * 0.5f, h * 0.98f)
quadraticBezierTo(w * 0.05f, h * 0.8f, w * 0.05f, h * 0.45f)
lineTo(w * 0.05f, h * 0.15f)
close()
}
}
// 彩虹渐变盾牌徽章 - 带闪光动画(按盾牌形状裁剪)
@Composable
fun RainbowShieldBadge(
size: androidx.compose.ui.unit.Dp = 18.dp,
modifier: Modifier = Modifier
) {
// 彩虹色列表
val rainbowColors = listOf(
Color(0xFFFF6B6B), // 红
Color(0xFFFF9F43), // 橙
@@ -712,32 +742,66 @@ fun RainbowShieldBadge(
Color(0xFF5CD85A), // 绿
Color(0xFF54A0FF), // 蓝
Color(0xFF9B59B6), // 紫
Color(0xFFFF6B6B) // 红(循环)
)
val rainbowBrush = Brush.sweepGradient(rainbowColors)
Box(
modifier = modifier.size(size),
contentAlignment = Alignment.Center
) {
// 彩虹渐变背景圆环
Canvas(modifier = Modifier.size(size)) {
drawCircle(
brush = rainbowBrush,
radius = size.toPx() / 2 - 1.dp.toPx(),
style = Stroke(width = 2.dp.toPx())
// 闪光动画 - 从左到右
val infiniteTransition = rememberInfiniteTransition(label = "shimmer")
val shimmerProgress by infiniteTransition.animateFloat(
initialValue = -0.5f,
targetValue = 1.5f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1200, easing = LinearEasing)
),
label = "shimmer"
)
// 彩虹渐变画刷
val rainbowBrush = Brush.linearGradient(
colors = rainbowColors + rainbowColors.first(),
start = Offset(0f, 0f),
end = Offset(size.value * 2, size.value * 2)
)
Canvas(modifier = modifier.size(size)) {
val w = size.toPx()
val h = size.toPx()
// 创建盾牌路径
val shieldPath = createShieldPath(w, h)
// 绘制彩虹色盾牌
drawPath(shieldPath, rainbowBrush)
// 按盾牌形状裁剪闪光效果
clipPath(shieldPath) {
val shimmerWidth = w * 0.5f
val shimmerStart = shimmerProgress * w * 1.5f - shimmerWidth / 2
drawRect(
brush = Brush.linearGradient(
colors = listOf(
Color.Transparent,
Color.White.copy(alpha = 0.7f),
Color.Transparent
),
start = Offset(shimmerStart, 0f),
end = Offset(shimmerStart + shimmerWidth, h)
)
)
}
// 盾牌图标
Icon(
imageVector = Icons.Filled.Shield,
contentDescription = "超级管理员",
tint = Color(0xFF54A0FF),
modifier = Modifier.size(size * 0.7f)
)
}
}
// 超级管理员文字标签 - 带彩虹渐变和闪光效果
@Composable
fun SuperAdminLabel(modifier: Modifier = Modifier) {
Text(
text = "超级管理员",
fontSize = 11.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = modifier
)
}
@Composable
fun EditPostDialog(
post: Post,

View File

@@ -7,8 +7,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@@ -48,7 +47,7 @@ fun CreatePostScreen(
val imagePicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetMultipleContents()
) { uris ->
selectedImages = (selectedImages + uris).take(4)
selectedImages = (selectedImages + uris).take(6)
}
Column(
@@ -174,36 +173,35 @@ fun CreatePostScreen(
)
)
// Selected Images
// Selected Images - 超过3张显示为两行
if (selectedImages.isNotEmpty()) {
Spacer(modifier = Modifier.height(16.dp))
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
items(selectedImages) { uri ->
Box {
AsyncImage(
model = uri,
contentDescription = null,
modifier = Modifier
.size(100.dp)
.clip(RoundedCornerShape(12.dp)),
contentScale = ContentScale.Crop
if (selectedImages.size <= 3) {
// 3张及以下单行显示
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
selectedImages.forEach { uri ->
SelectedImageItem(
uri = uri,
onRemove = { selectedImages = selectedImages - uri }
)
// Remove button
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(4.dp)
.size(24.dp)
.clip(CircleShape)
.background(Color.Black.copy(alpha = 0.6f))
.clickable { selectedImages = selectedImages - uri },
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.Close,
contentDescription = "移除",
modifier = Modifier.size(14.dp),
tint = Color.White
}
}
} else {
// 超过3张两行显示每行3张
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
selectedImages.take(3).forEach { uri ->
SelectedImageItem(
uri = uri,
onRemove = { selectedImages = selectedImages - uri }
)
}
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
selectedImages.drop(3).forEach { uri ->
SelectedImageItem(
uri = uri,
onRemove = { selectedImages = selectedImages - uri }
)
}
}
@@ -227,13 +225,13 @@ fun CreatePostScreen(
// Image Button
IconButton(
onClick = { imagePicker.launch("image/*") },
enabled = selectedImages.size < 4,
enabled = selectedImages.size < 6,
modifier = Modifier.size(40.dp)
) {
Icon(
Icons.Outlined.Image,
contentDescription = "添加图片",
tint = if (selectedImages.size < 4) Brand500 else MaterialTheme.colorScheme.onSurfaceVariant,
tint = if (selectedImages.size < 6) Brand500 else MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(24.dp)
)
}
@@ -285,6 +283,40 @@ fun CreatePostScreen(
}
}
@Composable
private fun SelectedImageItem(
uri: Uri,
onRemove: () -> Unit
) {
Box {
AsyncImage(
model = uri,
contentDescription = null,
modifier = Modifier
.size(90.dp)
.clip(RoundedCornerShape(10.dp)),
contentScale = ContentScale.Crop
)
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(4.dp)
.size(22.dp)
.clip(CircleShape)
.background(Color.Black.copy(alpha = 0.6f))
.clickable { onRemove() },
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.Close,
contentDescription = "移除",
modifier = Modifier.size(12.dp),
tint = Color.White
)
}
}
}
@Composable
private fun CreatePostEmojiPicker(
onDismiss: () -> Unit,

View File

@@ -7,8 +7,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@@ -51,7 +50,7 @@ fun EditPostScreen(
val imagePicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetMultipleContents()
) { uris ->
val remaining = 4 - totalImages
val remaining = 6 - totalImages
newImages = (newImages + uris).take(remaining.coerceAtLeast(0))
}
@@ -160,66 +159,51 @@ fun EditPostScreen(
)
)
// Images
if (existingImages.isNotEmpty() || newImages.isNotEmpty()) {
// Images - 合并已有图片和新图片超过3张显示为两行
val allImages = existingImages.map { ImageItem.Existing(it) } + newImages.map { ImageItem.New(it) }
if (allImages.isNotEmpty()) {
Spacer(modifier = Modifier.height(16.dp))
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
// Existing images
items(existingImages) { url ->
Box {
AsyncImage(
model = url,
contentDescription = null,
modifier = Modifier
.size(100.dp)
.clip(RoundedCornerShape(12.dp)),
contentScale = ContentScale.Crop
if (allImages.size <= 3) {
// 3张及以下单行显示
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
allImages.forEach { item ->
EditImageItem(
item = item,
onRemove = {
when (item) {
is ImageItem.Existing -> existingImages = existingImages - item.url
is ImageItem.New -> newImages = newImages - item.uri
}
}
)
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(4.dp)
.size(24.dp)
.clip(CircleShape)
.background(Color.Black.copy(alpha = 0.6f))
.clickable { existingImages = existingImages - url },
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.Close,
contentDescription = "移除",
modifier = Modifier.size(14.dp),
tint = Color.White
}
}
} else {
// 超过3张两行显示每行3张
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
allImages.take(3).forEach { item ->
EditImageItem(
item = item,
onRemove = {
when (item) {
is ImageItem.Existing -> existingImages = existingImages - item.url
is ImageItem.New -> newImages = newImages - item.uri
}
}
)
}
}
}
// New images
items(newImages) { uri ->
Box {
AsyncImage(
model = uri,
contentDescription = null,
modifier = Modifier
.size(100.dp)
.clip(RoundedCornerShape(12.dp)),
contentScale = ContentScale.Crop
)
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(4.dp)
.size(24.dp)
.clip(CircleShape)
.background(Color.Black.copy(alpha = 0.6f))
.clickable { newImages = newImages - uri },
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.Close,
contentDescription = "移除",
modifier = Modifier.size(14.dp),
tint = Color.White
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
allImages.drop(3).forEach { item ->
EditImageItem(
item = item,
onRemove = {
when (item) {
is ImageItem.Existing -> existingImages = existingImages - item.url
is ImageItem.New -> newImages = newImages - item.uri
}
}
)
}
}
@@ -241,13 +225,13 @@ fun EditPostScreen(
) {
IconButton(
onClick = { imagePicker.launch("image/*") },
enabled = totalImages < 4,
enabled = totalImages < 6,
modifier = Modifier.size(40.dp)
) {
Icon(
Icons.Outlined.Image,
contentDescription = "添加图片",
tint = if (totalImages < 4) Brand500 else Slate300,
tint = if (totalImages < 6) Brand500 else Slate300,
modifier = Modifier.size(24.dp)
)
}
@@ -277,6 +261,49 @@ fun EditPostScreen(
}
}
// 图片项类型
private sealed class ImageItem {
data class Existing(val url: String) : ImageItem()
data class New(val uri: Uri) : ImageItem()
}
@Composable
private fun EditImageItem(
item: ImageItem,
onRemove: () -> Unit
) {
Box {
AsyncImage(
model = when (item) {
is ImageItem.Existing -> item.url
is ImageItem.New -> item.uri
},
contentDescription = null,
modifier = Modifier
.size(90.dp)
.clip(RoundedCornerShape(10.dp)),
contentScale = ContentScale.Crop
)
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(4.dp)
.size(22.dp)
.clip(CircleShape)
.background(Color.Black.copy(alpha = 0.6f))
.clickable { onRemove() },
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.Close,
contentDescription = "移除",
modifier = Modifier.size(12.dp),
tint = Color.White
)
}
}
}
@Composable
private fun EditPostEmojiPicker(
onDismiss: () -> Unit,