From cc667a76439e81c354bc4701c63f53e14f678024 Mon Sep 17 00:00:00 2001 From: xiao12feng8 <16507319+xiao12feng8@user.noreply.gitee.com> Date: Wed, 7 Jan 2026 18:34:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=9A=E4=BD=9C=E5=93=81?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=92=8C=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/WorksServiceImpl.java | 74 ++++ .../livestreaming/MyCollectionsActivity.java | 3 +- .../livestreaming/MyLikesActivity.java | 3 +- .../livestreaming/ProfileActivity.java | 3 +- .../livestreaming/PublishWorkActivity.java | 399 +++++++++++++++--- .../livestreaming/RoomDetailActivity.java | 18 +- .../livestreaming/WorkDetailActivity.java | 186 ++++---- .../example/livestreaming/net/ApiService.java | 5 +- .../src/main/res/anim/trtc_button_click.xml | 20 + .../src/main/res/anim/trtc_like_animation.xml | 29 ++ .../res/drawable/bg_trtc_button_primary.xml | 6 + .../res/drawable/bg_trtc_button_secondary.xml | 6 + .../main/res/drawable/bg_trtc_info_card.xml | 6 + .../src/main/res/drawable/bg_trtc_input.xml | 6 + .../res/layout/activity_room_detail_new.xml | 155 +++---- .../app/src/main/res/values/colors_trtc.xml | 29 ++ 16 files changed, 703 insertions(+), 245 deletions(-) create mode 100644 android-app/app/src/main/res/anim/trtc_button_click.xml create mode 100644 android-app/app/src/main/res/anim/trtc_like_animation.xml create mode 100644 android-app/app/src/main/res/drawable/bg_trtc_button_primary.xml create mode 100644 android-app/app/src/main/res/drawable/bg_trtc_button_secondary.xml create mode 100644 android-app/app/src/main/res/drawable/bg_trtc_info_card.xml create mode 100644 android-app/app/src/main/res/drawable/bg_trtc_input.xml create mode 100644 android-app/app/src/main/res/values/colors_trtc.xml diff --git a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/WorksServiceImpl.java b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/WorksServiceImpl.java index bd0e2c1b..b93b0b5a 100644 --- a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/WorksServiceImpl.java +++ b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/WorksServiceImpl.java @@ -148,6 +148,21 @@ public class WorksServiceImpl extends ServiceImpl 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不能为空"); } @@ -157,6 +172,13 @@ public class WorksServiceImpl extends ServiceImpl implements Wo if (works == null || works.getIsDeleted() == 1) { 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)) { @@ -187,12 +209,64 @@ public class WorksServiceImpl extends ServiceImpl implements Wo if (request.getStatus() != null) { 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; } diff --git a/android-app/app/src/main/java/com/example/livestreaming/MyCollectionsActivity.java b/android-app/app/src/main/java/com/example/livestreaming/MyCollectionsActivity.java index e857aecf..2ebbad4d 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/MyCollectionsActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/MyCollectionsActivity.java @@ -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)); diff --git a/android-app/app/src/main/java/com/example/livestreaming/MyLikesActivity.java b/android-app/app/src/main/java/com/example/livestreaming/MyLikesActivity.java index 8bc64733..00aa8f65 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/MyLikesActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/MyLikesActivity.java @@ -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)); diff --git a/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java b/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java index 5e0a46f5..2bd6a88b 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java @@ -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)); diff --git a/android-app/app/src/main/java/com/example/livestreaming/PublishWorkActivity.java b/android-app/app/src/main/java/com/example/livestreaming/PublishWorkActivity.java index eb17a864..753482ee 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/PublishWorkActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/PublishWorkActivity.java @@ -90,6 +90,10 @@ public class PublishWorkActivity extends AppCompatActivity { // 天地图定位服务继续 private TianDiTuLocationService locationService; private ActivityResultLauncher requestLocationPermissionLauncher; + + // 编辑模式相关 + private boolean isEditMode = false; + private String editWorkId = null; public static void start(Context context) { Intent intent = new Intent(context, PublishWorkActivity.class); @@ -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 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 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,8 +829,140 @@ 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 localImageUris = new ArrayList<>(); + List 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 newImageUrls) { + android.util.Log.d("PublishWork", "新图片上传成功,数量: " + newImageUrls.size()); + // 合并新上传的图片URL和原有的图片URL + List 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 newImageUrls) { + android.util.Log.d("PublishWork", "新图片上传成功,数量: " + newImageUrls.size()); + List 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 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> call = apiService.publishWork(request); - - call.enqueue(new retrofit2.Callback>() { - @Override - public void onResponse(Call> call, retrofit2.Response> response) { - progressDialog.dismiss(); - - if (response.isSuccessful() && response.body() != null) { - ApiResponse apiResponse = response.body(); - if (apiResponse.getCode() == 200) { - Toast.makeText(PublishWorkActivity.this, "发布成功", Toast.LENGTH_SHORT).show(); - finish(); + + if (isEditMode) { + // 编辑模式:调用更新API + Call> updateCall = apiService.updateWork(request); + updateCall.enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(Call> call, retrofit2.Response> response) { + progressDialog.dismiss(); + + if (response.isSuccessful() && response.body() != null) { + ApiResponse 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> call, Throwable t) { - progressDialog.dismiss(); - Toast.makeText(PublishWorkActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show(); - } - }); + @Override + public void onFailure(Call> call, Throwable t) { + progressDialog.dismiss(); + Toast.makeText(PublishWorkActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + } else { + // 发布模式:调用发布API + Call> publishCall = apiService.publishWork(request); + publishCall.enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(Call> call, retrofit2.Response> response) { + progressDialog.dismiss(); + + if (response.isSuccessful() && response.body() != null) { + ApiResponse 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> 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()); } - }} + } +} diff --git a/android-app/app/src/main/java/com/example/livestreaming/RoomDetailActivity.java b/android-app/app/src/main/java/com/example/livestreaming/RoomDetailActivity.java index 5d22ec6d..1f114cca 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/RoomDetailActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/RoomDetailActivity.java @@ -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(); diff --git a/android-app/app/src/main/java/com/example/livestreaming/WorkDetailActivity.java b/android-app/app/src/main/java/com/example/livestreaming/WorkDetailActivity.java index b956bc01..fa120a80 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/WorkDetailActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/WorkDetailActivity.java @@ -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 - // 后端返回: { "code": 200, "data": { "isOwner": true/false } } - // - // 目前简化处理,所有作品都显示操作按钮 - // TODO: 根据isOwner字段控制按钮显示/隐藏 - - // 新布局中actionButton是ImageView,不是FloatingActionButton(添加防抖) + // 检查是否应该显示菜单 + boolean showMenu = getIntent().getBooleanExtra(EXTRA_SHOW_MENU, false); + if (!showMenu) { + binding.actionButton.setVisibility(View.GONE); + return; + } + + // 检查是否是当前用户的作品 + 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; // 简化处理 - - if (!isOwner) { + // 检查是否是当前用户的作品 + String currentUserIdStr = AuthStore.getUserId(this); + if (currentUserIdStr == null || workItem == null) { + return; + } + + 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 - // - // 前端需要传入的参数: - // - 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; + } + + 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()); - Toast.makeText(this, "编辑功能待实现", Toast.LENGTH_SHORT).show(); + 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(); + } + } + } } diff --git a/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java b/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java index daa04224..f36d153f 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java +++ b/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java @@ -374,6 +374,9 @@ public interface ApiService { @POST("api/front/works/publish") Call> publishWork(@Body WorksRequest body); + @POST("api/front/works/update") + Call> updateWork(@Body WorksRequest body); + @GET("api/front/works/detail/{id}") Call> 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> deleteWork(@Path("id") long id); @POST("api/front/works/like/{id}") diff --git a/android-app/app/src/main/res/anim/trtc_button_click.xml b/android-app/app/src/main/res/anim/trtc_button_click.xml new file mode 100644 index 00000000..0afc63cc --- /dev/null +++ b/android-app/app/src/main/res/anim/trtc_button_click.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/res/anim/trtc_like_animation.xml b/android-app/app/src/main/res/anim/trtc_like_animation.xml new file mode 100644 index 00000000..7fc20e33 --- /dev/null +++ b/android-app/app/src/main/res/anim/trtc_like_animation.xml @@ -0,0 +1,29 @@ + + + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/res/drawable/bg_trtc_button_primary.xml b/android-app/app/src/main/res/drawable/bg_trtc_button_primary.xml new file mode 100644 index 00000000..d8c4d4ac --- /dev/null +++ b/android-app/app/src/main/res/drawable/bg_trtc_button_primary.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/res/drawable/bg_trtc_button_secondary.xml b/android-app/app/src/main/res/drawable/bg_trtc_button_secondary.xml new file mode 100644 index 00000000..dbe4938c --- /dev/null +++ b/android-app/app/src/main/res/drawable/bg_trtc_button_secondary.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/res/drawable/bg_trtc_info_card.xml b/android-app/app/src/main/res/drawable/bg_trtc_info_card.xml new file mode 100644 index 00000000..f1aa82b2 --- /dev/null +++ b/android-app/app/src/main/res/drawable/bg_trtc_info_card.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/res/drawable/bg_trtc_input.xml b/android-app/app/src/main/res/drawable/bg_trtc_input.xml new file mode 100644 index 00000000..95e4759a --- /dev/null +++ b/android-app/app/src/main/res/drawable/bg_trtc_input.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/res/layout/activity_room_detail_new.xml b/android-app/app/src/main/res/layout/activity_room_detail_new.xml index cc59e714..54eb6d34 100644 --- a/android-app/app/src/main/res/layout/activity_room_detail_new.xml +++ b/android-app/app/src/main/res/layout/activity_room_detail_new.xml @@ -150,32 +150,32 @@ android:src="@drawable/ic_arrow_back_24" app:tint="@android:color/white" /> - + + android:paddingHorizontal="10dp" + android:paddingVertical="8dp"> + app:civ_border_color="@color/trtc_accent" + app:civ_border_width="2dp" /> + android:textColor="@color/trtc_text_secondary" + android:textSize="12sp" /> + app:backgroundTint="@color/trtc_accent" + app:cornerRadius="16dp" /> - + - + + android:paddingHorizontal="10dp" + android:paddingVertical="6dp"> + app:tint="@color/trtc_text_primary" /> + android:textColor="@color/trtc_text_primary" + android:textSize="13sp" + android:textStyle="bold" /> @@ -296,7 +298,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> - + - + + android:layout_marginBottom="16dp"> + android:tint="@color/trtc_accent" /> + android:textColor="@color/trtc_text_primary" + android:textSize="10sp" + android:textStyle="bold" /> - + + android:tint="@color/trtc_secondary" /> - + + android:tint="@color/trtc_text_primary" /> - + + android:paddingHorizontal="16dp" + android:textColor="@color/trtc_text_primary" + android:textSize="14sp" /> + android:textSize="13sp" + android:textStyle="bold" + app:backgroundTint="@color/trtc_accent" + app:cornerRadius="21dp" /> diff --git a/android-app/app/src/main/res/values/colors_trtc.xml b/android-app/app/src/main/res/values/colors_trtc.xml new file mode 100644 index 00000000..962589aa --- /dev/null +++ b/android-app/app/src/main/res/values/colors_trtc.xml @@ -0,0 +1,29 @@ + + + + #FF6B35 + #E55A2B + #4ECDC4 + #FF4081 + #FF6FA3 + + + #1A1A1A + #2D2D2D + #3D3D3D + + + #FFFFFF + #CCFFFFFF + #80FFFFFF + + + #80000000 + #40FFFFFF + #40FF4081 + + + #FF4444 + #4CAF50 + #999999 + \ No newline at end of file