feat:编辑帖子时支持删除和新增音乐

This commit is contained in:
amos
2025-12-30 14:04:15 +08:00
parent e2b2cb3900
commit 0d433a0fa5
7 changed files with 245 additions and 21 deletions

View File

@@ -13,11 +13,11 @@ android {
applicationId = "com.memory.app"
minSdk = 26
targetSdk = 35
versionCode = 27
versionName = "1.4.3"
versionCode = 28
versionName = "1.4.4"
buildConfigField("String", "API_BASE_URL", "\"https://x.amos.us.kg/api/\"")
buildConfigField("int", "VERSION_CODE", "27")
buildConfigField("int", "VERSION_CODE", "28")
}
signingConfigs {

View File

@@ -117,7 +117,8 @@ data class AddReactionRequest(
data class UpdatePostRequest(
val content: String,
@SerialName("media_ids") val mediaIds: List<String>? = null,
val visibility: Int? = null // 0=所有人可见, 1=仅自己可见
val visibility: Int? = null, // 0=所有人可见, 1=仅自己可见
@SerialName("music_url") val musicUrl: String? = null // null=不修改, ""=删除, "url"=新增/修改
)
@Serializable

View File

@@ -274,13 +274,13 @@ fun MainNavigation(
EditPostScreen(
post = post,
onClose = { editingPost = null },
onSave = { newContent, existingUrls, newImages, visibility ->
onSave = { newContent, existingUrls, newImages, visibility, musicUrl ->
if (newImages.isEmpty() && existingUrls == post.media.map { it.mediaUrl }) {
// 只更新文字可见性
homeViewModel.updatePost(post.id, newContent, null, visibility)
// 只更新文字可见性和音乐
homeViewModel.updatePost(post.id, newContent, null, visibility, musicUrl)
} else {
// 更新文字、图片可见性
homeViewModel.updatePostWithImages(context, post.id, newContent, existingUrls, newImages, visibility)
// 更新文字、图片可见性和音乐
homeViewModel.updatePostWithImages(context, post.id, newContent, existingUrls, newImages, visibility, musicUrl)
}
editingPost = null
},

View File

@@ -12,9 +12,11 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.MusicNote
import androidx.compose.material.icons.outlined.Image
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material.icons.outlined.Mood
import androidx.compose.material.icons.outlined.MusicNote
import androidx.compose.material.icons.outlined.Public
import androidx.compose.material3.*
import androidx.compose.runtime.*
@@ -35,7 +37,7 @@ import com.memory.app.ui.theme.*
fun EditPostScreen(
post: Post,
onClose: () -> Unit,
onSave: (String, List<String>, List<Uri>, Int) -> Unit, // 添加 visibility 参数
onSave: (String, List<String>, List<Uri>, Int, String?) -> Unit, // content, existingImages, newImages, visibility, musicUrl
isLoading: Boolean
) {
// 拦截系统返回手势
@@ -48,6 +50,10 @@ fun EditPostScreen(
var newImages by remember { mutableStateOf<List<Uri>>(emptyList()) }
var showEmojiPicker by remember { mutableStateOf(false) }
var visibility by remember { mutableStateOf(post.visibility) }
var musicUrl by remember { mutableStateOf(post.music?.shareUrl ?: "") }
var showMusicInput by remember { mutableStateOf(false) }
// 标记是否有原始音乐(用于判断是删除还是新增)
val hadOriginalMusic = post.music != null
val totalImages = existingImages.size + newImages.size
@@ -86,7 +92,18 @@ fun EditPostScreen(
}
Button(
onClick = { onSave(content, existingImages, newImages, visibility) },
onClick = {
// 如果原来有音乐但现在没有,传空字符串表示删除
// 如果原来没有音乐但现在有,传新的 URL
// 如果都没变,传 null 表示不修改
val musicParam = when {
hadOriginalMusic && musicUrl.isBlank() -> "" // 删除音乐
!hadOriginalMusic && musicUrl.isNotBlank() -> musicUrl // 新增音乐
hadOriginalMusic && musicUrl != post.music?.shareUrl -> musicUrl // 修改音乐(虽然目前不太可能)
else -> null // 不修改
}
onSave(content, existingImages, newImages, visibility, musicParam)
},
enabled = content.isNotBlank() && !isLoading,
shape = RoundedCornerShape(50),
colors = ButtonDefaults.buttonColors(
@@ -215,13 +232,73 @@ fun EditPostScreen(
}
}
// Music Card - 显示已有音乐(编辑时不可修改)
if (post.music != null) {
// Music Card - 显示已有音乐或新添加的音乐链接
if (musicUrl.isNotBlank()) {
Spacer(modifier = Modifier.height(12.dp))
MusicCard(
music = post.music,
onClick = { }
)
if (post.music != null && musicUrl == post.music.shareUrl) {
// 显示已有音乐卡片(带删除按钮)
Box {
MusicCard(
music = post.music,
onClick = { }
)
// 删除按钮
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(8.dp)
.size(24.dp)
.clip(CircleShape)
.background(Color.Black.copy(alpha = 0.6f))
.clickable { musicUrl = "" },
contentAlignment = Alignment.Center
) {
Icon(
Icons.Default.Close,
contentDescription = "移除音乐",
modifier = Modifier.size(14.dp),
tint = Color.White
)
}
}
} else {
// 显示新添加的音乐链接
Surface(
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.MusicNote,
contentDescription = null,
tint = Brand500,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "已添加音乐链接",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.weight(1f)
)
IconButton(
onClick = { musicUrl = "" },
modifier = Modifier.size(24.dp)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "移除",
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(16.dp)
)
}
}
}
}
}
}
}
@@ -248,6 +325,21 @@ fun EditPostScreen(
modifier = Modifier.size(24.dp)
)
}
// Music Button
IconButton(
onClick = { showMusicInput = true },
enabled = musicUrl.isBlank(),
modifier = Modifier.size(40.dp)
) {
Icon(
Icons.Outlined.MusicNote,
contentDescription = "添加音乐",
tint = if (musicUrl.isBlank()) Brand500 else Slate300,
modifier = Modifier.size(24.dp)
)
}
IconButton(
onClick = { showEmojiPicker = true },
modifier = Modifier.size(40.dp)
@@ -297,6 +389,17 @@ fun EditPostScreen(
}
)
}
// Music Link Input Dialog
if (showMusicInput) {
EditMusicLinkInputDialog(
onDismiss = { showMusicInput = false },
onConfirm = { url ->
musicUrl = url
showMusicInput = false
}
)
}
}
// 图片项类型
@@ -437,3 +540,97 @@ private fun EditPostEmojiPicker(
}
}
}
@Composable
private fun EditMusicLinkInputDialog(
onDismiss: () -> Unit,
onConfirm: (String) -> Unit
) {
var inputUrl by remember { mutableStateOf("") }
val isValidUrl = inputUrl.contains("y.qq.com") || inputUrl.contains("qq.com")
androidx.compose.ui.window.Dialog(onDismissRequest = onDismiss) {
Surface(
shape = RoundedCornerShape(24.dp),
color = MaterialTheme.colorScheme.surface,
shadowElevation = 16.dp,
modifier = Modifier.fillMaxWidth()
) {
Column(modifier = Modifier.padding(24.dp)) {
// 标题
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.size(4.dp, 24.dp)
.clip(RoundedCornerShape(2.dp))
.background(Brand500)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "添加音乐",
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
color = MaterialTheme.colorScheme.onSurface
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "粘贴 QQ 音乐分享链接",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(16.dp))
// 输入框
OutlinedTextField(
value = inputUrl,
onValueChange = { inputUrl = it },
placeholder = {
Text(
"https://y.qq.com/...",
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
)
},
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Brand500,
unfocusedBorderColor = MaterialTheme.colorScheme.outline
),
singleLine = true
)
Spacer(modifier = Modifier.height(20.dp))
// 按钮
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
TextButton(onClick = onDismiss) {
Text(
"取消",
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Spacer(modifier = Modifier.width(8.dp))
Button(
onClick = { onConfirm(inputUrl) },
enabled = isValidUrl,
colors = ButtonDefaults.buttonColors(
containerColor = Brand500,
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant
),
shape = RoundedCornerShape(12.dp)
) {
Text("确定", fontWeight = FontWeight.Bold)
}
}
}
}
}
}

View File

@@ -135,10 +135,10 @@ class HomeViewModel : ViewModel() {
}
}
fun updatePost(postId: Long, content: String, mediaIds: List<String>? = null, visibility: Int? = null) {
fun updatePost(postId: Long, content: String, mediaIds: List<String>? = null, visibility: Int? = null, musicUrl: String? = null) {
viewModelScope.launch {
try {
val response = ApiClient.api.updatePost(postId, UpdatePostRequest(content, mediaIds, visibility))
val response = ApiClient.api.updatePost(postId, UpdatePostRequest(content, mediaIds, visibility, musicUrl))
if (response.isSuccessful) {
// 刷新获取最新数据
loadPosts()
@@ -149,7 +149,7 @@ class HomeViewModel : ViewModel() {
}
}
fun updatePostWithImages(context: Context, postId: Long, content: String, existingUrls: List<String>, newImages: List<Uri>, visibility: Int? = null) {
fun updatePostWithImages(context: Context, postId: Long, content: String, existingUrls: List<String>, newImages: List<Uri>, visibility: Int? = null, musicUrl: String? = null) {
viewModelScope.launch {
_isPosting.value = true
try {
@@ -174,7 +174,7 @@ class HomeViewModel : ViewModel() {
val allMediaIds = existingUrls + newImageUrls
// 更新帖子
val response = ApiClient.api.updatePost(postId, UpdatePostRequest(content, allMediaIds, visibility))
val response = ApiClient.api.updatePost(postId, UpdatePostRequest(content, allMediaIds, visibility, musicUrl))
if (response.isSuccessful) {
loadPosts()
}

View File

@@ -271,6 +271,31 @@ func (h *PostHandler) Update(c *gin.Context) {
}
}
// 处理音乐更新
if req.MusicURL != nil {
// 先删除旧的音乐
_, err = tx.Exec("DELETE FROM post_music WHERE post_id = ?", postID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update music"})
return
}
// 如果提供了新的音乐链接,解析并保存
if *req.MusicURL != "" && IsQQMusicURL(*req.MusicURL) {
parser := &QQMusicParser{}
musicInfo, err := parser.Parse(*req.MusicURL)
if err == nil && musicInfo != nil {
_, err = tx.Exec(
"INSERT INTO post_music (post_id, title, artist, cover, share_url, platform) VALUES (?, ?, ?, ?, ?, ?)",
postID, musicInfo.Title, musicInfo.Artist, musicInfo.Cover, musicInfo.ShareURL, musicInfo.Platform,
)
if err != nil {
// 音乐解析失败不影响帖子更新
}
}
}
}
if err := tx.Commit(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to commit"})
return

View File

@@ -107,6 +107,7 @@ type UpdatePostRequest struct {
Content string `json:"content" binding:"required,max=1000"`
MediaIDs []string `json:"media_ids"`
Visibility *int `json:"visibility"` // 0=所有人可见, 1=仅自己可见
MusicURL *string `json:"music_url"` // nil=不修改, ""=删除, "url"=新增/修改
}
type CreateCommentRequest struct {