feat:编辑帖子时支持删除和新增音乐
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user