feat:超管显示功能
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user