功能:作品编辑和删除

This commit is contained in:
xiao12feng8 2026-01-07 18:34:31 +08:00
parent c8e22d497e
commit cc667a7643
16 changed files with 703 additions and 245 deletions

View File

@ -148,6 +148,21 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateWorks(WorksRequest request, Integer userId) {
log.info("=== 开始更新作品 ===");
log.info("作品ID: {}", request.getId());
log.info("用户ID: {}", userId);
log.info("标题: {}", request.getTitle());
log.info("描述: {}", request.getDescription());
log.info("类型: {}", request.getType());
log.info("封面URL: {}", request.getCoverUrl());
log.info("视频URL: {}", request.getVideoUrl());
log.info("图片URLs: {}", request.getImageUrls() != null ? request.getImageUrls().size() + " 张图片" : "无图片");
if (request.getImageUrls() != null) {
for (int i = 0; i < request.getImageUrls().size(); i++) {
log.info(" 图片 {}: {}", i+1, request.getImageUrls().get(i));
}
}
if (request.getId() == null) {
throw new CrmebException("作品ID不能为空");
}
@ -158,6 +173,13 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
throw new CrmebException("作品不存在");
}
log.info("原作品信息:");
log.info(" 原标题: {}", works.getTitle());
log.info(" 原描述: {}", works.getDescription());
log.info(" 原封面: {}", works.getCoverImage());
log.info(" 原图片: {}", works.getImages());
log.info(" 原视频: {}", works.getVideoUrl());
// 验证是否是作品作者
if (!works.getUid().equals(userId)) {
throw new CrmebException("无权限编辑此作品");
@ -188,11 +210,63 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
works.setStatus(request.getStatus());
}
// 更新媒体资源
if (request.getCoverUrl() != null) {
works.setCoverImage(request.getCoverUrl());
}
// 处理图片列表
if (request.getImageUrls() != null) {
if (request.getImageUrls().isEmpty()) {
works.setImages(null);
} else {
works.setImages(String.join(",", request.getImageUrls()));
}
}
// 处理视频URL
if (request.getVideoUrl() != null) {
works.setVideoUrl(request.getVideoUrl());
}
// 更新作品类型如果提供
if (request.getType() != null) {
works.setType(request.getType().toUpperCase());
}
// 更新位置信息
if (request.getLocation() != null) {
works.setLocation(request.getLocation());
}
// 更新可见范围
if (request.getVisibility() != null) {
String visibility = request.getVisibility().toUpperCase();
if (visibility.equals("PUBLIC") || visibility.equals("FRIENDS") || visibility.equals("PRIVATE")) {
works.setVisibility(visibility);
}
}
// 更新评论设置
if (request.getCommentSetting() != null) {
String commentSetting = request.getCommentSetting().toUpperCase();
if (commentSetting.equals("ALL") || commentSetting.equals("FRIENDS") || commentSetting.equals("DISABLED")) {
works.setCommentSetting(commentSetting);
}
}
boolean updated = updateById(works);
if (!updated) {
throw new CrmebException("更新作品失败");
}
log.info("=== 作品更新完成 ===");
log.info("更新后作品信息:");
log.info(" 新标题: {}", works.getTitle());
log.info(" 新描述: {}", works.getDescription());
log.info(" 新封面: {}", works.getCoverImage());
log.info(" 新图片: {}", works.getImages());
log.info(" 新视频: {}", works.getVideoUrl());
log.info("用户{}更新作品成功作品ID{}", userId, works.getId());
return true;
}

View File

@ -164,7 +164,8 @@ public class MyCollectionsActivity extends AppCompatActivity {
private void setupRecyclerView() {
adapter = new WorksAdapter(work -> {
if (work != null && getActivity() != null) {
WorkDetailActivity.start(getActivity(), String.valueOf(work.getId()));
// 从我的收藏进入显示编辑删除菜单如果是自己的作品
WorkDetailActivity.start(getActivity(), String.valueOf(work.getId()), true);
}
});
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));

View File

@ -164,7 +164,8 @@ public class MyLikesActivity extends AppCompatActivity {
private void setupRecyclerView() {
adapter = new WorksAdapter(work -> {
if (work != null && getActivity() != null) {
WorkDetailActivity.start(getActivity(), String.valueOf(work.getId()));
// 从我的点赞进入显示编辑删除菜单如果是自己的作品
WorkDetailActivity.start(getActivity(), String.valueOf(work.getId()), true);
}
});
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));

View File

@ -566,7 +566,8 @@ public class ProfileActivity extends AppCompatActivity {
// 设置我的作品区域
myWorksAdapter = new WorksAdapter(work -> {
if (work != null && work.getId() != null) {
WorkDetailActivity.start(this, String.valueOf(work.getId()));
// ""页面进入显示编辑删除菜单
WorkDetailActivity.start(this, String.valueOf(work.getId()), true);
}
});
binding.myWorksRecycler.setLayoutManager(new GridLayoutManager(this, 2));

View File

@ -91,6 +91,10 @@ public class PublishWorkActivity extends AppCompatActivity {
private TianDiTuLocationService locationService;
private ActivityResultLauncher<String[]> requestLocationPermissionLauncher;
// 编辑模式相关
private boolean isEditMode = false;
private String editWorkId = null;
public static void start(Context context) {
Intent intent = new Intent(context, PublishWorkActivity.class);
context.startActivity(intent);
@ -106,6 +110,17 @@ public class PublishWorkActivity extends AppCompatActivity {
return;
}
// 检查是否是编辑模式
isEditMode = getIntent().getBooleanExtra("edit_mode", false);
if (isEditMode) {
editWorkId = getIntent().getStringExtra("work_id");
if (TextUtils.isEmpty(editWorkId)) {
Toast.makeText(this, "作品ID不能为空", Toast.LENGTH_SHORT).show();
finish();
return;
}
}
binding = ActivityPublishWorkBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
@ -117,6 +132,65 @@ public class PublishWorkActivity extends AppCompatActivity {
setupLaunchers();
setupClickListeners();
loadCategories(); // 加载分类数据
// 如果是编辑模式预填充数据
if (isEditMode) {
loadEditData();
}
}
/**
* 加载编辑数据
*/
private void loadEditData() {
if (TextUtils.isEmpty(editWorkId)) {
return;
}
// 从Intent中获取作品数据
String workTitle = getIntent().getStringExtra("work_title");
String workDescription = getIntent().getStringExtra("work_description");
String workType = getIntent().getStringExtra("work_type");
String workCoverUrl = getIntent().getStringExtra("work_cover_url");
String workVideoUrl = getIntent().getStringExtra("work_video_url");
ArrayList<String> workImageUrls = getIntent().getStringArrayListExtra("work_image_urls");
// 填充标题和描述
if (!TextUtils.isEmpty(workTitle)) {
binding.titleEditText.setText(workTitle);
}
if (!TextUtils.isEmpty(workDescription)) {
binding.descriptionEditText.setText(workDescription);
}
// 设置作品类型和媒体
if ("VIDEO".equals(workType)) {
currentWorkType = WorkItem.WorkType.VIDEO;
if (!TextUtils.isEmpty(workVideoUrl)) {
selectedVideoUri = Uri.parse(workVideoUrl);
selectedMediaUris.clear();
selectedMediaUris.add(selectedVideoUri);
}
} else {
currentWorkType = WorkItem.WorkType.IMAGE;
if (workImageUrls != null && !workImageUrls.isEmpty()) {
selectedMediaUris.clear();
for (String url : workImageUrls) {
if (!TextUtils.isEmpty(url)) {
selectedMediaUris.add(Uri.parse(url));
}
}
}
}
// 设置封面
if (!TextUtils.isEmpty(workCoverUrl)) {
selectedCoverUri = Uri.parse(workCoverUrl);
}
// 更新UI显示
updateMediaDisplay();
updateCoverPreview();
}
private void setupToolbar() {
@ -124,6 +198,14 @@ public class PublishWorkActivity extends AppCompatActivity {
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
// 根据模式设置标题
if (isEditMode) {
getSupportActionBar().setTitle("编辑作品");
binding.publishButton.setText("保存");
} else {
getSupportActionBar().setTitle("发布作品");
binding.publishButton.setText("发布");
}
}
binding.toolbar.setNavigationOnClickListener(v -> finish());
}
@ -636,11 +718,29 @@ public class PublishWorkActivity extends AppCompatActivity {
}
private void publishWork() {
android.util.Log.d("PublishWork", "=== 开始发布/更新作品 ===");
android.util.Log.d("PublishWork", "编辑模式: " + isEditMode);
android.util.Log.d("PublishWork", "作品ID: " + editWorkId);
String title = binding.titleEditText.getText() != null
? binding.titleEditText.getText().toString().trim() : "";
String description = binding.descriptionEditText.getText() != null
? binding.descriptionEditText.getText().toString().trim() : "";
android.util.Log.d("PublishWork", "标题: " + title);
android.util.Log.d("PublishWork", "描述: " + description);
android.util.Log.d("PublishWork", "作品类型: " + currentWorkType);
android.util.Log.d("PublishWork", "媒体文件数量: " + selectedMediaUris.size());
for (int i = 0; i < selectedMediaUris.size(); i++) {
Uri uri = selectedMediaUris.get(i);
android.util.Log.d("PublishWork", "媒体文件 " + (i+1) + ": " + uri.toString());
android.util.Log.d("PublishWork", " -> 是否本地文件: " + isLocalUri(uri));
}
android.util.Log.d("PublishWork", "封面URI: " + (selectedCoverUri != null ? selectedCoverUri.toString() : "null"));
android.util.Log.d("PublishWork", "封面是否本地文件: " + (selectedCoverUri != null ? isLocalUri(selectedCoverUri) : "N/A"));
// 验证标题
if (TextUtils.isEmpty(title)) {
Toast.makeText(this, "请输入作品标题", Toast.LENGTH_SHORT).show();
@ -675,23 +775,38 @@ public class PublishWorkActivity extends AppCompatActivity {
// 显示加载对话框
android.app.ProgressDialog progressDialog = new android.app.ProgressDialog(this);
progressDialog.setMessage("正在发布作品...");
progressDialog.setMessage(isEditMode ? "正在更新作品..." : "正在发布作品...");
progressDialog.setCancelable(false);
progressDialog.show();
// 开始上传流程
if (currentWorkType == WorkItem.WorkType.VIDEO && selectedVideoUri != null) {
// 视频作品先上传封面再上传视频最后发布
uploadCoverImage(selectedCoverUri != null ? selectedCoverUri : selectedVideoUri,
new UploadCallback() {
@Override
public void onSuccess(String url) {
String coverUrl = url;
// 上传视频
// 视频作品处理
handleVideoWorkUpload(title, description, progressDialog);
} else {
// 图片作品处理
handleImageWorkUpload(title, description, progressDialog);
}
}
/**
* 处理视频作品上传
*/
private void handleVideoWorkUpload(String title, String description, android.app.ProgressDialog progressDialog) {
// 检查视频是否是本地文件需要上传还是网络URL编辑模式无需上传
boolean needUploadVideo = isLocalUri(selectedVideoUri);
boolean needUploadCover = selectedCoverUri != null && isLocalUri(selectedCoverUri);
if (needUploadCover) {
// 需要上传新封面
uploadCoverImage(selectedCoverUri, new UploadCallback() {
@Override
public void onSuccess(String coverUrl) {
if (needUploadVideo) {
// 需要上传新视频
uploadVideo(selectedVideoUri, new UploadCallback() {
@Override
public void onSuccess(String videoUrl) {
// 发布作品
publishWorkToServer(title, description, "VIDEO", coverUrl, videoUrl, null, progressDialog);
}
@ -701,36 +816,11 @@ public class PublishWorkActivity extends AppCompatActivity {
Toast.makeText(PublishWorkActivity.this, "视频上传失败: " + error, Toast.LENGTH_SHORT).show();
}
});
} else {
// 使用原有视频URL
String videoUrl = selectedVideoUri.toString();
publishWorkToServer(title, description, "VIDEO", coverUrl, videoUrl, null, progressDialog);
}
@Override
public void onFailure(String error) {
progressDialog.dismiss();
Toast.makeText(PublishWorkActivity.this, "封面上传失败: " + error, Toast.LENGTH_SHORT).show();
}
});
} else {
// 图片作品先上传封面再上传所有图片最后发布
Uri coverUri = selectedCoverUri != null ? selectedCoverUri :
(!selectedMediaUris.isEmpty() ? selectedMediaUris.get(0) : null);
uploadCoverImage(coverUri, new UploadCallback() {
@Override
public void onSuccess(String coverUrl) {
// 上传所有图片
uploadImages(selectedMediaUris, new UploadImagesCallback() {
@Override
public void onSuccess(List<String> imageUrls) {
// 发布作品
publishWorkToServer(title, description, "IMAGE", coverUrl, null, imageUrls, progressDialog);
}
@Override
public void onFailure(String error) {
progressDialog.dismiss();
Toast.makeText(PublishWorkActivity.this, "图片上传失败: " + error, Toast.LENGTH_SHORT).show();
}
});
}
@Override
@ -739,9 +829,141 @@ public class PublishWorkActivity extends AppCompatActivity {
Toast.makeText(PublishWorkActivity.this, "封面上传失败: " + error, Toast.LENGTH_SHORT).show();
}
});
} else if (needUploadVideo) {
// 只需要上传视频使用原有封面
String coverUrl = selectedCoverUri != null ? selectedCoverUri.toString() : selectedVideoUri.toString();
uploadVideo(selectedVideoUri, new UploadCallback() {
@Override
public void onSuccess(String videoUrl) {
publishWorkToServer(title, description, "VIDEO", coverUrl, videoUrl, null, progressDialog);
}
@Override
public void onFailure(String error) {
progressDialog.dismiss();
Toast.makeText(PublishWorkActivity.this, "视频上传失败: " + error, Toast.LENGTH_SHORT).show();
}
});
} else {
// 都不需要上传直接使用原有URL
String coverUrl = selectedCoverUri != null ? selectedCoverUri.toString() : selectedVideoUri.toString();
String videoUrl = selectedVideoUri.toString();
publishWorkToServer(title, description, "VIDEO", coverUrl, videoUrl, null, progressDialog);
}
}
/**
* 处理图片作品上传
*/
private void handleImageWorkUpload(String title, String description, android.app.ProgressDialog progressDialog) {
android.util.Log.d("PublishWork", "=== 处理图片作品上传 ===");
android.util.Log.d("PublishWork", "selectedMediaUris数量: " + selectedMediaUris.size());
// 分离本地文件和网络URL
List<Uri> localImageUris = new ArrayList<>();
List<String> existingImageUrls = new ArrayList<>();
for (int i = 0; i < selectedMediaUris.size(); i++) {
Uri uri = selectedMediaUris.get(i);
android.util.Log.d("PublishWork", "图片" + i + ": " + uri.toString());
if (isLocalUri(uri)) {
localImageUris.add(uri);
android.util.Log.d("PublishWork", " -> 本地文件,需要上传");
} else {
existingImageUrls.add(uri.toString());
android.util.Log.d("PublishWork", " -> 网络URL保留原有");
}
}
android.util.Log.d("PublishWork", "本地图片数量: " + localImageUris.size());
android.util.Log.d("PublishWork", "原有图片数量: " + existingImageUrls.size());
boolean needUploadCover = selectedCoverUri != null && isLocalUri(selectedCoverUri);
android.util.Log.d("PublishWork", "需要上传封面: " + needUploadCover);
Uri coverUri = selectedCoverUri != null ? selectedCoverUri :
(!selectedMediaUris.isEmpty() ? selectedMediaUris.get(0) : null);
if (needUploadCover) {
android.util.Log.d("PublishWork", "开始上传新封面...");
// 需要上传新封面
uploadCoverImage(selectedCoverUri, new UploadCallback() {
@Override
public void onSuccess(String coverUrl) {
android.util.Log.d("PublishWork", "封面上传成功: " + coverUrl);
if (!localImageUris.isEmpty()) {
android.util.Log.d("PublishWork", "开始上传新图片...");
// 需要上传新图片
uploadImages(localImageUris, new UploadImagesCallback() {
@Override
public void onSuccess(List<String> newImageUrls) {
android.util.Log.d("PublishWork", "新图片上传成功,数量: " + newImageUrls.size());
// 合并新上传的图片URL和原有的图片URL
List<String> allImageUrls = new ArrayList<>(existingImageUrls);
allImageUrls.addAll(newImageUrls);
android.util.Log.d("PublishWork", "合并后图片总数: " + allImageUrls.size());
publishWorkToServer(title, description, "IMAGE", coverUrl, null, allImageUrls, progressDialog);
}
@Override
public void onFailure(String error) {
android.util.Log.e("PublishWork", "图片上传失败: " + error);
progressDialog.dismiss();
Toast.makeText(PublishWorkActivity.this, "图片上传失败: " + error, Toast.LENGTH_SHORT).show();
}
});
} else {
android.util.Log.d("PublishWork", "无新图片需要上传,使用原有图片");
// 只使用原有图片
publishWorkToServer(title, description, "IMAGE", coverUrl, null, existingImageUrls, progressDialog);
}
}
@Override
public void onFailure(String error) {
android.util.Log.e("PublishWork", "封面上传失败: " + error);
progressDialog.dismiss();
Toast.makeText(PublishWorkActivity.this, "封面上传失败: " + error, Toast.LENGTH_SHORT).show();
}
});
} else if (!localImageUris.isEmpty()) {
android.util.Log.d("PublishWork", "无需上传封面,开始上传新图片...");
// 只需要上传新图片使用原有封面
String coverUrl = coverUri != null ? coverUri.toString() : "";
uploadImages(localImageUris, new UploadImagesCallback() {
@Override
public void onSuccess(List<String> newImageUrls) {
android.util.Log.d("PublishWork", "新图片上传成功,数量: " + newImageUrls.size());
List<String> allImageUrls = new ArrayList<>(existingImageUrls);
allImageUrls.addAll(newImageUrls);
android.util.Log.d("PublishWork", "合并后图片总数: " + allImageUrls.size());
publishWorkToServer(title, description, "IMAGE", coverUrl, null, allImageUrls, progressDialog);
}
@Override
public void onFailure(String error) {
android.util.Log.e("PublishWork", "图片上传失败: " + error);
progressDialog.dismiss();
Toast.makeText(PublishWorkActivity.this, "图片上传失败: " + error, Toast.LENGTH_SHORT).show();
}
});
} else {
android.util.Log.d("PublishWork", "无新文件需要上传直接使用原有URL");
// 都不需要上传直接使用原有URL
String coverUrl = coverUri != null ? coverUri.toString() : "";
publishWorkToServer(title, description, "IMAGE", coverUrl, null, existingImageUrls, progressDialog);
}
}
/**
* 判断URI是否是本地文件
*/
private boolean isLocalUri(Uri uri) {
if (uri == null) return false;
String scheme = uri.getScheme();
return "file".equals(scheme) || "content".equals(scheme);
}
/**
* 上传封面图片
*/
@ -936,12 +1158,24 @@ public class PublishWorkActivity extends AppCompatActivity {
}
/**
* 发布作品到服务器
* 发布或更新作品到服务器
*/
private void publishWorkToServer(String title, String description, String type,
String coverUrl, String videoUrl, List<String> imageUrls,
android.app.ProgressDialog progressDialog) {
WorksRequest request = new WorksRequest();
// 如果是编辑模式设置作品ID
if (isEditMode && !TextUtils.isEmpty(editWorkId)) {
try {
request.setId(Long.parseLong(editWorkId));
} catch (NumberFormatException e) {
progressDialog.dismiss();
Toast.makeText(this, "作品ID格式错误", Toast.LENGTH_SHORT).show();
return;
}
}
request.setTitle(title);
request.setDescription(description);
request.setType(type);
@ -979,34 +1213,70 @@ public class PublishWorkActivity extends AppCompatActivity {
}
ApiService apiService = ApiClient.getService(this);
Call<ApiResponse<Long>> call = apiService.publishWork(request);
call.enqueue(new retrofit2.Callback<ApiResponse<Long>>() {
@Override
public void onResponse(Call<ApiResponse<Long>> call, retrofit2.Response<ApiResponse<Long>> response) {
progressDialog.dismiss();
if (isEditMode) {
// 编辑模式调用更新API
Call<ApiResponse<Boolean>> updateCall = apiService.updateWork(request);
updateCall.enqueue(new retrofit2.Callback<ApiResponse<Boolean>>() {
@Override
public void onResponse(Call<ApiResponse<Boolean>> call, retrofit2.Response<ApiResponse<Boolean>> response) {
progressDialog.dismiss();
if (response.isSuccessful() && response.body() != null) {
ApiResponse<Long> apiResponse = response.body();
if (apiResponse.getCode() == 200) {
Toast.makeText(PublishWorkActivity.this, "发布成功", Toast.LENGTH_SHORT).show();
finish();
if (response.isSuccessful() && response.body() != null) {
ApiResponse<Boolean> apiResponse = response.body();
if (apiResponse.getCode() == 200) {
Toast.makeText(PublishWorkActivity.this, "更新成功", Toast.LENGTH_SHORT).show();
// 设置结果并返回
setResult(RESULT_OK);
finish();
} else {
String errorMessage = apiResponse.getMessage() != null ? apiResponse.getMessage() : "更新失败";
Toast.makeText(PublishWorkActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(PublishWorkActivity.this,
apiResponse.getMessage() != null ? apiResponse.getMessage() : "发布失败",
Toast.LENGTH_SHORT).show();
Toast.makeText(PublishWorkActivity.this, "更新失败", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(PublishWorkActivity.this, "发布失败", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<Long>> call, Throwable t) {
progressDialog.dismiss();
Toast.makeText(PublishWorkActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
@Override
public void onFailure(Call<ApiResponse<Boolean>> call, Throwable t) {
progressDialog.dismiss();
Toast.makeText(PublishWorkActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
} else {
// 发布模式调用发布API
Call<ApiResponse<Long>> publishCall = apiService.publishWork(request);
publishCall.enqueue(new retrofit2.Callback<ApiResponse<Long>>() {
@Override
public void onResponse(Call<ApiResponse<Long>> call, retrofit2.Response<ApiResponse<Long>> response) {
progressDialog.dismiss();
if (response.isSuccessful() && response.body() != null) {
ApiResponse<Long> apiResponse = response.body();
if (apiResponse.getCode() == 200) {
Toast.makeText(PublishWorkActivity.this, "发布成功", Toast.LENGTH_SHORT).show();
// 设置结果并返回
setResult(RESULT_OK);
finish();
} else {
String errorMessage = apiResponse.getMessage() != null ? apiResponse.getMessage() : "发布失败";
Toast.makeText(PublishWorkActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(PublishWorkActivity.this, "发布失败", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<Long>> call, Throwable t) {
progressDialog.dismiss();
Toast.makeText(PublishWorkActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
/**
@ -1598,4 +1868,5 @@ public class PublishWorkActivity extends AppCompatActivity {
} else {
binding.categorySpinner.setText(selectedCategory.getName());
}
}}
}
}

View File

@ -440,26 +440,16 @@ public class RoomDetailActivity extends AppCompatActivity implements SurfaceHold
// 加载点赞数
loadLikeCount();
// 点赞按钮点击事件
// 点赞按钮点击事件 - TRTC风格动画
likeButton.setOnClickListener(v -> {
if (!AuthHelper.isLoggedIn(this)) {
Toast.makeText(this, "请先登录", Toast.LENGTH_SHORT).show();
return;
}
// 点赞动画
likeButton.animate()
.scaleX(1.3f)
.scaleY(1.3f)
.setDuration(100)
.withEndAction(() -> {
likeButton.animate()
.scaleX(1.0f)
.scaleY(1.0f)
.setDuration(100)
.start();
})
.start();
// TRTC风格点赞动画
android.view.animation.Animation likeAnim = android.view.animation.AnimationUtils.loadAnimation(this, R.anim.trtc_like_animation);
likeButton.startAnimation(likeAnim);
// 调用点赞API
likeRoom();

View File

@ -49,10 +49,16 @@ public class WorkDetailActivity extends AppCompatActivity {
private int commentCount = 0;
private static final String EXTRA_WORK_ID = "work_id";
private static final String EXTRA_SHOW_MENU = "show_menu";
public static void start(Context context, String workId) {
start(context, workId, false);
}
public static void start(Context context, String workId, boolean showMenu) {
Intent intent = new Intent(context, WorkDetailActivity.class);
intent.putExtra(EXTRA_WORK_ID, workId);
intent.putExtra(EXTRA_SHOW_MENU, showMenu);
context.startActivity(intent);
}
@ -63,12 +69,21 @@ public class WorkDetailActivity extends AppCompatActivity {
setContentView(binding.getRoot());
String workId = getIntent().getStringExtra(EXTRA_WORK_ID);
boolean showMenu = getIntent().getBooleanExtra(EXTRA_SHOW_MENU, false);
if (TextUtils.isEmpty(workId)) {
Toast.makeText(this, "作品ID不能为空", Toast.LENGTH_SHORT).show();
finish();
return;
}
// 根据showMenu参数控制菜单显示
if (showMenu) {
binding.actionButton.setVisibility(View.VISIBLE);
} else {
binding.actionButton.setVisibility(View.GONE);
}
// 加载作品详情
loadWorkDetail(workId);
}
@ -968,39 +983,36 @@ public class WorkDetailActivity extends AppCompatActivity {
}
private void setupActionButton() {
// ============================================
// TODO: 判断是否是当前用户的作品
// ============================================
// 判断方式推荐方式3:
// 1. 从WorkItem中获取userId字段与当前登录用户ID比较需要从AuthStore获取当前用户ID
// 2. 或调用接口: GET /api/works/{workId}/isOwner
// 3. 或从作品详情接口返回的WorkItem中包含isOwner字段推荐已在获取详情时返回
//
// 如果使用方式3推荐:
// - 在获取作品详情时后端已经返回isOwner字段
// - 直接使用 workItem.getIsOwner() 或从详情接口返回的isOwner字段
// - 如果isOwner为true显示编辑/删除按钮
// - 如果isOwner为false或用户未登录隐藏编辑/删除按钮
//
// 如果使用方式1:
// - 需要从AuthStore获取当前登录用户的userId
// - 比较 workItem.getUserId() 与当前用户ID
// - 如果相同显示编辑/删除按钮否则隐藏
//
// 如果使用方式2:
// 接口路径: GET /api/works/{workId}/isOwner
// 请求方法: GET
// 请求头:
// - Authorization: Bearer {token} (必填需要登录)
// 路径参数:
// - workId: String (必填) - 作品ID
// 返回数据格式: ApiResponse<IsOwnerResponse>
// 后端返回: { "code": 200, "data": { "isOwner": true/false } }
//
// 目前简化处理所有作品都显示操作按钮
// TODO: 根据isOwner字段控制按钮显示/隐藏
// 检查是否应该显示菜单
boolean showMenu = getIntent().getBooleanExtra(EXTRA_SHOW_MENU, false);
if (!showMenu) {
binding.actionButton.setVisibility(View.GONE);
return;
}
// 新布局中actionButton是ImageView不是FloatingActionButton添加防抖
// 检查是否是当前用户的作品
String currentUserIdStr = AuthStore.getUserId(this);
if (currentUserIdStr == null || workItem == null) {
binding.actionButton.setVisibility(View.GONE);
return;
}
try {
int currentUserId = Integer.parseInt(currentUserIdStr);
int authorId = workItem.getAuthorId();
if (currentUserId != authorId) {
// 不是自己的作品隐藏菜单
binding.actionButton.setVisibility(View.GONE);
return;
}
} catch (NumberFormatException e) {
binding.actionButton.setVisibility(View.GONE);
return;
}
// 是自己的作品且需要显示菜单设置点击事件
binding.actionButton.setVisibility(View.VISIBLE);
binding.actionButton.setOnClickListener(new DebounceClickListener() {
@Override
public void onDebouncedClick(View v) {
@ -1010,10 +1022,21 @@ public class WorkDetailActivity extends AppCompatActivity {
}
private void showActionMenu() {
// TODO: 判断是否是当前用户的作品
boolean isOwner = true; // 简化处理
// 检查是否是当前用户的作品
String currentUserIdStr = AuthStore.getUserId(this);
if (currentUserIdStr == null || workItem == null) {
return;
}
if (!isOwner) {
try {
int currentUserId = Integer.parseInt(currentUserIdStr);
int authorId = workItem.getAuthorId();
if (currentUserId != authorId) {
Toast.makeText(this, "只能编辑自己的作品", Toast.LENGTH_SHORT).show();
return;
}
} catch (NumberFormatException e) {
return;
}
@ -1032,58 +1055,32 @@ public class WorkDetailActivity extends AppCompatActivity {
}
private void editWork() {
// ============================================
// TODO: 实现编辑作品功能
// ============================================
// 方案1: 跳转到编辑页面PublishWorkActivity传入workItem数据
// 方案2: 在当前页面弹出编辑对话框
//
// 如果使用方案1推荐:
// 1. 创建EditWorkActivity或复用PublishWorkActivity
// 2. 传入workItem数据通过Intent传递
// 3. 在编辑页面预填充作品信息标题描述图片/视频
// 4. 用户修改后调用更新接口保存
//
// 更新作品接口:
// 接口路径: PUT /api/works/{workId}
// 请求方法: PUT
// 请求头:
// - Authorization: Bearer {token} (必填需要登录)
// 路径参数:
// - workId: String (必填) - 作品ID
// 请求体 (multipart/form-data JSON):
// - title: String (可选) - 作品标题
// - description: String (可选) - 作品描述
// - cover: File (可选) - 新的封面图片如果要更换封面
// - video: File (可选) - 新的视频文件如果要更换视频仅视频作品
// - images: File[] (可选) - 新的图片文件数组如果要更换图片仅图片作品
// - deletedImageIds: String[] (可选) - 要删除的图片ID列表图片作品
// 返回数据格式: ApiResponse<WorkItem>
//
// 前端需要传入的参数:
// - workId: String (从workItem.getId()获取)
// - title: String (用户修改后的标题)
// - description: String (用户修改后的描述可选)
// - cover: File (如果要更换封面)
// - video/images: File/File[] (如果要更换媒体文件)
// - token: String (必填从AuthStore获取)
//
// 实现步骤:
// 1. 跳转到编辑页面传入workItem
// 2. 在编辑页面加载作品数据并预填充表单
// 3. 用户修改内容后点击保存
// 4. 验证输入标题不能为空等
// 5. 如果有新上传的文件先上传文件获取URL
// 6. 调用 PUT /api/works/{workId} 更新作品信息
// 7. 更新成功后刷新当前页面或返回并刷新列表
// 8. 处理错误情况未登录无权限文件上传失败等
//
// 注意:
// - 只有作品作者才能编辑
// - 如果更换媒体文件需要先上传新文件
// - 图片作品可以删除部分图片需要传递deletedImageIds
// 检查登录状态
if (!AuthHelper.requireLogin(this, "编辑作品需要登录")) {
return;
}
Toast.makeText(this, "编辑功能待实现", Toast.LENGTH_SHORT).show();
if (workItem == null) {
Toast.makeText(this, "作品信息不完整", Toast.LENGTH_SHORT).show();
return;
}
// 跳转到编辑页面复用PublishWorkActivity
Intent intent = new Intent(this, PublishWorkActivity.class);
intent.putExtra("edit_mode", true);
intent.putExtra("work_id", workItem.getId());
intent.putExtra("work_title", workItem.getTitle());
intent.putExtra("work_description", workItem.getDescription());
intent.putExtra("work_type", workItem.getType().name());
intent.putExtra("work_cover_url", workItem.getCoverUrl());
if (workItem.getType() == WorkItem.WorkType.VIDEO) {
intent.putExtra("work_video_url", workItem.getVideoUrl());
} else if (workItem.getImageUrls() != null) {
intent.putStringArrayListExtra("work_image_urls", new ArrayList<>(workItem.getImageUrls()));
}
startActivityForResult(intent, 1001);
}
private void deleteWork() {
@ -1312,5 +1309,18 @@ public class WorkDetailActivity extends AppCompatActivity {
return item;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1001 && resultCode == RESULT_OK) {
// 编辑成功重新加载作品详情
String workId = getIntent().getStringExtra(EXTRA_WORK_ID);
if (!TextUtils.isEmpty(workId)) {
loadWorkDetail(workId);
Toast.makeText(this, "作品更新成功", Toast.LENGTH_SHORT).show();
}
}
}
}

View File

@ -374,6 +374,9 @@ public interface ApiService {
@POST("api/front/works/publish")
Call<ApiResponse<Long>> publishWork(@Body WorksRequest body);
@POST("api/front/works/update")
Call<ApiResponse<Boolean>> updateWork(@Body WorksRequest body);
@GET("api/front/works/detail/{id}")
Call<ApiResponse<WorksResponse>> getWorkDetail(@Path("id") long id);
@ -394,7 +397,7 @@ public interface ApiService {
@Query("page") int page,
@Query("pageSize") int pageSize);
@DELETE("api/front/works/delete/{id}")
@POST("api/front/works/delete/{id}")
Call<ApiResponse<Boolean>> deleteWork(@Path("id") long id);
@POST("api/front/works/like/{id}")

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:duration="100"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="0.95"
android:toYScale="0.95" />
<scale
android:duration="100"
android:fromXScale="0.95"
android:fromYScale="0.95"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="100"
android:toXScale="1.0"
android:toYScale="1.0" />
</set>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:duration="150"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.3"
android:toYScale="1.3" />
<scale
android:duration="150"
android:fromXScale="1.3"
android:fromYScale="1.3"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="150"
android:toXScale="1.0"
android:toYScale="1.0" />
<alpha
android:duration="300"
android:fromAlpha="1.0"
android:toAlpha="0.8" />
<alpha
android:duration="200"
android:fromAlpha="0.8"
android:startOffset="300"
android:toAlpha="1.0" />
</set>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/trtc_accent"/>
<corners android:radius="22dp"/>
<stroke android:width="1dp" android:color="@color/trtc_accent_light"/>
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/trtc_overlay_dark"/>
<corners android:radius="22dp"/>
<stroke android:width="1dp" android:color="@color/trtc_overlay_light"/>
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/trtc_overlay_dark"/>
<corners android:radius="16dp"/>
<stroke android:width="1dp" android:color="@color/trtc_overlay_light"/>
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/trtc_overlay_light"/>
<corners android:radius="20dp"/>
<stroke android:width="1dp" android:color="@color/trtc_overlay_light"/>
</shape>

View File

@ -150,32 +150,32 @@
android:src="@drawable/ic_arrow_back_24"
app:tint="@android:color/white" />
<!-- 主播信息卡片 - 放在顶部栏内 -->
<!-- 主播信息卡片 - TRTC风格 -->
<LinearLayout
android:id="@+id/roomInfoLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:background="@drawable/bg_rounded_semi_transparent"
android:background="@drawable/bg_trtc_info_card"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="8dp"
android:paddingVertical="6dp">
android:paddingHorizontal="10dp"
android:paddingVertical="8dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/streamerAvatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@drawable/ic_person_24"
app:civ_border_color="#FFFFFF"
app:civ_border_width="1dp" />
app:civ_border_color="@color/trtc_accent"
app:civ_border_width="2dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:layout_marginStart="10dp"
android:orientation="vertical">
<TextView
@ -183,8 +183,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主播"
android:textColor="@android:color/white"
android:textSize="13sp"
android:textColor="@color/trtc_text_primary"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
@ -195,68 +195,70 @@
android:ellipsize="end"
android:maxLines="1"
android:text="直播间"
android:textColor="#CCFFFFFF"
android:textSize="11sp" />
android:textColor="@color/trtc_text_secondary"
android:textSize="12sp" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/followButton"
style="@style/Widget.Material3.Button.TonalButton"
style="@style/Widget.Material3.Button"
android:layout_width="wrap_content"
android:layout_height="26dp"
android:layout_marginStart="4dp"
android:minWidth="48dp"
android:paddingHorizontal="10dp"
android:layout_height="32dp"
android:layout_marginStart="6dp"
android:minWidth="56dp"
android:paddingHorizontal="12dp"
android:text="关注"
android:textColor="@android:color/white"
android:textSize="11sp"
android:textColor="@color/trtc_text_primary"
android:textSize="12sp"
android:textAllCaps="false"
app:backgroundTint="#FF4081"
app:cornerRadius="13dp" />
app:backgroundTint="@color/trtc_accent"
app:cornerRadius="16dp" />
</LinearLayout>
<!-- 直播状态标签 -->
<!-- 直播状态标签 - TRTC风格 -->
<TextView
android:id="@+id/liveTag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:background="@drawable/live_badge_background"
android:paddingHorizontal="8dp"
android:paddingVertical="3dp"
android:text="● 直播中"
android:textColor="@android:color/white"
android:textSize="11sp"
android:background="@drawable/bg_trtc_button_primary"
android:paddingHorizontal="10dp"
android:paddingVertical="4dp"
android:text="● LIVE"
android:textColor="@color/trtc_text_primary"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="visible" />
<!-- 观看人数 -->
<!-- 观看人数 - TRTC风格 -->
<LinearLayout
android:id="@+id/topViewerLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:background="@drawable/bg_rounded_semi_transparent"
android:background="@drawable/bg_trtc_button_secondary"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp">
android:paddingHorizontal="10dp"
android:paddingVertical="6dp">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="4dp"
android:src="@drawable/ic_people_24"
app:tint="@android:color/white" />
app:tint="@color/trtc_text_primary" />
<TextView
android:id="@+id/topViewerCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textColor="@android:color/white"
android:textSize="12sp" />
android:textColor="@color/trtc_text_primary"
android:textSize="13sp"
android:textStyle="bold" />
</LinearLayout>
@ -296,7 +298,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- 右侧悬浮功能按钮 -->
<!-- 右侧悬浮功能按钮 - TRTC风格 -->
<LinearLayout
android:id="@+id/rightButtons"
android:layout_width="wrap_content"
@ -308,60 +310,61 @@
app:layout_constraintBottom_toTopOf="@id/chatInputLayout"
app:layout_constraintEnd_toEndOf="parent">
<!-- 点赞按钮 -->
<!-- 点赞按钮 - TRTC风格 -->
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
android:layout_marginBottom="16dp">
<ImageButton
android:id="@+id/likeButton"
android:layout_width="44dp"
android:layout_height="44dp"
android:background="@drawable/bg_circle_semi_transparent"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_trtc_button_secondary"
android:contentDescription="点赞"
android:src="@drawable/ic_like_24"
android:tint="#FF4081" />
android:tint="@color/trtc_accent" />
<TextView
android:id="@+id/likeCountText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="-2dp"
android:background="@drawable/bg_purple_20"
android:paddingHorizontal="5dp"
android:paddingVertical="1dp"
android:layout_marginBottom="-4dp"
android:background="@drawable/bg_trtc_button_primary"
android:paddingHorizontal="6dp"
android:paddingVertical="2dp"
android:text="0"
android:textColor="#FFFFFF"
android:textSize="9sp" />
android:textColor="@color/trtc_text_primary"
android:textSize="10sp"
android:textStyle="bold" />
</FrameLayout>
<!-- 礼物按钮 -->
<!-- 礼物按钮 - TRTC风格 -->
<ImageButton
android:id="@+id/giftButton"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginBottom="12dp"
android:background="@drawable/bg_circle_semi_transparent"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="16dp"
android:background="@drawable/bg_trtc_button_secondary"
android:contentDescription="送礼物"
android:src="@drawable/ic_gift_24"
android:tint="#FF9800" />
android:tint="@color/trtc_secondary" />
<!-- 分享按钮 -->
<!-- 分享按钮 - TRTC风格 -->
<ImageButton
android:id="@+id/shareButton"
android:layout_width="44dp"
android:layout_height="44dp"
android:background="@drawable/bg_circle_semi_transparent"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_trtc_button_secondary"
android:contentDescription="分享"
android:src="@drawable/ic_share_24"
android:tint="@android:color/white" />
android:tint="@color/trtc_text_primary" />
</LinearLayout>
<!-- 底部输入栏 - 悬浮 -->
<!-- 底部输入栏 - TRTC风格 -->
<LinearLayout
android:id="@+id/chatInputLayout"
android:layout_width="0dp"
@ -369,7 +372,7 @@
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="12dp"
android:paddingVertical="10dp"
android:paddingVertical="12dp"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -378,27 +381,29 @@
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/chatInput"
android:layout_width="0dp"
android:layout_height="38dp"
android:layout_height="42dp"
android:layout_weight="1"
android:background="@drawable/bg_chat_input_dark"
android:background="@drawable/bg_trtc_input"
android:hint="说点什么..."
android:textColorHint="#80FFFFFF"
android:textColorHint="@color/trtc_text_hint"
android:imeOptions="actionSend"
android:inputType="text"
android:maxLines="1"
android:paddingHorizontal="14dp"
android:textColor="@android:color/white"
android:textSize="13sp" />
android:paddingHorizontal="16dp"
android:textColor="@color/trtc_text_primary"
android:textSize="14sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sendButton"
android:layout_width="wrap_content"
android:layout_height="38dp"
android:layout_marginStart="8dp"
android:layout_height="42dp"
android:layout_marginStart="10dp"
android:minWidth="64dp"
android:text="发送"
android:textSize="12sp"
app:backgroundTint="#FF4081"
app:cornerRadius="19dp" />
android:textSize="13sp"
android:textStyle="bold"
app:backgroundTint="@color/trtc_accent"
app:cornerRadius="21dp" />
</LinearLayout>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- TRTC风格颜色主题 -->
<color name="trtc_primary">#FF6B35</color>
<color name="trtc_primary_dark">#E55A2B</color>
<color name="trtc_secondary">#4ECDC4</color>
<color name="trtc_accent">#FF4081</color>
<color name="trtc_accent_light">#FF6FA3</color>
<!-- 背景色 -->
<color name="trtc_background">#1A1A1A</color>
<color name="trtc_surface">#2D2D2D</color>
<color name="trtc_surface_light">#3D3D3D</color>
<!-- 文字颜色 -->
<color name="trtc_text_primary">#FFFFFF</color>
<color name="trtc_text_secondary">#CCFFFFFF</color>
<color name="trtc_text_hint">#80FFFFFF</color>
<!-- 半透明背景 -->
<color name="trtc_overlay_dark">#80000000</color>
<color name="trtc_overlay_light">#40FFFFFF</color>
<color name="trtc_overlay_accent">#40FF4081</color>
<!-- 状态颜色 -->
<color name="trtc_live_indicator">#FF4444</color>
<color name="trtc_online_indicator">#4CAF50</color>
<color name="trtc_offline_indicator">#999999</color>
</resources>