功能:作品点赞+关注+收藏+作品搜索+作品标签
This commit is contained in:
parent
d2fa9a1055
commit
c8e22d497e
|
|
@ -1,3 +1,8 @@
|
||||||
|
# 手动引用
|
||||||
|
1. 你明白我的意思吗。有没有不清楚的问题,请先询问我然后进行开发。有歧义要先询问我之后再进行下一步,不能你自己猜测可能的结果
|
||||||
|
2.
|
||||||
|
|
||||||
|
|
||||||
# AI工作指南
|
# AI工作指南
|
||||||
|
|
||||||
## 🚀 快速引用
|
## 🚀 快速引用
|
||||||
|
|
|
||||||
59
Log/8-配置上传服务器.md
Normal file
59
Log/8-配置上传服务器.md
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
安装软件
|
||||||
|
# 安装 Node.js 18
|
||||||
|
curl -fsSL https://rpm.nodesource.com/setup_18.x | bash -
|
||||||
|
yum install -y nodejs
|
||||||
|
|
||||||
|
# 验证
|
||||||
|
node -v
|
||||||
|
npm -v
|
||||||
|
|
||||||
|
npm install -g pm2
|
||||||
|
pm2 start server.js --name upload-server
|
||||||
|
pm2 save
|
||||||
|
pm2 startup
|
||||||
|
|
||||||
|
更改配置
|
||||||
|
[root@VM-0-16-opencloudos ~]# # 重写正确的配置
|
||||||
|
cat > /www/server/panel/vhost/nginx/1.15.149.240_30005.conf << 'EOF'
|
||||||
|
server
|
||||||
|
{
|
||||||
|
listen 30005;
|
||||||
|
listen [::]:30005;
|
||||||
|
server_name 1.15.149.240_30005;
|
||||||
|
index index.php index.html index.htm default.php default.htm default.html;
|
||||||
|
root /www/wwwroot/1.15.149.240_30005;
|
||||||
|
|
||||||
|
#CERT-APPLY-CHECK--START
|
||||||
|
include /www/server/panel/vhost/nginx/well-known/1.15.149.240_30005.conf;
|
||||||
|
#CERT-APPLY-CHECK--END
|
||||||
|
|
||||||
|
#ERROR-PAGE-START
|
||||||
|
error_page 404 /404.html;
|
||||||
|
#ERROR-PAGE-END
|
||||||
|
|
||||||
|
#PHP-INFO-START
|
||||||
|
include enable-php-84.conf;
|
||||||
|
#PHP-INFO-END
|
||||||
|
|
||||||
|
#REWRITE-START
|
||||||
|
include /www/server/panel/vhost/rewrite/1.15.149.240_30005.conf;
|
||||||
|
#REWRITE-END
|
||||||
|
|
||||||
|
# 上传API代理
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
client_max_body_size 500M;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
|
||||||
|
{
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
nginx -s reload/www/wwwlogs/1.15.149.240_30005.error.log;a|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
|
||||||
|
nginx: [warn] conflicting server name "1.15.149.240" on 0.0.0.0:20001, ignored
|
||||||
|
nginx: the configuration file /www/server/nginx/conf/nginx.conf syntax is ok
|
||||||
|
nginx: configuration file /www/server/nginx/conf/nginx.conf test is successful
|
||||||
|
nginx: [warn] conflicting server name "1.15.149.240" on 0.0.0.0:20001, ignored
|
||||||
|
|
@ -36,7 +36,7 @@ LIVE_PUBLIC_SRS_HTTP_PORT: 25003
|
||||||
file:
|
file:
|
||||||
upload:
|
upload:
|
||||||
server:
|
server:
|
||||||
url: http://1.15.149.240:30005/upload # 文件上传服务器地址
|
url: http://1.15.149.240:30005/api/upload # 文件上传服务器地址
|
||||||
|
|
||||||
# 配置端口
|
# 配置端口
|
||||||
server:
|
server:
|
||||||
|
|
|
||||||
|
|
@ -67,14 +67,11 @@ public class CategoryController {
|
||||||
@ApiOperation(value = "获取作品分类列表")
|
@ApiOperation(value = "获取作品分类列表")
|
||||||
@GetMapping("/work")
|
@GetMapping("/work")
|
||||||
public CommonResult<List<CategoryResponse>> getWorkCategories() {
|
public CommonResult<List<CategoryResponse>> getWorkCategories() {
|
||||||
List<Category> categories = categoryService.getList(
|
// 暂时使用直播间分类作为作品分类,实现统一分类系统
|
||||||
new com.zbkj.common.request.CategorySearchRequest()
|
List<LiveRoomCategory> liveCategories = liveRoomCategoryService.getEnabledList();
|
||||||
.setType(CategoryConstants.CATEGORY_TYPE_WORK)
|
|
||||||
.setStatus(CategoryConstants.CATEGORY_STATUS_NORMAL)
|
|
||||||
);
|
|
||||||
|
|
||||||
List<CategoryResponse> response = categories.stream()
|
List<CategoryResponse> response = liveCategories.stream()
|
||||||
.map(this::toCategoryResponse)
|
.map(this::toLiveRoomCategoryResponse)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return CommonResult.success(response);
|
return CommonResult.success(response);
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,12 @@ LIVE_PUBLIC_SRS_HOST: 1.15.149.240
|
||||||
LIVE_PUBLIC_SRS_RTMP_PORT: 25002
|
LIVE_PUBLIC_SRS_RTMP_PORT: 25002
|
||||||
LIVE_PUBLIC_SRS_HTTP_PORT: 25003
|
LIVE_PUBLIC_SRS_HTTP_PORT: 25003
|
||||||
|
|
||||||
|
# ============ 文件上传服务器配置 ============
|
||||||
|
file:
|
||||||
|
upload:
|
||||||
|
server:
|
||||||
|
url: http://1.15.149.240:30005/api/upload
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
# 配置的环境
|
# 配置的环境
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ public class RemoteUploadServiceImpl {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RemoteUploadServiceImpl.class);
|
private static final Logger logger = LoggerFactory.getLogger(RemoteUploadServiceImpl.class);
|
||||||
|
|
||||||
@Value("${file.upload.server.url:http://1.15.149.240:30005/upload}")
|
@Value("${file.upload.server.url:http://1.15.149.240:30005/api/upload}")
|
||||||
private String uploadServerUrl;
|
private String uploadServerUrl;
|
||||||
|
|
||||||
private RestTemplate restTemplate;
|
private RestTemplate restTemplate;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.zbkj.common.exception.CrmebException;
|
import com.zbkj.common.exception.CrmebException;
|
||||||
import com.zbkj.common.model.category.Category;
|
|
||||||
import com.zbkj.common.model.user.User;
|
import com.zbkj.common.model.user.User;
|
||||||
import com.zbkj.common.model.works.Works;
|
import com.zbkj.common.model.works.Works;
|
||||||
import com.zbkj.common.page.CommonPage;
|
import com.zbkj.common.page.CommonPage;
|
||||||
|
|
@ -14,7 +13,7 @@ import com.zbkj.common.request.WorksRequest;
|
||||||
import com.zbkj.common.request.WorksSearchRequest;
|
import com.zbkj.common.request.WorksSearchRequest;
|
||||||
import com.zbkj.common.response.WorksResponse;
|
import com.zbkj.common.response.WorksResponse;
|
||||||
import com.zbkj.service.dao.WorksDao;
|
import com.zbkj.service.dao.WorksDao;
|
||||||
import com.zbkj.service.service.CategoryService;
|
import com.zbkj.service.service.LiveRoomCategoryService;
|
||||||
import com.zbkj.service.service.UserService;
|
import com.zbkj.service.service.UserService;
|
||||||
import com.zbkj.service.service.WorksLikeService;
|
import com.zbkj.service.service.WorksLikeService;
|
||||||
import com.zbkj.service.service.WorksCollectService;
|
import com.zbkj.service.service.WorksCollectService;
|
||||||
|
|
@ -40,7 +39,7 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private CategoryService categoryService;
|
private LiveRoomCategoryService liveRoomCategoryService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private WorksLikeService worksLikeService;
|
private WorksLikeService worksLikeService;
|
||||||
|
|
@ -77,8 +76,8 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
|
||||||
|
|
||||||
// 验证分类是否存在(如果提供了分类ID)
|
// 验证分类是否存在(如果提供了分类ID)
|
||||||
if (request.getCategoryId() != null) {
|
if (request.getCategoryId() != null) {
|
||||||
Category category = categoryService.getById(request.getCategoryId());
|
com.zbkj.common.model.live.LiveRoomCategory category = liveRoomCategoryService.getById(request.getCategoryId());
|
||||||
if (category == null || !category.getStatus()) {
|
if (category == null || category.getStatus() == null || category.getStatus() != 1) {
|
||||||
throw new CrmebException("分类不存在或已禁用");
|
throw new CrmebException("分类不存在或已禁用");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -166,8 +165,8 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
|
||||||
|
|
||||||
// 验证分类是否存在(如果提供了分类ID)
|
// 验证分类是否存在(如果提供了分类ID)
|
||||||
if (request.getCategoryId() != null) {
|
if (request.getCategoryId() != null) {
|
||||||
Category category = categoryService.getById(request.getCategoryId());
|
com.zbkj.common.model.live.LiveRoomCategory category = liveRoomCategoryService.getById(request.getCategoryId());
|
||||||
if (category == null || !category.getStatus()) {
|
if (category == null || category.getStatus() == null || category.getStatus() != 1) {
|
||||||
throw new CrmebException("分类不存在或已禁用");
|
throw new CrmebException("分类不存在或已禁用");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -421,7 +420,7 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
|
||||||
|
|
||||||
// 获取分类信息
|
// 获取分类信息
|
||||||
if (works.getCategoryId() != null) {
|
if (works.getCategoryId() != null) {
|
||||||
Category category = categoryService.getById(works.getCategoryId());
|
com.zbkj.common.model.live.LiveRoomCategory category = liveRoomCategoryService.getById(works.getCategoryId());
|
||||||
if (category != null) {
|
if (category != null) {
|
||||||
response.setCategoryName(category.getName());
|
response.setCategoryName(category.getName());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
Zhibo/zhibo-h/sql/init_live_room_categories.sql
Normal file
18
Zhibo/zhibo-h/sql/init_live_room_categories.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
-- 初始化直播间分类数据
|
||||||
|
-- 确保eb_live_room_category表有基础的分类数据
|
||||||
|
|
||||||
|
USE zhibo;
|
||||||
|
|
||||||
|
-- 插入基础的直播间分类数据(如果不存在)
|
||||||
|
INSERT IGNORE INTO eb_live_room_category (id, name, sort, status, create_time, update_time) VALUES
|
||||||
|
(1, '娱乐', 1, 1, NOW(), NOW()),
|
||||||
|
(2, '游戏', 2, 1, NOW(), NOW()),
|
||||||
|
(3, '音乐', 3, 1, NOW(), NOW()),
|
||||||
|
(4, '户外', 4, 1, NOW(), NOW()),
|
||||||
|
(5, '美食', 5, 1, NOW(), NOW()),
|
||||||
|
(6, '体育', 6, 1, NOW(), NOW()),
|
||||||
|
(7, '教育', 7, 1, NOW(), NOW()),
|
||||||
|
(8, '科技', 8, 1, NOW(), NOW());
|
||||||
|
|
||||||
|
-- 查看插入结果
|
||||||
|
SELECT id, name, status FROM eb_live_room_category ORDER BY sort;
|
||||||
12
Zhibo/zhibo-h/sql/optimize_works_category.sql
Normal file
12
Zhibo/zhibo-h/sql/optimize_works_category.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- 作品分类功能优化
|
||||||
|
-- 作品表已经有 category_id 字段支持单分类选择
|
||||||
|
-- 确保索引存在以提高查询性能
|
||||||
|
|
||||||
|
USE zhibo;
|
||||||
|
|
||||||
|
-- 确保 category_id 字段存在并有正确的索引
|
||||||
|
-- 如果索引不存在,创建索引以提高查询性能
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_category_id ON eb_works(category_id);
|
||||||
|
|
||||||
|
-- 更新表注释
|
||||||
|
ALTER TABLE eb_works COMMENT = '作品表 - 支持单分类选择';
|
||||||
|
|
@ -101,6 +101,16 @@
|
||||||
android:name="com.example.livestreaming.LikedRoomsActivity"
|
android:name="com.example.livestreaming.LikedRoomsActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.example.livestreaming.MyLikesActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.example.livestreaming.MyCollectionsActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.example.livestreaming.LikesListActivity"
|
android:name="com.example.livestreaming.LikesListActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
package com.example.livestreaming;
|
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 频道管理适配器
|
|
||||||
* 用于显示我的频道和推荐频道
|
|
||||||
*/
|
|
||||||
public class ChannelManagerAdapter extends RecyclerView.Adapter<ChannelManagerAdapter.ViewHolder> {
|
|
||||||
|
|
||||||
public interface OnChannelClickListener {
|
|
||||||
void onChannelClick(ChannelItem item, int position);
|
|
||||||
void onChannelDelete(ChannelItem item, int position);
|
|
||||||
void onChannelAdd(ChannelItem item, int position);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<ChannelItem> items = new ArrayList<>();
|
|
||||||
private OnChannelClickListener listener;
|
|
||||||
private boolean isEditMode = false;
|
|
||||||
private boolean isRecommendMode = false; // 是否是推荐频道模式
|
|
||||||
private int fixedCount = 4; // 前4个固定不能删除
|
|
||||||
|
|
||||||
public void setOnChannelClickListener(OnChannelClickListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEditMode(boolean editMode) {
|
|
||||||
this.isEditMode = editMode;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRecommendMode(boolean recommendMode) {
|
|
||||||
this.isRecommendMode = recommendMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFixedCount(int count) {
|
|
||||||
this.fixedCount = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void submitList(List<ChannelItem> newItems) {
|
|
||||||
items.clear();
|
|
||||||
if (newItems != null) {
|
|
||||||
items.addAll(newItems);
|
|
||||||
}
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ChannelItem> getItems() {
|
|
||||||
return new ArrayList<>(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
|
||||||
.inflate(R.layout.item_channel_tag, parent, false);
|
|
||||||
return new ViewHolder(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
|
||||||
ChannelItem item = items.get(position);
|
|
||||||
holder.bind(item, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return items.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
private final TextView channelName;
|
|
||||||
private final ImageView fixedIcon;
|
|
||||||
private final ImageView deleteIcon;
|
|
||||||
private final ImageView addIcon;
|
|
||||||
|
|
||||||
ViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
channelName = itemView.findViewById(R.id.channelName);
|
|
||||||
fixedIcon = itemView.findViewById(R.id.fixedIcon);
|
|
||||||
deleteIcon = itemView.findViewById(R.id.deleteIcon);
|
|
||||||
addIcon = itemView.findViewById(R.id.addIcon);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bind(ChannelItem item, int position) {
|
|
||||||
channelName.setText(item.getName());
|
|
||||||
|
|
||||||
// 重置所有图标状态
|
|
||||||
fixedIcon.setVisibility(View.GONE);
|
|
||||||
deleteIcon.setVisibility(View.GONE);
|
|
||||||
addIcon.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
if (isRecommendMode) {
|
|
||||||
// 推荐频道模式:显示添加图标
|
|
||||||
addIcon.setVisibility(View.VISIBLE);
|
|
||||||
itemView.setOnClickListener(v -> {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onChannelAdd(item, position);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 我的频道模式
|
|
||||||
boolean isFixed = position < fixedCount;
|
|
||||||
|
|
||||||
if (isEditMode) {
|
|
||||||
// 编辑模式
|
|
||||||
if (isFixed) {
|
|
||||||
fixedIcon.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
deleteIcon.setVisibility(View.VISIBLE);
|
|
||||||
deleteIcon.setOnClickListener(v -> {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onChannelDelete(item, position);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
itemView.setOnClickListener(v -> {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onChannelClick(item, position);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 频道项数据类
|
|
||||||
*/
|
|
||||||
public static class ChannelItem {
|
|
||||||
private final String id;
|
|
||||||
private final String name;
|
|
||||||
private boolean isFixed;
|
|
||||||
|
|
||||||
public ChannelItem(String id, String name) {
|
|
||||||
this.id = id;
|
|
||||||
this.name = name;
|
|
||||||
this.isFixed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChannelItem(String id, String name, boolean isFixed) {
|
|
||||||
this.id = id;
|
|
||||||
this.name = name;
|
|
||||||
this.isFixed = isFixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() { return id; }
|
|
||||||
public String getName() { return name; }
|
|
||||||
public boolean isFixed() { return isFixed; }
|
|
||||||
public void setFixed(boolean fixed) { this.isFixed = fixed; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -71,40 +71,72 @@ public class ChannelTagAdapter extends ListAdapter<ChannelTagAdapter.ChannelTag,
|
||||||
|
|
||||||
class ViewHolder extends RecyclerView.ViewHolder {
|
class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
private final TextView tagText;
|
private final TextView tagText;
|
||||||
|
private final View deleteIcon;
|
||||||
|
|
||||||
ViewHolder(@NonNull View itemView) {
|
ViewHolder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
tagText = itemView.findViewById(R.id.channelName);
|
tagText = itemView.findViewById(R.id.channelName);
|
||||||
|
deleteIcon = itemView.findViewById(R.id.deleteIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bind(ChannelTag tag, int position) {
|
void bind(ChannelTag tag, int position) {
|
||||||
// 显示文本
|
if (tag == null || tag.getName() == null) {
|
||||||
if (isRecommendMode) {
|
return;
|
||||||
tagText.setText("+ " + tag.getName());
|
|
||||||
} else {
|
|
||||||
tagText.setText(tag.getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选中状态
|
try {
|
||||||
boolean isSelected = position == selectedPosition;
|
// 显示文本
|
||||||
if (isSelected) {
|
if (isRecommendMode) {
|
||||||
tagText.setBackgroundResource(R.drawable.bg_channel_tag_selected);
|
tagText.setText("+ " + tag.getName());
|
||||||
tagText.setTextColor(itemView.getContext().getResources().getColor(android.R.color.black, null));
|
deleteIcon.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
tagText.setBackgroundResource(R.drawable.bg_channel_tag);
|
tagText.setText(tag.getName());
|
||||||
tagText.setTextColor(itemView.getContext().getResources().getColor(android.R.color.darker_gray, null));
|
// 我的频道模式下,除了"推荐"外都可以删除
|
||||||
}
|
if (position == 0 && "推荐".equals(tag.getName())) {
|
||||||
|
deleteIcon.setVisibility(View.GONE);
|
||||||
// 点击事件
|
|
||||||
itemView.setOnClickListener(v -> {
|
|
||||||
if (listener != null) {
|
|
||||||
if (isRecommendMode) {
|
|
||||||
listener.onTagAddClick(tag, position);
|
|
||||||
} else {
|
} else {
|
||||||
listener.onTagClick(tag, position);
|
deleteIcon.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// 选中状态样式 - 更明显的标签样式
|
||||||
|
boolean isSelected = position == selectedPosition;
|
||||||
|
if (isSelected) {
|
||||||
|
// 选中状态:蓝色背景,白色文字
|
||||||
|
tagText.setBackgroundResource(R.drawable.bg_channel_tag_selected);
|
||||||
|
tagText.setTextColor(itemView.getContext().getResources().getColor(android.R.color.white, null));
|
||||||
|
} else {
|
||||||
|
if (isRecommendMode) {
|
||||||
|
// 推荐频道:虚线边框,紫色文字
|
||||||
|
tagText.setBackgroundResource(R.drawable.bg_channel_tag_recommend);
|
||||||
|
tagText.setTextColor(itemView.getContext().getResources().getColor(R.color.purple_500, null));
|
||||||
|
} else {
|
||||||
|
// 我的频道:实线边框,深灰色文字
|
||||||
|
tagText.setBackgroundResource(R.drawable.bg_channel_tag_normal);
|
||||||
|
tagText.setTextColor(itemView.getContext().getResources().getColor(android.R.color.darker_gray, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签点击事件
|
||||||
|
tagText.setOnClickListener(v -> {
|
||||||
|
if (listener != null) {
|
||||||
|
if (isRecommendMode) {
|
||||||
|
listener.onTagAddClick(tag, position);
|
||||||
|
} else {
|
||||||
|
listener.onTagClick(tag, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除按钮点击事件
|
||||||
|
deleteIcon.setOnClickListener(v -> {
|
||||||
|
if (listener != null && !isRecommendMode) {
|
||||||
|
listener.onTagClick(tag, position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
android.util.Log.e("ChannelTagAdapter", "绑定标签数据失败", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import android.speech.SpeechRecognizer;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
@ -44,6 +45,7 @@ import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
import com.example.livestreaming.net.ApiClient;
|
import com.example.livestreaming.net.ApiClient;
|
||||||
import com.example.livestreaming.net.ApiResponse;
|
import com.example.livestreaming.net.ApiResponse;
|
||||||
|
import com.example.livestreaming.net.ApiService;
|
||||||
import com.example.livestreaming.net.CommunityResponse;
|
import com.example.livestreaming.net.CommunityResponse;
|
||||||
import com.example.livestreaming.net.ConversationResponse;
|
import com.example.livestreaming.net.ConversationResponse;
|
||||||
import com.example.livestreaming.net.CreateRoomRequest;
|
import com.example.livestreaming.net.CreateRoomRequest;
|
||||||
|
|
@ -201,7 +203,8 @@ public class MainActivity extends AppCompatActivity {
|
||||||
// 隐藏粉丝和获赞菜单项
|
// 隐藏粉丝和获赞菜单项
|
||||||
// items.add(new DrawerCardItem(DrawerCardItem.ACTION_FANS, "粉丝", "关注你的人", R.drawable.ic_people_24));
|
// items.add(new DrawerCardItem(DrawerCardItem.ACTION_FANS, "粉丝", "关注你的人", R.drawable.ic_people_24));
|
||||||
// items.add(new DrawerCardItem(DrawerCardItem.ACTION_LIKES, "获赞", "收到的点赞", R.drawable.ic_heart_24));
|
// items.add(new DrawerCardItem(DrawerCardItem.ACTION_LIKES, "获赞", "收到的点赞", R.drawable.ic_heart_24));
|
||||||
items.add(new DrawerCardItem(DrawerCardItem.ACTION_HISTORY, "观看历史", "最近看过的直播", R.drawable.ic_grid_24));
|
// 隐藏观看历史菜单项
|
||||||
|
// items.add(new DrawerCardItem(DrawerCardItem.ACTION_HISTORY, "观看历史", "最近看过的直播", R.drawable.ic_grid_24));
|
||||||
items.add(new DrawerCardItem(DrawerCardItem.ACTION_SEARCH, "搜索", "找主播/房间/标签", R.drawable.ic_search_24));
|
items.add(new DrawerCardItem(DrawerCardItem.ACTION_SEARCH, "搜索", "找主播/房间/标签", R.drawable.ic_search_24));
|
||||||
items.add(new DrawerCardItem(DrawerCardItem.ACTION_SETTINGS, "设置", "账号、隐私、通知", R.drawable.ic_menu_24));
|
items.add(new DrawerCardItem(DrawerCardItem.ACTION_SETTINGS, "设置", "账号、隐私、通知", R.drawable.ic_menu_24));
|
||||||
items.add(new DrawerCardItem(DrawerCardItem.ACTION_HELP, "帮助与反馈", "常见问题与建议", R.drawable.ic_chat_24));
|
items.add(new DrawerCardItem(DrawerCardItem.ACTION_HELP, "帮助与反馈", "常见问题与建议", R.drawable.ic_chat_24));
|
||||||
|
|
@ -398,6 +401,17 @@ public class MainActivity extends AppCompatActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置分类展开按钮点击事件
|
||||||
|
View btnExpandCategories = findViewById(R.id.btnExpandCategories);
|
||||||
|
if (btnExpandCategories != null) {
|
||||||
|
btnExpandCategories.setOnClickListener(new DebounceClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onDebouncedClick(View v) {
|
||||||
|
showCategoryManagementDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 设置通知图标点击事件(如果存在)
|
// 设置通知图标点击事件(如果存在)
|
||||||
try {
|
try {
|
||||||
View notificationIcon = findViewById(R.id.notificationIcon);
|
View notificationIcon = findViewById(R.id.notificationIcon);
|
||||||
|
|
@ -1751,6 +1765,9 @@ public class MainActivity extends AppCompatActivity {
|
||||||
private void loadCategoriesFromBackend() {
|
private void loadCategoriesFromBackend() {
|
||||||
Log.d(TAG, "loadCategoriesFromBackend() 开始加载直播间分类");
|
Log.d(TAG, "loadCategoriesFromBackend() 开始加载直播间分类");
|
||||||
|
|
||||||
|
// 先从本地加载我的频道配置
|
||||||
|
loadMyChannelsFromPrefs();
|
||||||
|
|
||||||
// 调用后端接口获取直播间分类列表
|
// 调用后端接口获取直播间分类列表
|
||||||
ApiClient.getService(getApplicationContext()).getLiveRoomCategories()
|
ApiClient.getService(getApplicationContext()).getLiveRoomCategories()
|
||||||
.enqueue(new Callback<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>>() {
|
.enqueue(new Callback<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>>() {
|
||||||
|
|
@ -1767,8 +1784,11 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
if (categories != null && !categories.isEmpty()) {
|
if (categories != null && !categories.isEmpty()) {
|
||||||
Log.d(TAG, "loadCategoriesFromBackend() 成功获取 " + categories.size() + " 个分类");
|
Log.d(TAG, "loadCategoriesFromBackend() 成功获取 " + categories.size() + " 个分类");
|
||||||
// 更新分类标签
|
// 缓存后端分类数据
|
||||||
updateCategoryTabs(categories);
|
allBackendCategories.clear();
|
||||||
|
allBackendCategories.addAll(categories);
|
||||||
|
// 使用我的频道配置更新分类标签
|
||||||
|
updateCategoryTabsFromMyChannels();
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "loadCategoriesFromBackend() 未获取到分类数据,使用默认分类");
|
Log.w(TAG, "loadCategoriesFromBackend() 未获取到分类数据,使用默认分类");
|
||||||
// 使用默认分类
|
// 使用默认分类
|
||||||
|
|
@ -1823,32 +1843,14 @@ public class MainActivity extends AppCompatActivity {
|
||||||
private void useDefaultCategories() {
|
private void useDefaultCategories() {
|
||||||
if (binding == null || binding.categoryTabs == null) return;
|
if (binding == null || binding.categoryTabs == null) return;
|
||||||
|
|
||||||
|
// 如果我的频道配置为空,初始化默认配置
|
||||||
|
if (myChannels.isEmpty()) {
|
||||||
|
initDefaultMyChannels();
|
||||||
|
}
|
||||||
|
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
// 清空现有标签
|
// 使用我的频道配置更新分类标签
|
||||||
binding.categoryTabs.removeAllTabs();
|
updateCategoryTabsFromMyChannels();
|
||||||
|
|
||||||
// 使用后端分类(如果已加载)
|
|
||||||
if (!allBackendCategories.isEmpty()) {
|
|
||||||
// 添加"推荐"作为第一个标签
|
|
||||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("推荐"));
|
|
||||||
|
|
||||||
// 添加后端分类
|
|
||||||
for (com.example.livestreaming.net.CategoryResponse cat : allBackendCategories) {
|
|
||||||
if (cat != null && cat.getName() != null) {
|
|
||||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText(cat.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 后端分类未加载,使用硬编码默认值
|
|
||||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("推荐"));
|
|
||||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("娱乐"));
|
|
||||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("游戏"));
|
|
||||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("音乐"));
|
|
||||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("户外"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复上次选中的分类
|
|
||||||
restoreCategoryTabSelection();
|
|
||||||
|
|
||||||
Log.d(TAG, "useDefaultCategories() 使用默认分类,共 " + binding.categoryTabs.getTabCount() + " 个标签");
|
Log.d(TAG, "useDefaultCategories() 使用默认分类,共 " + binding.categoryTabs.getTabCount() + " 个标签");
|
||||||
});
|
});
|
||||||
|
|
@ -3480,246 +3482,6 @@ public class MainActivity extends AppCompatActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示频道管理底部弹窗
|
|
||||||
*/
|
|
||||||
private void showChannelManagerDialog() {
|
|
||||||
// 创建底部弹窗
|
|
||||||
com.google.android.material.bottomsheet.BottomSheetDialog dialog =
|
|
||||||
new com.google.android.material.bottomsheet.BottomSheetDialog(this);
|
|
||||||
View dialogView = getLayoutInflater().inflate(R.layout.bottom_sheet_channel_manager, null);
|
|
||||||
dialog.setContentView(dialogView);
|
|
||||||
|
|
||||||
// 获取视图引用
|
|
||||||
RecyclerView myChannelRecycler = dialogView.findViewById(R.id.myChannelsRecycler);
|
|
||||||
RecyclerView recommendChannelRecycler = dialogView.findViewById(R.id.recommendChannelsRecycler);
|
|
||||||
TextView btnEditChannel = dialogView.findViewById(R.id.btnEditChannels);
|
|
||||||
View btnCloseChannelManager = dialogView.findViewById(R.id.btnCloseChannelManager);
|
|
||||||
|
|
||||||
// 初始化我的频道适配器
|
|
||||||
ChannelManagerAdapter myAdapter = new ChannelManagerAdapter();
|
|
||||||
myAdapter.setFixedCount(4); // 前4个固定
|
|
||||||
myAdapter.setRecommendMode(false);
|
|
||||||
|
|
||||||
// 初始化推荐频道适配器
|
|
||||||
ChannelManagerAdapter recommendAdapter = new ChannelManagerAdapter();
|
|
||||||
recommendAdapter.setRecommendMode(true);
|
|
||||||
|
|
||||||
// 设置布局管理器 - 使用FlexboxLayoutManager或GridLayoutManager
|
|
||||||
myChannelRecycler.setLayoutManager(new androidx.recyclerview.widget.GridLayoutManager(this, 4));
|
|
||||||
recommendChannelRecycler.setLayoutManager(new androidx.recyclerview.widget.GridLayoutManager(this, 4));
|
|
||||||
|
|
||||||
myChannelRecycler.setAdapter(myAdapter);
|
|
||||||
recommendChannelRecycler.setAdapter(recommendAdapter);
|
|
||||||
|
|
||||||
// 加载我的频道数据(从SharedPreferences或默认值)
|
|
||||||
List<ChannelManagerAdapter.ChannelItem> myChannelList = loadMyChannels();
|
|
||||||
myAdapter.submitList(myChannelList);
|
|
||||||
|
|
||||||
// 加载推荐频道数据
|
|
||||||
List<ChannelManagerAdapter.ChannelItem> recommendList = loadRecommendChannels(myChannelList);
|
|
||||||
recommendAdapter.submitList(recommendList);
|
|
||||||
|
|
||||||
// 设置我的频道点击事件
|
|
||||||
myAdapter.setOnChannelClickListener(new ChannelManagerAdapter.OnChannelClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onChannelClick(ChannelManagerAdapter.ChannelItem item, int position) {
|
|
||||||
// 点击频道,切换到该分类
|
|
||||||
dialog.dismiss();
|
|
||||||
selectCategoryTab(item.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChannelDelete(ChannelManagerAdapter.ChannelItem item, int position) {
|
|
||||||
// 删除频道
|
|
||||||
List<ChannelManagerAdapter.ChannelItem> currentList = myAdapter.getItems();
|
|
||||||
currentList.remove(position);
|
|
||||||
myAdapter.submitList(new ArrayList<>(currentList));
|
|
||||||
saveMyChannels(currentList);
|
|
||||||
// 更新推荐列表
|
|
||||||
recommendAdapter.submitList(loadRecommendChannels(currentList));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChannelAdd(ChannelManagerAdapter.ChannelItem item, int position) {
|
|
||||||
// 不处理
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置推荐频道点击事件
|
|
||||||
recommendAdapter.setOnChannelClickListener(new ChannelManagerAdapter.OnChannelClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onChannelClick(ChannelManagerAdapter.ChannelItem item, int position) {
|
|
||||||
// 不处理
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChannelDelete(ChannelManagerAdapter.ChannelItem item, int position) {
|
|
||||||
// 不处理
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChannelAdd(ChannelManagerAdapter.ChannelItem item, int position) {
|
|
||||||
// 添加到我的频道
|
|
||||||
List<ChannelManagerAdapter.ChannelItem> currentList = myAdapter.getItems();
|
|
||||||
currentList.add(new ChannelManagerAdapter.ChannelItem(item.getId(), item.getName()));
|
|
||||||
myAdapter.submitList(new ArrayList<>(currentList));
|
|
||||||
saveMyChannels(currentList);
|
|
||||||
// 更新推荐列表
|
|
||||||
recommendAdapter.submitList(loadRecommendChannels(currentList));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 编辑按钮点击事件
|
|
||||||
final boolean[] isEditMode = {false};
|
|
||||||
if (btnEditChannel != null) {
|
|
||||||
btnEditChannel.setOnClickListener(v -> {
|
|
||||||
isEditMode[0] = !isEditMode[0];
|
|
||||||
myAdapter.setEditMode(isEditMode[0]);
|
|
||||||
btnEditChannel.setText(isEditMode[0] ? "完成" : "编辑");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭按钮点击事件
|
|
||||||
if (btnCloseChannelManager != null) {
|
|
||||||
btnCloseChannelManager.setOnClickListener(v -> dialog.dismiss());
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载我的频道列表
|
|
||||||
*/
|
|
||||||
private List<ChannelManagerAdapter.ChannelItem> loadMyChannels() {
|
|
||||||
List<ChannelManagerAdapter.ChannelItem> channels = new ArrayList<>();
|
|
||||||
|
|
||||||
// 从SharedPreferences加载
|
|
||||||
android.content.SharedPreferences prefs = getSharedPreferences("channel_prefs", MODE_PRIVATE);
|
|
||||||
|
|
||||||
// 检查是否需要迁移到新版本(使用后端分类)
|
|
||||||
int savedVersion = prefs.getInt("channel_version", 0);
|
|
||||||
if (savedVersion < 2) {
|
|
||||||
// 旧版本数据,清除并使用后端分类
|
|
||||||
prefs.edit()
|
|
||||||
.remove("my_channels")
|
|
||||||
.putInt("channel_version", 2)
|
|
||||||
.apply();
|
|
||||||
Log.d(TAG, "loadMyChannels() 清除旧版本频道数据,使用后端分类");
|
|
||||||
}
|
|
||||||
|
|
||||||
String savedChannels = prefs.getString("my_channels", null);
|
|
||||||
|
|
||||||
if (savedChannels != null && !savedChannels.isEmpty()) {
|
|
||||||
String[] channelNames = savedChannels.split(",");
|
|
||||||
for (int i = 0; i < channelNames.length; i++) {
|
|
||||||
channels.add(new ChannelManagerAdapter.ChannelItem(
|
|
||||||
String.valueOf(i), channelNames[i], i < 4));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 使用后端分类作为默认频道(如果有的话)
|
|
||||||
if (!allBackendCategories.isEmpty()) {
|
|
||||||
// 添加"推荐"作为第一个固定频道
|
|
||||||
channels.add(new ChannelManagerAdapter.ChannelItem("0", "推荐", true));
|
|
||||||
// 添加后端分类(最多取前4个作为默认)
|
|
||||||
int count = Math.min(allBackendCategories.size(), 4);
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
com.example.livestreaming.net.CategoryResponse cat = allBackendCategories.get(i);
|
|
||||||
if (cat != null && cat.getName() != null) {
|
|
||||||
channels.add(new ChannelManagerAdapter.ChannelItem(
|
|
||||||
String.valueOf(cat.getId()), cat.getName(), true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 后端分类未加载,使用硬编码默认值
|
|
||||||
channels.add(new ChannelManagerAdapter.ChannelItem("0", "推荐", true));
|
|
||||||
channels.add(new ChannelManagerAdapter.ChannelItem("1", "娱乐", true));
|
|
||||||
channels.add(new ChannelManagerAdapter.ChannelItem("2", "游戏", true));
|
|
||||||
channels.add(new ChannelManagerAdapter.ChannelItem("3", "音乐", true));
|
|
||||||
channels.add(new ChannelManagerAdapter.ChannelItem("4", "户外", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存我的频道列表
|
|
||||||
*/
|
|
||||||
private void saveMyChannels(List<ChannelManagerAdapter.ChannelItem> channels) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (int i = 0; i < channels.size(); i++) {
|
|
||||||
if (i > 0) sb.append(",");
|
|
||||||
sb.append(channels.get(i).getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
android.content.SharedPreferences prefs = getSharedPreferences("channel_prefs", MODE_PRIVATE);
|
|
||||||
prefs.edit().putString("my_channels", sb.toString()).apply();
|
|
||||||
|
|
||||||
// 更新分类标签
|
|
||||||
updateCategoryTabsFromChannels(channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载推荐频道列表(排除已添加的)
|
|
||||||
*/
|
|
||||||
private List<ChannelManagerAdapter.ChannelItem> loadRecommendChannels(List<ChannelManagerAdapter.ChannelItem> myChannels) {
|
|
||||||
// 获取已添加的频道名称
|
|
||||||
java.util.Set<String> addedNames = new java.util.HashSet<>();
|
|
||||||
for (ChannelManagerAdapter.ChannelItem item : myChannels) {
|
|
||||||
addedNames.add(item.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ChannelManagerAdapter.ChannelItem> recommendList = new ArrayList<>();
|
|
||||||
|
|
||||||
// 优先使用后端分类
|
|
||||||
if (!allBackendCategories.isEmpty()) {
|
|
||||||
for (com.example.livestreaming.net.CategoryResponse cat : allBackendCategories) {
|
|
||||||
if (cat != null && cat.getName() != null && !addedNames.contains(cat.getName())) {
|
|
||||||
recommendList.add(new ChannelManagerAdapter.ChannelItem(
|
|
||||||
String.valueOf(cat.getId()), cat.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 后端分类未加载,使用硬编码默认值
|
|
||||||
String[] defaultChannels = {"娱乐", "游戏", "音乐", "户外", "聊天"};
|
|
||||||
for (int i = 0; i < defaultChannels.length; i++) {
|
|
||||||
if (!addedNames.contains(defaultChannels[i])) {
|
|
||||||
recommendList.add(new ChannelManagerAdapter.ChannelItem(String.valueOf(i), defaultChannels[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return recommendList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据频道列表更新分类标签
|
|
||||||
*/
|
|
||||||
private void updateCategoryTabsFromChannels(List<ChannelManagerAdapter.ChannelItem> channels) {
|
|
||||||
if (binding.categoryTabs == null) return;
|
|
||||||
|
|
||||||
binding.categoryTabs.removeAllTabs();
|
|
||||||
for (ChannelManagerAdapter.ChannelItem channel : channels) {
|
|
||||||
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText(channel.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 选中指定分类标签
|
|
||||||
*/
|
|
||||||
private void selectCategoryTab(String categoryName) {
|
|
||||||
if (binding.categoryTabs == null) return;
|
|
||||||
|
|
||||||
for (int i = 0; i < binding.categoryTabs.getTabCount(); i++) {
|
|
||||||
TabLayout.Tab tab = binding.categoryTabs.getTabAt(i);
|
|
||||||
if (tab != null && categoryName.equals(tab.getText())) {
|
|
||||||
tab.select();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开应用设置页面,引导用户手动开启权限
|
* 打开应用设置页面,引导用户手动开启权限
|
||||||
*/
|
*/
|
||||||
|
|
@ -3741,4 +3503,378 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 显示分类管理对话框
|
||||||
|
*/
|
||||||
|
private void showCategoryManagementDialog() {
|
||||||
|
// 创建底部弹出面板
|
||||||
|
com.google.android.material.bottomsheet.BottomSheetDialog bottomSheetDialog =
|
||||||
|
new com.google.android.material.bottomsheet.BottomSheetDialog(this);
|
||||||
|
View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_category_management, null);
|
||||||
|
bottomSheetDialog.setContentView(dialogView);
|
||||||
|
|
||||||
|
// 初始化分类管理界面
|
||||||
|
setupCategoryManagementDialog(dialogView, bottomSheetDialog);
|
||||||
|
|
||||||
|
bottomSheetDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置分类管理对话框
|
||||||
|
*/
|
||||||
|
private void setupCategoryManagementDialog(View dialogView, com.google.android.material.bottomsheet.BottomSheetDialog dialog) {
|
||||||
|
try {
|
||||||
|
if (dialogView == null || dialog == null) {
|
||||||
|
Log.e(TAG, "对话框视图或对话框对象为null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 我的频道
|
||||||
|
RecyclerView myChannelsRecyclerView = dialogView.findViewById(R.id.myChannelsRecyclerView);
|
||||||
|
TextView addChannelText = dialogView.findViewById(R.id.addChannelText);
|
||||||
|
|
||||||
|
// 推荐频道
|
||||||
|
RecyclerView recommendChannelsRecyclerView = dialogView.findViewById(R.id.recommendChannelsRecyclerView);
|
||||||
|
TextView recommendTitle = dialogView.findViewById(R.id.recommendTitle);
|
||||||
|
|
||||||
|
// 完成按钮
|
||||||
|
TextView completeButton = dialogView.findViewById(R.id.completeButton);
|
||||||
|
|
||||||
|
if (myChannelsRecyclerView == null || recommendChannelsRecyclerView == null || completeButton == null) {
|
||||||
|
Log.e(TAG, "对话框中的关键视图为null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置我的频道列表
|
||||||
|
myChannelAdapter = new ChannelTagAdapter();
|
||||||
|
myChannelAdapter.setRecommendMode(false);
|
||||||
|
myChannelAdapter.setOnTagClickListener(new ChannelTagAdapter.OnTagClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onTagClick(ChannelTagAdapter.ChannelTag tag, int position) {
|
||||||
|
// 移除频道(除了"推荐")
|
||||||
|
try {
|
||||||
|
if (position == 0 && "推荐".equals(tag.getName())) {
|
||||||
|
Toast.makeText(MainActivity.this, "推荐频道不能移除", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myChannels.size() > 1 && position >= 0 && position < myChannels.size()) {
|
||||||
|
ChannelTagAdapter.ChannelTag removed = myChannels.remove(position);
|
||||||
|
if (removed != null) {
|
||||||
|
// 添加到推荐频道
|
||||||
|
recommendChannels.add(removed);
|
||||||
|
|
||||||
|
// 更新适配器
|
||||||
|
if (myChannelAdapter != null) {
|
||||||
|
myChannelAdapter.submitList(new ArrayList<>(myChannels));
|
||||||
|
}
|
||||||
|
if (recommendChannelAdapter != null) {
|
||||||
|
recommendChannelAdapter.submitList(new ArrayList<>(recommendChannels));
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(MainActivity.this, "已移除 " + removed.getName(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(MainActivity.this, "至少需要保留一个频道", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "移除频道失败", e);
|
||||||
|
Toast.makeText(MainActivity.this, "移除频道失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTagAddClick(ChannelTagAdapter.ChannelTag tag, int position) {
|
||||||
|
// 不处理
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
myChannelsRecyclerView.setLayoutManager(new GridLayoutManager(this, 4));
|
||||||
|
myChannelsRecyclerView.setAdapter(myChannelAdapter);
|
||||||
|
|
||||||
|
// 设置推荐频道列表
|
||||||
|
recommendChannelAdapter = new ChannelTagAdapter();
|
||||||
|
recommendChannelAdapter.setRecommendMode(true);
|
||||||
|
recommendChannelAdapter.setOnTagClickListener(new ChannelTagAdapter.OnTagClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onTagClick(ChannelTagAdapter.ChannelTag tag, int position) {
|
||||||
|
// 不处理
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTagAddClick(ChannelTagAdapter.ChannelTag tag, int position) {
|
||||||
|
// 添加频道到我的频道
|
||||||
|
try {
|
||||||
|
if (position >= 0 && position < recommendChannels.size()) {
|
||||||
|
ChannelTagAdapter.ChannelTag removed = recommendChannels.remove(position);
|
||||||
|
if (removed != null) {
|
||||||
|
// 添加到我的频道
|
||||||
|
myChannels.add(removed);
|
||||||
|
|
||||||
|
// 更新适配器
|
||||||
|
if (recommendChannelAdapter != null) {
|
||||||
|
recommendChannelAdapter.submitList(new ArrayList<>(recommendChannels));
|
||||||
|
}
|
||||||
|
if (myChannelAdapter != null) {
|
||||||
|
myChannelAdapter.submitList(new ArrayList<>(myChannels));
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(MainActivity.this, "已添加 " + removed.getName(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "添加频道失败", e);
|
||||||
|
Toast.makeText(MainActivity.this, "添加频道失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
recommendChannelsRecyclerView.setLayoutManager(new GridLayoutManager(this, 4));
|
||||||
|
recommendChannelsRecyclerView.setAdapter(recommendChannelAdapter);
|
||||||
|
|
||||||
|
// 完成按钮点击事件
|
||||||
|
completeButton.setOnClickListener(v -> {
|
||||||
|
// 保存我的频道配置到本地
|
||||||
|
saveMyChannelsToPrefs();
|
||||||
|
// 更新分类标签
|
||||||
|
updateCategoryTabsFromMyChannels();
|
||||||
|
dialog.dismiss();
|
||||||
|
Toast.makeText(MainActivity.this, "频道设置已保存", Toast.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载分类数据
|
||||||
|
loadCategoriesForDialog();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "设置分类管理对话框失败", e);
|
||||||
|
Toast.makeText(this, "初始化对话框失败", Toast.LENGTH_SHORT).show();
|
||||||
|
if (dialog != null) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载分类数据用于对话框
|
||||||
|
*/
|
||||||
|
private void loadCategoriesForDialog() {
|
||||||
|
// 先从本地加载我的频道配置
|
||||||
|
loadMyChannelsFromPrefs();
|
||||||
|
|
||||||
|
ApiService apiService = ApiClient.getService(this);
|
||||||
|
|
||||||
|
// 加载直播间分类
|
||||||
|
Call<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>> call =
|
||||||
|
apiService.getLiveRoomCategories();
|
||||||
|
|
||||||
|
call.enqueue(new Callback<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>> call,
|
||||||
|
Response<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>> response) {
|
||||||
|
try {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
ApiResponse<List<com.example.livestreaming.net.CategoryResponse>> apiResponse = response.body();
|
||||||
|
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
|
||||||
|
List<com.example.livestreaming.net.CategoryResponse> categories = apiResponse.getData();
|
||||||
|
|
||||||
|
// 在主线程中更新UI
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
try {
|
||||||
|
// 清空推荐频道
|
||||||
|
recommendChannels.clear();
|
||||||
|
|
||||||
|
// 获取我的频道中已有的分类ID
|
||||||
|
Set<Integer> myChannelIds = new HashSet<>();
|
||||||
|
for (ChannelTagAdapter.ChannelTag myChannel : myChannels) {
|
||||||
|
if (myChannel != null) {
|
||||||
|
myChannelIds.add(myChannel.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将不在我的频道中的分类添加到推荐频道
|
||||||
|
for (com.example.livestreaming.net.CategoryResponse category : categories) {
|
||||||
|
if (category != null && category.getName() != null &&
|
||||||
|
!myChannelIds.contains(category.getId())) {
|
||||||
|
recommendChannels.add(new ChannelTagAdapter.ChannelTag(category.getId(), category.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新适配器
|
||||||
|
if (myChannelAdapter != null) {
|
||||||
|
myChannelAdapter.submitList(new ArrayList<>(myChannels));
|
||||||
|
}
|
||||||
|
if (recommendChannelAdapter != null) {
|
||||||
|
recommendChannelAdapter.submitList(new ArrayList<>(recommendChannels));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "更新推荐频道失败", e);
|
||||||
|
initDefaultRecommendChannels();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 如果API调用失败,使用默认的推荐频道
|
||||||
|
runOnUiThread(() -> initDefaultRecommendChannels());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果API调用失败,使用默认的推荐频道
|
||||||
|
runOnUiThread(() -> initDefaultRecommendChannels());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "处理分类数据失败", e);
|
||||||
|
runOnUiThread(() -> initDefaultRecommendChannels());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>> call, Throwable t) {
|
||||||
|
Log.e(TAG, "加载分类失败", t);
|
||||||
|
// 如果API调用失败,使用默认的推荐频道
|
||||||
|
runOnUiThread(() -> initDefaultRecommendChannels());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化默认的推荐频道
|
||||||
|
*/
|
||||||
|
private void initDefaultRecommendChannels() {
|
||||||
|
try {
|
||||||
|
recommendChannels.clear();
|
||||||
|
|
||||||
|
// 获取我的频道中已有的分类名称
|
||||||
|
Set<String> myChannelNames = new HashSet<>();
|
||||||
|
for (ChannelTagAdapter.ChannelTag myChannel : myChannels) {
|
||||||
|
if (myChannel != null && myChannel.getName() != null) {
|
||||||
|
myChannelNames.add(myChannel.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加一些默认的推荐频道(不在我的频道中的)
|
||||||
|
String[] defaultChannels = {"游戏", "舞蹈", "二次元", "体育", "户外", "才艺", "美食"};
|
||||||
|
int id = 100; // 使用较大的ID避免冲突
|
||||||
|
for (String channelName : defaultChannels) {
|
||||||
|
if (channelName != null && !myChannelNames.contains(channelName)) {
|
||||||
|
recommendChannels.add(new ChannelTagAdapter.ChannelTag(id++, channelName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新适配器
|
||||||
|
if (recommendChannelAdapter != null) {
|
||||||
|
recommendChannelAdapter.submitList(new ArrayList<>(recommendChannels));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "初始化默认推荐频道失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从我的频道更新分类标签
|
||||||
|
*/
|
||||||
|
private void updateCategoryTabsFromMyChannels() {
|
||||||
|
try {
|
||||||
|
if (binding == null || binding.categoryTabs == null) {
|
||||||
|
Log.w(TAG, "binding或categoryTabs为null,无法更新分类标签");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.categoryTabs.removeAllTabs();
|
||||||
|
|
||||||
|
// 添加我的频道到分类标签
|
||||||
|
for (ChannelTagAdapter.ChannelTag channel : myChannels) {
|
||||||
|
if (channel != null && channel.getName() != null) {
|
||||||
|
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText(channel.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中第一个标签
|
||||||
|
if (myChannels.size() > 0 && binding.categoryTabs.getTabCount() > 0) {
|
||||||
|
ChannelTagAdapter.ChannelTag firstChannel = myChannels.get(0);
|
||||||
|
if (firstChannel != null && firstChannel.getName() != null) {
|
||||||
|
currentCategory = firstChannel.getName();
|
||||||
|
TabLayout.Tab firstTab = binding.categoryTabs.getTabAt(0);
|
||||||
|
if (firstTab != null) {
|
||||||
|
firstTab.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "更新分类标签失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存我的频道配置到本地
|
||||||
|
*/
|
||||||
|
private void saveMyChannelsToPrefs() {
|
||||||
|
android.content.SharedPreferences prefs = getSharedPreferences("channel_prefs", MODE_PRIVATE);
|
||||||
|
android.content.SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
|
||||||
|
// 将我的频道列表转换为JSON字符串保存
|
||||||
|
StringBuilder channelsJson = new StringBuilder();
|
||||||
|
channelsJson.append("[");
|
||||||
|
for (int i = 0; i < myChannels.size(); i++) {
|
||||||
|
ChannelTagAdapter.ChannelTag channel = myChannels.get(i);
|
||||||
|
if (i > 0) channelsJson.append(",");
|
||||||
|
channelsJson.append("{\"id\":").append(channel.getId())
|
||||||
|
.append(",\"name\":\"").append(channel.getName()).append("\"}");
|
||||||
|
}
|
||||||
|
channelsJson.append("]");
|
||||||
|
|
||||||
|
editor.putString("my_channels", channelsJson.toString());
|
||||||
|
editor.apply();
|
||||||
|
|
||||||
|
Log.d(TAG, "保存我的频道配置: " + channelsJson.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从本地加载我的频道配置
|
||||||
|
*/
|
||||||
|
private void loadMyChannelsFromPrefs() {
|
||||||
|
android.content.SharedPreferences prefs = getSharedPreferences("channel_prefs", MODE_PRIVATE);
|
||||||
|
String channelsJson = prefs.getString("my_channels", "");
|
||||||
|
|
||||||
|
if (!channelsJson.isEmpty()) {
|
||||||
|
try {
|
||||||
|
// 简单解析JSON字符串
|
||||||
|
myChannels.clear();
|
||||||
|
channelsJson = channelsJson.trim();
|
||||||
|
if (channelsJson.startsWith("[") && channelsJson.endsWith("]")) {
|
||||||
|
channelsJson = channelsJson.substring(1, channelsJson.length() - 1);
|
||||||
|
if (!channelsJson.isEmpty()) {
|
||||||
|
String[] items = channelsJson.split("\\},\\{");
|
||||||
|
for (String item : items) {
|
||||||
|
item = item.replace("{", "").replace("}", "");
|
||||||
|
String[] parts = item.split(",");
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
int id = Integer.parseInt(parts[0].split(":")[1]);
|
||||||
|
String name = parts[1].split(":")[1].replace("\"", "");
|
||||||
|
myChannels.add(new ChannelTagAdapter.ChannelTag(id, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "加载我的频道配置: " + myChannels.size() + " 个频道");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "解析我的频道配置失败", e);
|
||||||
|
// 如果解析失败,使用默认配置
|
||||||
|
initDefaultMyChannels();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有保存的配置,使用默认配置
|
||||||
|
initDefaultMyChannels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化默认的我的频道配置
|
||||||
|
*/
|
||||||
|
private void initDefaultMyChannels() {
|
||||||
|
myChannels.clear();
|
||||||
|
myChannels.add(new ChannelTagAdapter.ChannelTag(0, "推荐"));
|
||||||
|
myChannels.add(new ChannelTagAdapter.ChannelTag(1, "直播"));
|
||||||
|
myChannels.add(new ChannelTagAdapter.ChannelTag(2, "视频"));
|
||||||
|
myChannels.add(new ChannelTagAdapter.ChannelTag(3, "音乐"));
|
||||||
|
Log.d(TAG, "使用默认我的频道配置");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,401 @@
|
||||||
|
package com.example.livestreaming;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import com.example.livestreaming.net.ApiClient;
|
||||||
|
import com.example.livestreaming.net.ApiResponse;
|
||||||
|
import com.example.livestreaming.net.PageResponse;
|
||||||
|
import com.example.livestreaming.net.Room;
|
||||||
|
import com.example.livestreaming.net.WorksResponse;
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 我的收藏页面 - 分类显示作品收藏和直播间收藏
|
||||||
|
*/
|
||||||
|
public class MyCollectionsActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private TabLayout tabLayout;
|
||||||
|
private ViewPager2 viewPager;
|
||||||
|
|
||||||
|
public static void start(Context context) {
|
||||||
|
Intent intent = new Intent(context, MyCollectionsActivity.class);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_my_collections);
|
||||||
|
|
||||||
|
setupToolbar();
|
||||||
|
setupViewPager();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupToolbar() {
|
||||||
|
androidx.appcompat.widget.Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
if (toolbar != null) {
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
toolbar.setNavigationOnClickListener(v -> finish());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupViewPager() {
|
||||||
|
tabLayout = findViewById(R.id.tabLayout);
|
||||||
|
viewPager = findViewById(R.id.viewPager);
|
||||||
|
|
||||||
|
CollectionsPagerAdapter adapter = new CollectionsPagerAdapter(this);
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
tab.setText("作品");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
tab.setText("直播间");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}).attach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewPager适配器
|
||||||
|
*/
|
||||||
|
private static class CollectionsPagerAdapter extends FragmentStateAdapter {
|
||||||
|
public CollectionsPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
|
||||||
|
super(fragmentActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment(int position) {
|
||||||
|
if (position == 0) {
|
||||||
|
return new CollectedWorksFragment();
|
||||||
|
} else {
|
||||||
|
return new CollectedRoomsFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收藏的作品Fragment
|
||||||
|
*/
|
||||||
|
public static class CollectedWorksFragment extends Fragment {
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
|
private View emptyView;
|
||||||
|
private View loadingView;
|
||||||
|
private WorksAdapter adapter;
|
||||||
|
private final List<WorksResponse> collectedWorks = new ArrayList<>();
|
||||||
|
private int currentPage = 1;
|
||||||
|
private boolean isLoading = false;
|
||||||
|
private boolean hasMore = true;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_list_content, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
initViews(view);
|
||||||
|
setupRecyclerView();
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViews(View view) {
|
||||||
|
recyclerView = view.findViewById(R.id.recyclerView);
|
||||||
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
|
||||||
|
emptyView = view.findViewById(R.id.emptyView);
|
||||||
|
loadingView = view.findViewById(R.id.loadingView);
|
||||||
|
|
||||||
|
ImageView emptyIcon = view.findViewById(R.id.emptyIcon);
|
||||||
|
TextView emptyText = view.findViewById(R.id.emptyText);
|
||||||
|
if (emptyIcon != null) emptyIcon.setImageResource(R.drawable.ic_star_24);
|
||||||
|
if (emptyText != null) emptyText.setText("还没有收藏过作品");
|
||||||
|
|
||||||
|
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||||
|
currentPage = 1;
|
||||||
|
hasMore = true;
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView() {
|
||||||
|
adapter = new WorksAdapter(work -> {
|
||||||
|
if (work != null && getActivity() != null) {
|
||||||
|
WorkDetailActivity.start(getActivity(), String.valueOf(work.getId()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (dy > 0 && !isLoading && hasMore) {
|
||||||
|
GridLayoutManager lm = (GridLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
if (lm != null) {
|
||||||
|
int total = lm.getItemCount();
|
||||||
|
int lastVisible = lm.findLastVisibleItemPosition();
|
||||||
|
if (lastVisible >= total - 4) {
|
||||||
|
currentPage++;
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadData() {
|
||||||
|
if (isLoading || getContext() == null) return;
|
||||||
|
isLoading = true;
|
||||||
|
if (currentPage == 1) showLoading();
|
||||||
|
|
||||||
|
ApiClient.getService(getContext())
|
||||||
|
.getMyCollectedWorks(currentPage, 20)
|
||||||
|
.enqueue(new Callback<ApiResponse<PageResponse<WorksResponse>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<PageResponse<WorksResponse>>> call,
|
||||||
|
Response<ApiResponse<PageResponse<WorksResponse>>> response) {
|
||||||
|
isLoading = false;
|
||||||
|
hideLoading();
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||||
|
PageResponse<WorksResponse> pageData = response.body().getData();
|
||||||
|
if (pageData != null && pageData.getList() != null) {
|
||||||
|
if (currentPage == 1) collectedWorks.clear();
|
||||||
|
collectedWorks.addAll(pageData.getList());
|
||||||
|
adapter.submitList(new ArrayList<>(collectedWorks));
|
||||||
|
hasMore = pageData.getList().size() >= 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateEmptyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<PageResponse<WorksResponse>>> call, Throwable t) {
|
||||||
|
isLoading = false;
|
||||||
|
hideLoading();
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
if (getContext() != null) {
|
||||||
|
Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLoading() {
|
||||||
|
if (loadingView != null) loadingView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideLoading() {
|
||||||
|
if (loadingView != null) loadingView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEmptyState() {
|
||||||
|
if (collectedWorks.isEmpty()) {
|
||||||
|
emptyView.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
emptyView.setVisibility(View.GONE);
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收藏的直播间Fragment(暂时复用点赞的直播间数据,后续可扩展)
|
||||||
|
*/
|
||||||
|
public static class CollectedRoomsFragment extends Fragment {
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
|
private View emptyView;
|
||||||
|
private View loadingView;
|
||||||
|
private RoomsAdapter adapter;
|
||||||
|
private final List<Room> collectedRooms = new ArrayList<>();
|
||||||
|
private int currentPage = 1;
|
||||||
|
private boolean isLoading = false;
|
||||||
|
private boolean hasMore = true;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_list_content, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
initViews(view);
|
||||||
|
setupRecyclerView();
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViews(View view) {
|
||||||
|
recyclerView = view.findViewById(R.id.recyclerView);
|
||||||
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
|
||||||
|
emptyView = view.findViewById(R.id.emptyView);
|
||||||
|
loadingView = view.findViewById(R.id.loadingView);
|
||||||
|
|
||||||
|
ImageView emptyIcon = view.findViewById(R.id.emptyIcon);
|
||||||
|
TextView emptyText = view.findViewById(R.id.emptyText);
|
||||||
|
if (emptyIcon != null) emptyIcon.setImageResource(R.drawable.ic_live_24);
|
||||||
|
if (emptyText != null) emptyText.setText("还没有收藏过直播间");
|
||||||
|
|
||||||
|
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||||
|
currentPage = 1;
|
||||||
|
hasMore = true;
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView() {
|
||||||
|
adapter = new RoomsAdapter(room -> {
|
||||||
|
if (room != null && getActivity() != null) {
|
||||||
|
Intent intent = new Intent(getActivity(), RoomDetailActivity.class);
|
||||||
|
intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (dy > 0 && !isLoading && hasMore) {
|
||||||
|
LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
if (lm != null) {
|
||||||
|
int total = lm.getItemCount();
|
||||||
|
int lastVisible = lm.findLastVisibleItemPosition();
|
||||||
|
if (lastVisible >= total - 2) {
|
||||||
|
currentPage++;
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadData() {
|
||||||
|
if (isLoading || getContext() == null) return;
|
||||||
|
isLoading = true;
|
||||||
|
if (currentPage == 1) showLoading();
|
||||||
|
|
||||||
|
// 暂时使用点赞的直播间接口,后续可以添加专门的收藏直播间接口
|
||||||
|
ApiClient.getService(getContext())
|
||||||
|
.getMyLikedRooms(currentPage, 20)
|
||||||
|
.enqueue(new Callback<ApiResponse<PageResponse<Map<String, Object>>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<PageResponse<Map<String, Object>>>> call,
|
||||||
|
Response<ApiResponse<PageResponse<Map<String, Object>>>> response) {
|
||||||
|
isLoading = false;
|
||||||
|
hideLoading();
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||||
|
PageResponse<Map<String, Object>> pageData = response.body().getData();
|
||||||
|
if (pageData != null && pageData.getList() != null) {
|
||||||
|
if (currentPage == 1) collectedRooms.clear();
|
||||||
|
collectedRooms.addAll(convertToRooms(pageData.getList()));
|
||||||
|
adapter.submitList(new ArrayList<>(collectedRooms));
|
||||||
|
hasMore = pageData.getList().size() >= 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateEmptyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<PageResponse<Map<String, Object>>>> call, Throwable t) {
|
||||||
|
isLoading = false;
|
||||||
|
hideLoading();
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
if (getContext() != null) {
|
||||||
|
Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Room> convertToRooms(List<Map<String, Object>> roomMaps) {
|
||||||
|
List<Room> rooms = new ArrayList<>();
|
||||||
|
for (Map<String, Object> map : roomMaps) {
|
||||||
|
Room room = new Room();
|
||||||
|
if (map.containsKey("roomId")) room.setId(map.get("roomId"));
|
||||||
|
if (map.containsKey("roomTitle")) room.setTitle((String) map.get("roomTitle"));
|
||||||
|
if (map.containsKey("streamerName")) room.setStreamerName((String) map.get("streamerName"));
|
||||||
|
if (map.containsKey("streamerId")) room.setStreamerId(((Number) map.get("streamerId")).intValue());
|
||||||
|
if (map.containsKey("coverImage")) room.setCoverImage((String) map.get("coverImage"));
|
||||||
|
if (map.containsKey("isLive")) {
|
||||||
|
Object isLive = map.get("isLive");
|
||||||
|
if (isLive instanceof Number) room.setLive(((Number) isLive).intValue() == 1);
|
||||||
|
else if (isLive instanceof Boolean) room.setLive((Boolean) isLive);
|
||||||
|
}
|
||||||
|
rooms.add(room);
|
||||||
|
}
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLoading() {
|
||||||
|
if (loadingView != null) loadingView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideLoading() {
|
||||||
|
if (loadingView != null) loadingView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEmptyState() {
|
||||||
|
if (collectedRooms.isEmpty()) {
|
||||||
|
emptyView.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
emptyView.setVisibility(View.GONE);
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,400 @@
|
||||||
|
package com.example.livestreaming;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import com.example.livestreaming.net.ApiClient;
|
||||||
|
import com.example.livestreaming.net.ApiResponse;
|
||||||
|
import com.example.livestreaming.net.PageResponse;
|
||||||
|
import com.example.livestreaming.net.Room;
|
||||||
|
import com.example.livestreaming.net.WorksResponse;
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 我的点赞页面 - 分类显示作品点赞和直播间点赞
|
||||||
|
*/
|
||||||
|
public class MyLikesActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private TabLayout tabLayout;
|
||||||
|
private ViewPager2 viewPager;
|
||||||
|
|
||||||
|
public static void start(Context context) {
|
||||||
|
Intent intent = new Intent(context, MyLikesActivity.class);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_my_likes);
|
||||||
|
|
||||||
|
setupToolbar();
|
||||||
|
setupViewPager();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupToolbar() {
|
||||||
|
androidx.appcompat.widget.Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
if (toolbar != null) {
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
toolbar.setNavigationOnClickListener(v -> finish());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupViewPager() {
|
||||||
|
tabLayout = findViewById(R.id.tabLayout);
|
||||||
|
viewPager = findViewById(R.id.viewPager);
|
||||||
|
|
||||||
|
LikesPagerAdapter adapter = new LikesPagerAdapter(this);
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
tab.setText("作品");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
tab.setText("直播间");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}).attach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewPager适配器
|
||||||
|
*/
|
||||||
|
private static class LikesPagerAdapter extends FragmentStateAdapter {
|
||||||
|
public LikesPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
|
||||||
|
super(fragmentActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment(int position) {
|
||||||
|
if (position == 0) {
|
||||||
|
return new LikedWorksFragment();
|
||||||
|
} else {
|
||||||
|
return new LikedRoomsFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞的作品Fragment
|
||||||
|
*/
|
||||||
|
public static class LikedWorksFragment extends Fragment {
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
|
private View emptyView;
|
||||||
|
private View loadingView;
|
||||||
|
private WorksAdapter adapter;
|
||||||
|
private final List<WorksResponse> likedWorks = new ArrayList<>();
|
||||||
|
private int currentPage = 1;
|
||||||
|
private boolean isLoading = false;
|
||||||
|
private boolean hasMore = true;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_list_content, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
initViews(view);
|
||||||
|
setupRecyclerView();
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViews(View view) {
|
||||||
|
recyclerView = view.findViewById(R.id.recyclerView);
|
||||||
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
|
||||||
|
emptyView = view.findViewById(R.id.emptyView);
|
||||||
|
loadingView = view.findViewById(R.id.loadingView);
|
||||||
|
|
||||||
|
ImageView emptyIcon = view.findViewById(R.id.emptyIcon);
|
||||||
|
TextView emptyText = view.findViewById(R.id.emptyText);
|
||||||
|
if (emptyIcon != null) emptyIcon.setImageResource(R.drawable.ic_like_filled_24);
|
||||||
|
if (emptyText != null) emptyText.setText("还没有点赞过作品");
|
||||||
|
|
||||||
|
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||||
|
currentPage = 1;
|
||||||
|
hasMore = true;
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView() {
|
||||||
|
adapter = new WorksAdapter(work -> {
|
||||||
|
if (work != null && getActivity() != null) {
|
||||||
|
WorkDetailActivity.start(getActivity(), String.valueOf(work.getId()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (dy > 0 && !isLoading && hasMore) {
|
||||||
|
GridLayoutManager lm = (GridLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
if (lm != null) {
|
||||||
|
int total = lm.getItemCount();
|
||||||
|
int lastVisible = lm.findLastVisibleItemPosition();
|
||||||
|
if (lastVisible >= total - 4) {
|
||||||
|
currentPage++;
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadData() {
|
||||||
|
if (isLoading || getContext() == null) return;
|
||||||
|
isLoading = true;
|
||||||
|
if (currentPage == 1) showLoading();
|
||||||
|
|
||||||
|
ApiClient.getService(getContext())
|
||||||
|
.getMyLikedWorks(currentPage, 20)
|
||||||
|
.enqueue(new Callback<ApiResponse<PageResponse<WorksResponse>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<PageResponse<WorksResponse>>> call,
|
||||||
|
Response<ApiResponse<PageResponse<WorksResponse>>> response) {
|
||||||
|
isLoading = false;
|
||||||
|
hideLoading();
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||||
|
PageResponse<WorksResponse> pageData = response.body().getData();
|
||||||
|
if (pageData != null && pageData.getList() != null) {
|
||||||
|
if (currentPage == 1) likedWorks.clear();
|
||||||
|
likedWorks.addAll(pageData.getList());
|
||||||
|
adapter.submitList(new ArrayList<>(likedWorks));
|
||||||
|
hasMore = pageData.getList().size() >= 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateEmptyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<PageResponse<WorksResponse>>> call, Throwable t) {
|
||||||
|
isLoading = false;
|
||||||
|
hideLoading();
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
if (getContext() != null) {
|
||||||
|
Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLoading() {
|
||||||
|
if (loadingView != null) loadingView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideLoading() {
|
||||||
|
if (loadingView != null) loadingView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEmptyState() {
|
||||||
|
if (likedWorks.isEmpty()) {
|
||||||
|
emptyView.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
emptyView.setVisibility(View.GONE);
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞的直播间Fragment
|
||||||
|
*/
|
||||||
|
public static class LikedRoomsFragment extends Fragment {
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
|
private View emptyView;
|
||||||
|
private View loadingView;
|
||||||
|
private RoomsAdapter adapter;
|
||||||
|
private final List<Room> likedRooms = new ArrayList<>();
|
||||||
|
private int currentPage = 1;
|
||||||
|
private boolean isLoading = false;
|
||||||
|
private boolean hasMore = true;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_list_content, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
initViews(view);
|
||||||
|
setupRecyclerView();
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViews(View view) {
|
||||||
|
recyclerView = view.findViewById(R.id.recyclerView);
|
||||||
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
|
||||||
|
emptyView = view.findViewById(R.id.emptyView);
|
||||||
|
loadingView = view.findViewById(R.id.loadingView);
|
||||||
|
|
||||||
|
ImageView emptyIcon = view.findViewById(R.id.emptyIcon);
|
||||||
|
TextView emptyText = view.findViewById(R.id.emptyText);
|
||||||
|
if (emptyIcon != null) emptyIcon.setImageResource(R.drawable.ic_live_24);
|
||||||
|
if (emptyText != null) emptyText.setText("还没有点赞过直播间");
|
||||||
|
|
||||||
|
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||||
|
currentPage = 1;
|
||||||
|
hasMore = true;
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView() {
|
||||||
|
adapter = new RoomsAdapter(room -> {
|
||||||
|
if (room != null && getActivity() != null) {
|
||||||
|
Intent intent = new Intent(getActivity(), RoomDetailActivity.class);
|
||||||
|
intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (dy > 0 && !isLoading && hasMore) {
|
||||||
|
LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
if (lm != null) {
|
||||||
|
int total = lm.getItemCount();
|
||||||
|
int lastVisible = lm.findLastVisibleItemPosition();
|
||||||
|
if (lastVisible >= total - 2) {
|
||||||
|
currentPage++;
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadData() {
|
||||||
|
if (isLoading || getContext() == null) return;
|
||||||
|
isLoading = true;
|
||||||
|
if (currentPage == 1) showLoading();
|
||||||
|
|
||||||
|
ApiClient.getService(getContext())
|
||||||
|
.getMyLikedRooms(currentPage, 20)
|
||||||
|
.enqueue(new Callback<ApiResponse<PageResponse<Map<String, Object>>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<PageResponse<Map<String, Object>>>> call,
|
||||||
|
Response<ApiResponse<PageResponse<Map<String, Object>>>> response) {
|
||||||
|
isLoading = false;
|
||||||
|
hideLoading();
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||||
|
PageResponse<Map<String, Object>> pageData = response.body().getData();
|
||||||
|
if (pageData != null && pageData.getList() != null) {
|
||||||
|
if (currentPage == 1) likedRooms.clear();
|
||||||
|
likedRooms.addAll(convertToRooms(pageData.getList()));
|
||||||
|
adapter.submitList(new ArrayList<>(likedRooms));
|
||||||
|
hasMore = pageData.getList().size() >= 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateEmptyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<PageResponse<Map<String, Object>>>> call, Throwable t) {
|
||||||
|
isLoading = false;
|
||||||
|
hideLoading();
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
if (getContext() != null) {
|
||||||
|
Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Room> convertToRooms(List<Map<String, Object>> roomMaps) {
|
||||||
|
List<Room> rooms = new ArrayList<>();
|
||||||
|
for (Map<String, Object> map : roomMaps) {
|
||||||
|
Room room = new Room();
|
||||||
|
if (map.containsKey("roomId")) room.setId(map.get("roomId"));
|
||||||
|
if (map.containsKey("roomTitle")) room.setTitle((String) map.get("roomTitle"));
|
||||||
|
if (map.containsKey("streamerName")) room.setStreamerName((String) map.get("streamerName"));
|
||||||
|
if (map.containsKey("streamerId")) room.setStreamerId(((Number) map.get("streamerId")).intValue());
|
||||||
|
if (map.containsKey("coverImage")) room.setCoverImage((String) map.get("coverImage"));
|
||||||
|
if (map.containsKey("isLive")) {
|
||||||
|
Object isLive = map.get("isLive");
|
||||||
|
if (isLive instanceof Number) room.setLive(((Number) isLive).intValue() == 1);
|
||||||
|
else if (isLive instanceof Boolean) room.setLive((Boolean) isLive);
|
||||||
|
}
|
||||||
|
rooms.add(room);
|
||||||
|
}
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLoading() {
|
||||||
|
if (loadingView != null) loadingView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideLoading() {
|
||||||
|
if (loadingView != null) loadingView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEmptyState() {
|
||||||
|
if (likedRooms.isEmpty()) {
|
||||||
|
emptyView.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
emptyView.setVisibility(View.GONE);
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,9 @@ import com.example.livestreaming.ShareUtils;
|
||||||
import com.example.livestreaming.location.TianDiTuLocationService;
|
import com.example.livestreaming.location.TianDiTuLocationService;
|
||||||
import com.example.livestreaming.net.ApiClient;
|
import com.example.livestreaming.net.ApiClient;
|
||||||
import com.example.livestreaming.net.ApiResponse;
|
import com.example.livestreaming.net.ApiResponse;
|
||||||
|
import com.example.livestreaming.net.PageResponse;
|
||||||
import com.example.livestreaming.net.UserInfoResponse;
|
import com.example.livestreaming.net.UserInfoResponse;
|
||||||
|
import com.example.livestreaming.net.WorksResponse;
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
|
||||||
|
|
@ -70,6 +72,7 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private ActivityResultLauncher<Intent> editProfileLauncher;
|
private ActivityResultLauncher<Intent> editProfileLauncher;
|
||||||
private UserWorksAdapter worksAdapter;
|
private UserWorksAdapter worksAdapter;
|
||||||
|
private WorksAdapter myWorksAdapter;
|
||||||
|
|
||||||
public static void start(Context context) {
|
public static void start(Context context) {
|
||||||
Intent intent = new Intent(context, ProfileActivity.class);
|
Intent intent = new Intent(context, ProfileActivity.class);
|
||||||
|
|
@ -437,13 +440,19 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
startActivity(new Intent(this, FollowingActivity.class));
|
startActivity(new Intent(this, FollowingActivity.class));
|
||||||
});
|
});
|
||||||
binding.action2.setOnClickListener(v -> {
|
binding.action2.setOnClickListener(v -> {
|
||||||
// 我的收藏(点赞的直播间)
|
// 我的点赞(作品+直播间)
|
||||||
|
if (!AuthHelper.requireLogin(this, "查看点赞需要登录")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MyLikesActivity.start(this);
|
||||||
|
});
|
||||||
|
binding.action3.setOnClickListener(v -> {
|
||||||
|
// 我的收藏(作品+直播间)
|
||||||
if (!AuthHelper.requireLogin(this, "查看收藏需要登录")) {
|
if (!AuthHelper.requireLogin(this, "查看收藏需要登录")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
startActivity(new Intent(this, LikedRoomsActivity.class));
|
MyCollectionsActivity.start(this);
|
||||||
});
|
});
|
||||||
binding.action3.setOnClickListener(v -> startActivity(new Intent(this, MyFriendsActivity.class)));
|
|
||||||
binding.action4.setOnClickListener(v -> {
|
binding.action4.setOnClickListener(v -> {
|
||||||
// 我的记录 - 跳转到统一记录页面
|
// 我的记录 - 跳转到统一记录页面
|
||||||
if (!AuthHelper.requireLogin(this, "查看记录需要登录")) {
|
if (!AuthHelper.requireLogin(this, "查看记录需要登录")) {
|
||||||
|
|
@ -474,11 +483,11 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
});
|
});
|
||||||
|
|
||||||
binding.addFriendBtn.setOnClickListener(v -> {
|
binding.addFriendBtn.setOnClickListener(v -> {
|
||||||
// 检查登录状态,添加好友需要登录
|
// 我的挚友(原添加好友功能已在挚友页面内)
|
||||||
if (!AuthHelper.requireLogin(this, "添加好友需要登录")) {
|
if (!AuthHelper.requireLogin(this, "查看挚友需要登录")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AddFriendActivity.start(this);
|
startActivity(new Intent(this, MyFriendsActivity.class));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 我的钱包按钮点击事件
|
// 我的钱包按钮点击事件
|
||||||
|
|
@ -554,45 +563,91 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupWorksRecycler() {
|
private void setupWorksRecycler() {
|
||||||
worksAdapter = new UserWorksAdapter();
|
// 设置我的作品区域
|
||||||
worksAdapter.setOnWorkClickListener(workItem -> {
|
myWorksAdapter = new WorksAdapter(work -> {
|
||||||
if (workItem != null && !TextUtils.isEmpty(workItem.getId())) {
|
if (work != null && work.getId() != null) {
|
||||||
WorkDetailActivity.start(this, workItem.getId());
|
WorkDetailActivity.start(this, String.valueOf(work.getId()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
binding.worksRecycler.setLayoutManager(new GridLayoutManager(this, 3));
|
binding.myWorksRecycler.setLayoutManager(new GridLayoutManager(this, 2));
|
||||||
binding.worksRecycler.setAdapter(worksAdapter);
|
binding.myWorksRecycler.setAdapter(myWorksAdapter);
|
||||||
loadWorks();
|
|
||||||
|
// 发布按钮点击事件
|
||||||
|
binding.myWorksPublishBtn.setOnClickListener(v -> {
|
||||||
|
if (!AuthHelper.requireLogin(this, "发布作品需要登录")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PublishWorkActivity.start(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadMyWorks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMyWorks() {
|
||||||
|
if (!AuthHelper.isLoggedIn(this)) {
|
||||||
|
// 未登录时显示空状态
|
||||||
|
binding.myWorksRecycler.setVisibility(View.GONE);
|
||||||
|
binding.myWorksEmptyState.setVisibility(View.VISIBLE);
|
||||||
|
binding.myWorksCount.setText("0个作品");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户ID
|
||||||
|
String userIdStr = com.example.livestreaming.net.AuthStore.getUserId(this);
|
||||||
|
if (userIdStr == null || userIdStr.isEmpty()) {
|
||||||
|
Log.e(TAG, "无法获取用户ID");
|
||||||
|
showMyWorksEmpty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int userId;
|
||||||
|
try {
|
||||||
|
userId = Integer.parseInt(userIdStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e(TAG, "用户ID格式错误: " + userIdStr);
|
||||||
|
showMyWorksEmpty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiClient.getService(this).getUserWorks(userId, 1, 50)
|
||||||
|
.enqueue(new Callback<ApiResponse<PageResponse<WorksResponse>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<PageResponse<WorksResponse>>> call,
|
||||||
|
Response<ApiResponse<PageResponse<WorksResponse>>> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||||
|
PageResponse<WorksResponse> pageData = response.body().getData();
|
||||||
|
if (pageData != null && pageData.getList() != null && !pageData.getList().isEmpty()) {
|
||||||
|
List<WorksResponse> works = pageData.getList();
|
||||||
|
binding.myWorksRecycler.setVisibility(View.VISIBLE);
|
||||||
|
binding.myWorksEmptyState.setVisibility(View.GONE);
|
||||||
|
binding.myWorksCount.setText(works.size() + "个作品");
|
||||||
|
myWorksAdapter.submitList(works);
|
||||||
|
} else {
|
||||||
|
showMyWorksEmpty();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "加载我的作品失败: " + (response.body() != null ? response.body().getMessage() : "未知错误"));
|
||||||
|
showMyWorksEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<PageResponse<WorksResponse>>> call, Throwable t) {
|
||||||
|
Log.e(TAG, "加载我的作品失败: " + t.getMessage());
|
||||||
|
showMyWorksEmpty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMyWorksEmpty() {
|
||||||
|
binding.myWorksRecycler.setVisibility(View.GONE);
|
||||||
|
binding.myWorksEmptyState.setVisibility(View.VISIBLE);
|
||||||
|
binding.myWorksCount.setText("0个作品");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadWorks() {
|
private void loadWorks() {
|
||||||
// TODO: 接入后端接口 - 获取当前用户的作品列表
|
// 旧方法保留兼容,实际使用loadMyWorks
|
||||||
// 接口路径: GET /api/users/{userId}/works
|
loadMyWorks();
|
||||||
// 请求方法: GET
|
|
||||||
// 请求头: Authorization: Bearer {token} (必填,从AuthStore获取)
|
|
||||||
// 路径参数:
|
|
||||||
// - userId: String (必填) - 当前用户ID(从token中解析获取)
|
|
||||||
// 请求参数(Query):
|
|
||||||
// - page: int (可选,默认1) - 页码
|
|
||||||
// - pageSize: int (可选,默认20) - 每页数量
|
|
||||||
// 返回数据格式: ApiResponse<PageResult<WorkItem>>
|
|
||||||
// 实现步骤:
|
|
||||||
// 1. 从AuthStore获取token,解析userId(或从用户信息中获取)
|
|
||||||
// 2. 调用接口获取作品列表
|
|
||||||
// 3. 更新UI显示作品列表或空状态
|
|
||||||
// 4. 处理错误情况(网络错误、未登录等)
|
|
||||||
// 注意: 此方法在onResume时也会调用,需要避免重复请求
|
|
||||||
|
|
||||||
// 临时:从本地存储加载(等待后端接口)
|
|
||||||
List<WorkItem> works = WorkManager.getAllWorks(this);
|
|
||||||
if (works != null && !works.isEmpty()) {
|
|
||||||
binding.worksRecycler.setVisibility(View.VISIBLE);
|
|
||||||
binding.worksEmptyState.setVisibility(View.GONE);
|
|
||||||
worksAdapter.submitList(works);
|
|
||||||
} else {
|
|
||||||
binding.worksRecycler.setVisibility(View.GONE);
|
|
||||||
binding.worksEmptyState.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showTab(int index) {
|
private void showTab(int index) {
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,10 @@ public class PublishWorkActivity extends AppCompatActivity {
|
||||||
private String selectedVisibility = "所有人可见"; // 可见范围
|
private String selectedVisibility = "所有人可见"; // 可见范围
|
||||||
private String selectedCommentSetting = "所有人可评论"; // 评论设置
|
private String selectedCommentSetting = "所有人可评论"; // 评论设置
|
||||||
|
|
||||||
|
// 分类选择相关
|
||||||
|
private final List<com.example.livestreaming.net.CategoryResponse> allCategories = new ArrayList<>();
|
||||||
|
private com.example.livestreaming.net.CategoryResponse selectedCategory = null;
|
||||||
|
|
||||||
// 天地图定位服务继续
|
// 天地图定位服务继续
|
||||||
private TianDiTuLocationService locationService;
|
private TianDiTuLocationService locationService;
|
||||||
private ActivityResultLauncher<String[]> requestLocationPermissionLauncher;
|
private ActivityResultLauncher<String[]> requestLocationPermissionLauncher;
|
||||||
|
|
@ -112,6 +116,7 @@ public class PublishWorkActivity extends AppCompatActivity {
|
||||||
setupMediaAdapter();
|
setupMediaAdapter();
|
||||||
setupLaunchers();
|
setupLaunchers();
|
||||||
setupClickListeners();
|
setupClickListeners();
|
||||||
|
loadCategories(); // 加载分类数据
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupToolbar() {
|
private void setupToolbar() {
|
||||||
|
|
@ -347,6 +352,9 @@ public class PublishWorkActivity extends AppCompatActivity {
|
||||||
binding.selectCoverButton.setOnClickListener(v -> showCoverPickerDialog());
|
binding.selectCoverButton.setOnClickListener(v -> showCoverPickerDialog());
|
||||||
binding.publishButton.setOnClickListener(v -> publishWork());
|
binding.publishButton.setOnClickListener(v -> publishWork());
|
||||||
|
|
||||||
|
// 分类选择点击事件
|
||||||
|
binding.categorySpinner.setOnClickListener(v -> showCategoryPickerDialog());
|
||||||
|
|
||||||
// 封面预览点击也可以选择封面
|
// 封面预览点击也可以选择封面
|
||||||
binding.coverPreview.setOnClickListener(v -> {
|
binding.coverPreview.setOnClickListener(v -> {
|
||||||
showCoverPickerDialog();
|
showCoverPickerDialog();
|
||||||
|
|
@ -964,6 +972,11 @@ public class PublishWorkActivity extends AppCompatActivity {
|
||||||
commentSettingValue = "DISABLED";
|
commentSettingValue = "DISABLED";
|
||||||
}
|
}
|
||||||
request.setCommentSetting(commentSettingValue);
|
request.setCommentSetting(commentSettingValue);
|
||||||
|
|
||||||
|
// 设置分类ID
|
||||||
|
if (selectedCategory != null) {
|
||||||
|
request.setCategoryId(selectedCategory.getId());
|
||||||
|
}
|
||||||
|
|
||||||
ApiService apiService = ApiClient.getService(this);
|
ApiService apiService = ApiClient.getService(this);
|
||||||
Call<ApiResponse<Long>> call = apiService.publishWork(request);
|
Call<ApiResponse<Long>> call = apiService.publishWork(request);
|
||||||
|
|
@ -1488,5 +1501,101 @@ public class PublishWorkActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 加载分类数据
|
||||||
|
*/
|
||||||
|
private void loadCategories() {
|
||||||
|
ApiService apiService = ApiClient.getService(this);
|
||||||
|
if (apiService == null) {
|
||||||
|
android.util.Log.e("PublishWork", "ApiService 为空,无法加载分类");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Call<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>> call =
|
||||||
|
apiService.getLiveRoomCategories();
|
||||||
|
|
||||||
|
call.enqueue(new retrofit2.Callback<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>> call,
|
||||||
|
retrofit2.Response<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>> response) {
|
||||||
|
try {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
ApiResponse<List<com.example.livestreaming.net.CategoryResponse>> apiResponse = response.body();
|
||||||
|
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
|
||||||
|
allCategories.clear();
|
||||||
|
allCategories.addAll(apiResponse.getData());
|
||||||
|
android.util.Log.d("PublishWork", "加载分类成功: " + allCategories.size() + " 个分类");
|
||||||
|
} else {
|
||||||
|
android.util.Log.w("PublishWork", "分类数据为空或API返回错误");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
android.util.Log.e("PublishWork", "加载分类失败: " + response.code());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
android.util.Log.e("PublishWork", "处理分类数据异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>> call, Throwable t) {
|
||||||
|
android.util.Log.e("PublishWork", "加载分类网络错误", t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示分类选择对话框(单选)
|
||||||
|
*/
|
||||||
|
private void showCategoryPickerDialog() {
|
||||||
|
if (allCategories.isEmpty()) {
|
||||||
|
Toast.makeText(this, "分类数据加载中,请稍后再试", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建分类名称数组
|
||||||
|
String[] categoryNames = new String[allCategories.size()];
|
||||||
|
int selectedIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < allCategories.size(); i++) {
|
||||||
|
com.example.livestreaming.net.CategoryResponse category = allCategories.get(i);
|
||||||
|
categoryNames[i] = category.getName();
|
||||||
|
// 检查是否是当前选中的分类
|
||||||
|
if (selectedCategory != null && selectedCategory.getId().equals(category.getId())) {
|
||||||
|
selectedIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle("选择分类");
|
||||||
|
|
||||||
|
builder.setSingleChoiceItems(categoryNames, selectedIndex, (dialog, which) -> {
|
||||||
|
selectedCategory = allCategories.get(which);
|
||||||
|
updateCategoryDisplay();
|
||||||
|
Toast.makeText(this, "已选择分类:" + selectedCategory.getName(), Toast.LENGTH_SHORT).show();
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.setNegativeButton("取消", null);
|
||||||
|
|
||||||
|
builder.setNeutralButton("清空", (dialog, which) -> {
|
||||||
|
selectedCategory = null;
|
||||||
|
updateCategoryDisplay();
|
||||||
|
Toast.makeText(this, "已清空分类选择", Toast.LENGTH_SHORT).show();
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新分类显示
|
||||||
|
*/
|
||||||
|
private void updateCategoryDisplay() {
|
||||||
|
if (selectedCategory == null) {
|
||||||
|
binding.categorySpinner.setText("");
|
||||||
|
binding.categorySpinner.setHint("请选择分类");
|
||||||
|
} else {
|
||||||
|
binding.categorySpinner.setText(selectedCategory.getName());
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import com.example.livestreaming.net.HotSearchResponse;
|
||||||
import com.example.livestreaming.net.PageResponse;
|
import com.example.livestreaming.net.PageResponse;
|
||||||
import com.example.livestreaming.net.ApiClient;
|
import com.example.livestreaming.net.ApiClient;
|
||||||
import com.example.livestreaming.net.Room;
|
import com.example.livestreaming.net.Room;
|
||||||
|
import com.example.livestreaming.net.WorksResponse;
|
||||||
|
import com.example.livestreaming.net.SearchUserResponse;
|
||||||
import com.google.android.flexbox.FlexboxLayout;
|
import com.google.android.flexbox.FlexboxLayout;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
|
|
@ -47,10 +49,14 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
// 主播列表
|
// 主播列表
|
||||||
private SearchStreamerAdapter streamersAdapter;
|
private SearchStreamerAdapter streamersAdapter;
|
||||||
private final List<Map<String, Object>> streamersList = new ArrayList<>();
|
private final List<Map<String, Object>> streamersList = new ArrayList<>();
|
||||||
|
|
||||||
|
// 作品列表
|
||||||
|
private WorksAdapter worksAdapter;
|
||||||
|
private final List<WorksResponse> worksList = new ArrayList<>();
|
||||||
|
|
||||||
private boolean isSearching = false;
|
private boolean isSearching = false;
|
||||||
private String lastSearchKeyword = "";
|
private String lastSearchKeyword = "";
|
||||||
private int currentTab = 0; // 0=直播间, 1=主播
|
private int currentTab = 0; // 0=直播间, 1=主播, 2=作品
|
||||||
|
|
||||||
private static final String EXTRA_SEARCH_QUERY = "search_query";
|
private static final String EXTRA_SEARCH_QUERY = "search_query";
|
||||||
|
|
||||||
|
|
@ -118,11 +124,11 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
streamersAdapter.setOnStreamerClickListener(new SearchStreamerAdapter.OnStreamerClickListener() {
|
streamersAdapter.setOnStreamerClickListener(new SearchStreamerAdapter.OnStreamerClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onStreamerClick(Map<String, Object> streamer) {
|
public void onStreamerClick(Map<String, Object> streamer) {
|
||||||
// 点击主播,跳转到主播主页
|
// 点击用户,跳转到用户主页
|
||||||
Object id = streamer.get("id");
|
Object id = streamer.get("id");
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
int streamerId = ((Number) id).intValue();
|
int userId = ((Number) id).intValue();
|
||||||
UserProfileActivity.start(SearchActivity.this, streamerId);
|
UserProfileReadOnlyActivity.start(SearchActivity.this, userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,12 +141,24 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
|
|
||||||
binding.streamersRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
binding.streamersRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||||
binding.streamersRecyclerView.setAdapter(streamersAdapter);
|
binding.streamersRecyclerView.setAdapter(streamersAdapter);
|
||||||
|
|
||||||
|
// 作品适配器
|
||||||
|
worksAdapter = new WorksAdapter(work -> {
|
||||||
|
if (work == null) return;
|
||||||
|
WorkDetailActivity.start(SearchActivity.this, String.valueOf(work.getId()));
|
||||||
|
});
|
||||||
|
|
||||||
|
StaggeredGridLayoutManager worksLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
|
||||||
|
worksLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
|
||||||
|
binding.worksRecyclerView.setLayoutManager(worksLayoutManager);
|
||||||
|
binding.worksRecyclerView.setAdapter(worksAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupTabs() {
|
private void setupTabs() {
|
||||||
// 添加Tab
|
// 添加Tab
|
||||||
binding.searchTabs.addTab(binding.searchTabs.newTab().setText("直播间"));
|
binding.searchTabs.addTab(binding.searchTabs.newTab().setText("直播间"));
|
||||||
binding.searchTabs.addTab(binding.searchTabs.newTab().setText("主播"));
|
binding.searchTabs.addTab(binding.searchTabs.newTab().setText("用户"));
|
||||||
|
binding.searchTabs.addTab(binding.searchTabs.newTab().setText("作品"));
|
||||||
|
|
||||||
binding.searchTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
binding.searchTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -216,6 +234,149 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
binding.hotSearchContainer.setVisibility(View.GONE);
|
binding.hotSearchContainer.setVisibility(View.GONE);
|
||||||
binding.emptyStateView.setVisibility(View.GONE);
|
binding.emptyStateView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// 清空之前的结果
|
||||||
|
roomsList.clear();
|
||||||
|
streamersList.clear();
|
||||||
|
worksList.clear();
|
||||||
|
|
||||||
|
ApiService apiService = ApiClient.getService(this);
|
||||||
|
|
||||||
|
// 同时搜索直播间+用户和作品
|
||||||
|
final String searchKeyword = keyword;
|
||||||
|
final int[] completedCount = {0};
|
||||||
|
final int totalRequests = 2;
|
||||||
|
|
||||||
|
// 搜索直播间和用户(使用原来的综合搜索API)
|
||||||
|
Call<ApiResponse<Map<String, Object>>> roomsCall =
|
||||||
|
apiService.comprehensiveSearch(searchKeyword, 1, 20);
|
||||||
|
roomsCall.enqueue(new Callback<ApiResponse<Map<String, Object>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
|
||||||
|
Response<ApiResponse<Map<String, Object>>> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
ApiResponse<Map<String, Object>> apiResponse = response.body();
|
||||||
|
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
|
||||||
|
Map<String, Object> data = apiResponse.getData();
|
||||||
|
|
||||||
|
// 解析直播间列表
|
||||||
|
Object roomsObj = data.get("rooms");
|
||||||
|
if (roomsObj instanceof List) {
|
||||||
|
List<?> rooms = (List<?>) roomsObj;
|
||||||
|
for (Object item : rooms) {
|
||||||
|
if (item instanceof Map) {
|
||||||
|
Room room = parseRoomFromMap((Map<String, Object>) item);
|
||||||
|
if (room != null) {
|
||||||
|
roomsList.add(room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析用户列表(使用原来的streamers字段)
|
||||||
|
Object streamersObj = data.get("streamers");
|
||||||
|
if (streamersObj instanceof List) {
|
||||||
|
List<?> streamers = (List<?>) streamersObj;
|
||||||
|
for (Object item : streamers) {
|
||||||
|
if (item instanceof Map) {
|
||||||
|
streamersList.add((Map<String, Object>) item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completedCount[0]++;
|
||||||
|
if (completedCount[0] >= totalRequests) {
|
||||||
|
onSearchCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
|
||||||
|
Log.e(TAG, "搜索直播间和用户失败", t);
|
||||||
|
completedCount[0]++;
|
||||||
|
if (completedCount[0] >= totalRequests) {
|
||||||
|
onSearchCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 搜索作品
|
||||||
|
Map<String, Object> worksRequest = new HashMap<>();
|
||||||
|
worksRequest.put("keyword", searchKeyword);
|
||||||
|
worksRequest.put("page", 1);
|
||||||
|
worksRequest.put("pageSize", 20);
|
||||||
|
|
||||||
|
Call<ApiResponse<PageResponse<WorksResponse>>> worksCall =
|
||||||
|
apiService.searchWorks(worksRequest);
|
||||||
|
worksCall.enqueue(new Callback<ApiResponse<PageResponse<WorksResponse>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<PageResponse<WorksResponse>>> call,
|
||||||
|
Response<ApiResponse<PageResponse<WorksResponse>>> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
ApiResponse<PageResponse<WorksResponse>> apiResponse = response.body();
|
||||||
|
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
|
||||||
|
List<WorksResponse> works = apiResponse.getData().getList();
|
||||||
|
if (works != null) {
|
||||||
|
worksList.addAll(works);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completedCount[0]++;
|
||||||
|
if (completedCount[0] >= totalRequests) {
|
||||||
|
onSearchCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<PageResponse<WorksResponse>>> call, Throwable t) {
|
||||||
|
Log.e(TAG, "搜索作品失败", t);
|
||||||
|
completedCount[0]++;
|
||||||
|
if (completedCount[0] >= totalRequests) {
|
||||||
|
onSearchCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索完成后的处理
|
||||||
|
*/
|
||||||
|
private void onSearchCompleted() {
|
||||||
|
isSearching = false;
|
||||||
|
binding.loadingProgress.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
int roomsTotal = roomsList.size();
|
||||||
|
int usersTotal = streamersList.size();
|
||||||
|
int worksTotal = worksList.size();
|
||||||
|
|
||||||
|
Log.d(TAG, "搜索完成,直播间: " + roomsTotal + ", 用户: " + usersTotal + ", 作品: " + worksTotal);
|
||||||
|
|
||||||
|
// 更新Tab标题显示数量
|
||||||
|
updateTabTitles(roomsTotal, usersTotal, worksTotal);
|
||||||
|
|
||||||
|
// 显示搜索结果
|
||||||
|
showSearchResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行综合搜索(旧方法,保留备用)
|
||||||
|
*/
|
||||||
|
private void performSearchOld(String keyword) {
|
||||||
|
if (keyword == null || keyword.trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyword = keyword.trim();
|
||||||
|
|
||||||
|
Log.d(TAG, "执行综合搜索: " + keyword);
|
||||||
|
|
||||||
|
// 显示加载状态
|
||||||
|
binding.loadingProgress.setVisibility(View.VISIBLE);
|
||||||
|
binding.hotSearchContainer.setVisibility(View.GONE);
|
||||||
|
binding.emptyStateView.setVisibility(View.GONE);
|
||||||
|
|
||||||
ApiService apiService = ApiClient.getService(this);
|
ApiService apiService = ApiClient.getService(this);
|
||||||
Call<ApiResponse<Map<String, Object>>> call =
|
Call<ApiResponse<Map<String, Object>>> call =
|
||||||
apiService.comprehensiveSearch(keyword, 1, 20);
|
apiService.comprehensiveSearch(keyword, 1, 20);
|
||||||
|
|
@ -263,11 +424,12 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
// 获取总数
|
// 获取总数
|
||||||
int roomsTotal = getIntValue(data.get("roomsTotal"), 0);
|
int roomsTotal = getIntValue(data.get("roomsTotal"), 0);
|
||||||
int streamersTotal = getIntValue(data.get("streamersTotal"), 0);
|
int streamersTotal = getIntValue(data.get("streamersTotal"), 0);
|
||||||
|
int worksTotal = 0; // 旧方法没有作品数据
|
||||||
|
|
||||||
Log.d(TAG, "搜索成功,直播间: " + roomsTotal + ", 主播: " + streamersTotal);
|
Log.d(TAG, "搜索成功,直播间: " + roomsTotal + ", 主播: " + streamersTotal);
|
||||||
|
|
||||||
// 更新Tab标题显示数量
|
// 更新Tab标题显示数量
|
||||||
updateTabTitles(roomsTotal, streamersTotal);
|
updateTabTitles(roomsTotal, streamersTotal, worksTotal);
|
||||||
|
|
||||||
// 显示搜索结果
|
// 显示搜索结果
|
||||||
showSearchResults();
|
showSearchResults();
|
||||||
|
|
@ -301,15 +463,19 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
/**
|
/**
|
||||||
* 更新Tab标题显示数量
|
* 更新Tab标题显示数量
|
||||||
*/
|
*/
|
||||||
private void updateTabTitles(int roomsCount, int streamersCount) {
|
private void updateTabTitles(int roomsCount, int streamersCount, int worksCount) {
|
||||||
TabLayout.Tab roomsTab = binding.searchTabs.getTabAt(0);
|
TabLayout.Tab roomsTab = binding.searchTabs.getTabAt(0);
|
||||||
TabLayout.Tab streamersTab = binding.searchTabs.getTabAt(1);
|
TabLayout.Tab streamersTab = binding.searchTabs.getTabAt(1);
|
||||||
|
TabLayout.Tab worksTab = binding.searchTabs.getTabAt(2);
|
||||||
|
|
||||||
if (roomsTab != null) {
|
if (roomsTab != null) {
|
||||||
roomsTab.setText("直播间 " + roomsCount);
|
roomsTab.setText("直播间 " + roomsCount);
|
||||||
}
|
}
|
||||||
if (streamersTab != null) {
|
if (streamersTab != null) {
|
||||||
streamersTab.setText("主播 " + streamersCount);
|
streamersTab.setText("用户 " + streamersCount);
|
||||||
|
}
|
||||||
|
if (worksTab != null) {
|
||||||
|
worksTab.setText("作品 " + worksCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,6 +489,7 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
// 更新列表数据
|
// 更新列表数据
|
||||||
roomsAdapter.submitList(new ArrayList<>(roomsList));
|
roomsAdapter.submitList(new ArrayList<>(roomsList));
|
||||||
streamersAdapter.submitList(new ArrayList<>(streamersList));
|
streamersAdapter.submitList(new ArrayList<>(streamersList));
|
||||||
|
worksAdapter.submitList(new ArrayList<>(worksList));
|
||||||
|
|
||||||
// 根据当前Tab显示对应内容
|
// 根据当前Tab显示对应内容
|
||||||
updateTabContent();
|
updateTabContent();
|
||||||
|
|
@ -336,6 +503,7 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
// 显示直播间
|
// 显示直播间
|
||||||
binding.roomsRecyclerView.setVisibility(View.VISIBLE);
|
binding.roomsRecyclerView.setVisibility(View.VISIBLE);
|
||||||
binding.streamersRecyclerView.setVisibility(View.GONE);
|
binding.streamersRecyclerView.setVisibility(View.GONE);
|
||||||
|
binding.worksRecyclerView.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (roomsList.isEmpty()) {
|
if (roomsList.isEmpty()) {
|
||||||
binding.emptyStateView.setNoSearchResultsState();
|
binding.emptyStateView.setNoSearchResultsState();
|
||||||
|
|
@ -343,10 +511,11 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
} else {
|
} else {
|
||||||
binding.emptyStateView.setVisibility(View.GONE);
|
binding.emptyStateView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (currentTab == 1) {
|
||||||
// 显示主播
|
// 显示主播
|
||||||
binding.roomsRecyclerView.setVisibility(View.GONE);
|
binding.roomsRecyclerView.setVisibility(View.GONE);
|
||||||
binding.streamersRecyclerView.setVisibility(View.VISIBLE);
|
binding.streamersRecyclerView.setVisibility(View.VISIBLE);
|
||||||
|
binding.worksRecyclerView.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (streamersList.isEmpty()) {
|
if (streamersList.isEmpty()) {
|
||||||
binding.emptyStateView.setNoSearchResultsState();
|
binding.emptyStateView.setNoSearchResultsState();
|
||||||
|
|
@ -354,6 +523,18 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
} else {
|
} else {
|
||||||
binding.emptyStateView.setVisibility(View.GONE);
|
binding.emptyStateView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 显示作品
|
||||||
|
binding.roomsRecyclerView.setVisibility(View.GONE);
|
||||||
|
binding.streamersRecyclerView.setVisibility(View.GONE);
|
||||||
|
binding.worksRecyclerView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (worksList.isEmpty()) {
|
||||||
|
binding.emptyStateView.setNoSearchResultsState();
|
||||||
|
binding.emptyStateView.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
binding.emptyStateView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -364,14 +545,17 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
binding.searchTabs.setVisibility(View.GONE);
|
binding.searchTabs.setVisibility(View.GONE);
|
||||||
binding.roomsRecyclerView.setVisibility(View.GONE);
|
binding.roomsRecyclerView.setVisibility(View.GONE);
|
||||||
binding.streamersRecyclerView.setVisibility(View.GONE);
|
binding.streamersRecyclerView.setVisibility(View.GONE);
|
||||||
|
binding.worksRecyclerView.setVisibility(View.GONE);
|
||||||
binding.emptyStateView.setVisibility(View.GONE);
|
binding.emptyStateView.setVisibility(View.GONE);
|
||||||
binding.hotSearchContainer.setVisibility(View.VISIBLE);
|
binding.hotSearchContainer.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// 重置Tab标题
|
// 重置Tab标题
|
||||||
TabLayout.Tab roomsTab = binding.searchTabs.getTabAt(0);
|
TabLayout.Tab roomsTab = binding.searchTabs.getTabAt(0);
|
||||||
TabLayout.Tab streamersTab = binding.searchTabs.getTabAt(1);
|
TabLayout.Tab streamersTab = binding.searchTabs.getTabAt(1);
|
||||||
|
TabLayout.Tab worksTab = binding.searchTabs.getTabAt(2);
|
||||||
if (roomsTab != null) roomsTab.setText("直播间");
|
if (roomsTab != null) roomsTab.setText("直播间");
|
||||||
if (streamersTab != null) streamersTab.setText("主播");
|
if (streamersTab != null) streamersTab.setText("用户");
|
||||||
|
if (worksTab != null) worksTab.setText("作品");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -451,21 +635,21 @@ public class SearchActivity extends AppCompatActivity {
|
||||||
* 处理关注点击
|
* 处理关注点击
|
||||||
*/
|
*/
|
||||||
private void handleFollowClick(Map<String, Object> streamer, int position) {
|
private void handleFollowClick(Map<String, Object> streamer, int position) {
|
||||||
if (!AuthHelper.requireLogin(this, "关注主播需要登录")) {
|
if (!AuthHelper.requireLogin(this, "关注用户需要登录")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object id = streamer.get("id");
|
Object id = streamer.get("id");
|
||||||
if (id == null) return;
|
if (id == null) return;
|
||||||
|
|
||||||
int streamerId = ((Number) id).intValue();
|
int userId = ((Number) id).intValue();
|
||||||
Object isFollowing = streamer.get("isFollowing");
|
Object isFollowing = streamer.get("isFollowing");
|
||||||
boolean following = isFollowing instanceof Boolean && (Boolean) isFollowing;
|
boolean following = isFollowing instanceof Boolean && (Boolean) isFollowing;
|
||||||
|
|
||||||
String action = following ? "unfollow" : "follow";
|
String action = following ? "unfollow" : "follow";
|
||||||
|
|
||||||
Map<String, Object> body = new HashMap<>();
|
Map<String, Object> body = new HashMap<>();
|
||||||
body.put("followedId", streamerId);
|
body.put("userId", userId);
|
||||||
|
|
||||||
ApiService apiService = ApiClient.getService(this);
|
ApiService apiService = ApiClient.getService(this);
|
||||||
Call<ApiResponse<Map<String, Object>>> call = following ?
|
Call<ApiResponse<Map<String, Object>>> call = following ?
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,15 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只传userId启动,其他信息从后端获取
|
||||||
|
*/
|
||||||
|
public static void start(Context context, int userId) {
|
||||||
|
Intent intent = new Intent(context, UserProfileReadOnlyActivity.class);
|
||||||
|
intent.putExtra(EXTRA_USER_ID, String.valueOf(userId));
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
@ -122,6 +131,9 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
|
||||||
// 检查关注状态
|
// 检查关注状态
|
||||||
checkFollowStatus();
|
checkFollowStatus();
|
||||||
|
|
||||||
|
// 初始化关注按钮显示(默认显示"关注")
|
||||||
|
updateFollowButton();
|
||||||
|
|
||||||
// 检查好友状态
|
// 检查好友状态
|
||||||
checkFriendStatus();
|
checkFriendStatus();
|
||||||
|
|
||||||
|
|
@ -285,11 +297,13 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
|
||||||
if (isFollowing) {
|
if (isFollowing) {
|
||||||
binding.followButton.setText("已关注");
|
binding.followButton.setText("已关注");
|
||||||
binding.followButton.setSelected(true);
|
binding.followButton.setSelected(true);
|
||||||
|
binding.followButton.setBackgroundResource(R.drawable.bg_follow_button_followed);
|
||||||
binding.followButton.setTextColor(getResources().getColor(android.R.color.darker_gray, null));
|
binding.followButton.setTextColor(getResources().getColor(android.R.color.darker_gray, null));
|
||||||
} else {
|
} else {
|
||||||
binding.followButton.setText("关注");
|
binding.followButton.setText("关注");
|
||||||
binding.followButton.setSelected(false);
|
binding.followButton.setSelected(false);
|
||||||
binding.followButton.setTextColor(getResources().getColor(R.color.red_primary, null));
|
binding.followButton.setBackgroundResource(R.drawable.bg_follow_button_normal);
|
||||||
|
binding.followButton.setTextColor(0xFF333333);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -502,7 +516,168 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
|
||||||
private void bindDemoStatsAndWorks(String userId) {
|
private void bindDemoStatsAndWorks(String userId) {
|
||||||
if (binding == null) return;
|
if (binding == null) return;
|
||||||
|
|
||||||
String seed = !TextUtils.isEmpty(userId) ? userId : "demo";
|
// 如果只传了userId,需要从后端获取用户信息
|
||||||
|
if (!TextUtils.isEmpty(userId)) {
|
||||||
|
loadUserProfile(userId);
|
||||||
|
loadUserWorks(userId);
|
||||||
|
} else {
|
||||||
|
// 没有userId,显示演示数据
|
||||||
|
showDemoData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从后端加载用户主页信息
|
||||||
|
*/
|
||||||
|
private void loadUserProfile(String userId) {
|
||||||
|
try {
|
||||||
|
int uid = Integer.parseInt(userId);
|
||||||
|
ApiService apiService = ApiClient.getService(this);
|
||||||
|
Call<ApiResponse<Map<String, Object>>> call = apiService.getUserProfile(uid);
|
||||||
|
|
||||||
|
call.enqueue(new Callback<ApiResponse<Map<String, Object>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
|
||||||
|
Response<ApiResponse<Map<String, Object>>> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
ApiResponse<Map<String, Object>> apiResponse = response.body();
|
||||||
|
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
|
||||||
|
Map<String, Object> data = apiResponse.getData();
|
||||||
|
|
||||||
|
// 更新用户名
|
||||||
|
String nickname = (String) data.get("nickname");
|
||||||
|
if (!TextUtils.isEmpty(nickname)) {
|
||||||
|
currentUserName = nickname;
|
||||||
|
binding.name.setText(nickname);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新头像
|
||||||
|
String avatar = (String) data.get("avatar");
|
||||||
|
if (!TextUtils.isEmpty(avatar)) {
|
||||||
|
com.bumptech.glide.Glide.with(UserProfileReadOnlyActivity.this)
|
||||||
|
.load(avatar)
|
||||||
|
.placeholder(R.drawable.wish_tree_checker_backup)
|
||||||
|
.error(R.drawable.wish_tree_checker_backup)
|
||||||
|
.circleCrop()
|
||||||
|
.into(binding.avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新统计数据
|
||||||
|
Object worksCount = data.get("worksCount");
|
||||||
|
Object followingCount = data.get("followingCount");
|
||||||
|
Object followersCount = data.get("followersCount");
|
||||||
|
Object likesCount = data.get("likesCount");
|
||||||
|
|
||||||
|
if (worksCount != null) {
|
||||||
|
binding.statWorksValue.setText(formatIntValue(worksCount));
|
||||||
|
}
|
||||||
|
if (followingCount != null) {
|
||||||
|
binding.statFollowingValue.setText(formatIntValue(followingCount));
|
||||||
|
}
|
||||||
|
if (followersCount != null) {
|
||||||
|
binding.statFollowersValue.setText(formatIntValue(followersCount));
|
||||||
|
}
|
||||||
|
if (likesCount != null) {
|
||||||
|
binding.statLikesValue.setText(formatIntValue(likesCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新签名
|
||||||
|
String mark = (String) data.get("mark");
|
||||||
|
if (!TextUtils.isEmpty(mark)) {
|
||||||
|
binding.bio.setText(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
|
||||||
|
Log.e(TAG, "加载用户信息失败", t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e(TAG, "用户ID格式错误: " + userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从后端加载用户作品列表
|
||||||
|
*/
|
||||||
|
private void loadUserWorks(String userId) {
|
||||||
|
try {
|
||||||
|
int uid = Integer.parseInt(userId);
|
||||||
|
ApiService apiService = ApiClient.getService(this);
|
||||||
|
Call<ApiResponse<com.example.livestreaming.net.PageResponse<com.example.livestreaming.net.WorksResponse>>> call =
|
||||||
|
apiService.getUserWorks(uid, 1, 50);
|
||||||
|
|
||||||
|
call.enqueue(new Callback<ApiResponse<com.example.livestreaming.net.PageResponse<com.example.livestreaming.net.WorksResponse>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<com.example.livestreaming.net.PageResponse<com.example.livestreaming.net.WorksResponse>>> call,
|
||||||
|
Response<ApiResponse<com.example.livestreaming.net.PageResponse<com.example.livestreaming.net.WorksResponse>>> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
ApiResponse<com.example.livestreaming.net.PageResponse<com.example.livestreaming.net.WorksResponse>> apiResponse = response.body();
|
||||||
|
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
|
||||||
|
List<com.example.livestreaming.net.WorksResponse> worksList = apiResponse.getData().getList();
|
||||||
|
|
||||||
|
if (worksList != null && !worksList.isEmpty()) {
|
||||||
|
// 转换为WorkItem列表
|
||||||
|
List<WorkItem> works = new ArrayList<>();
|
||||||
|
for (com.example.livestreaming.net.WorksResponse wr : worksList) {
|
||||||
|
WorkItem work = new WorkItem();
|
||||||
|
work.setId(String.valueOf(wr.getId()));
|
||||||
|
work.setTitle(wr.getTitle());
|
||||||
|
work.setDescription(wr.getDescription());
|
||||||
|
work.setCoverUrl(wr.getCoverUrl());
|
||||||
|
work.setVideoUrl(wr.getVideoUrl());
|
||||||
|
work.setImageUrls(wr.getImageUrls());
|
||||||
|
work.setLikeCount(wr.getLikeCount());
|
||||||
|
work.setCollectCount(wr.getCollectCount());
|
||||||
|
work.setViewCount(wr.getViewCount());
|
||||||
|
work.setType("VIDEO".equals(wr.getType()) ? WorkItem.WorkType.VIDEO : WorkItem.WorkType.IMAGE);
|
||||||
|
works.add(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新作品数量
|
||||||
|
binding.statWorksValue.setText(String.valueOf(works.size()));
|
||||||
|
|
||||||
|
// 设置适配器
|
||||||
|
if (worksAdapter != null) {
|
||||||
|
worksAdapter.setOnWorkClickListener(workItem -> {
|
||||||
|
if (workItem != null && !TextUtils.isEmpty(workItem.getId())) {
|
||||||
|
WorkDetailActivity.start(UserProfileReadOnlyActivity.this, workItem.getId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
worksAdapter.submitList(works);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 没有作品,显示空状态
|
||||||
|
if (worksAdapter != null) {
|
||||||
|
worksAdapter.submitList(new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "获取作品失败: " + (apiResponse.getMessage() != null ? apiResponse.getMessage() : "未知错误"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<com.example.livestreaming.net.PageResponse<com.example.livestreaming.net.WorksResponse>>> call, Throwable t) {
|
||||||
|
Log.e(TAG, "加载用户作品失败", t);
|
||||||
|
Toast.makeText(UserProfileReadOnlyActivity.this, "加载作品失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e(TAG, "用户ID格式错误: " + userId);
|
||||||
|
Toast.makeText(this, "用户ID格式错误", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示演示数据(当没有userId时)
|
||||||
|
*/
|
||||||
|
private void showDemoData() {
|
||||||
|
String seed = "demo";
|
||||||
int h = Math.abs(seed.hashCode());
|
int h = Math.abs(seed.hashCode());
|
||||||
|
|
||||||
int worksCount = 6 + (h % 18);
|
int worksCount = 6 + (h % 18);
|
||||||
|
|
@ -517,16 +692,9 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
|
||||||
|
|
||||||
// 创建演示作品列表
|
// 创建演示作品列表
|
||||||
List<WorkItem> works = new ArrayList<>();
|
List<WorkItem> works = new ArrayList<>();
|
||||||
int[] pool = new int[] {
|
|
||||||
R.drawable.wish_tree_checker_backup,
|
|
||||||
R.drawable.wish_tree_prev_no_bg,
|
|
||||||
R.drawable.wish_tree_trim_backup,
|
|
||||||
R.drawable.wish_tree_black,
|
|
||||||
R.drawable.wish_tree
|
|
||||||
};
|
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
WorkItem work = new WorkItem();
|
WorkItem work = new WorkItem();
|
||||||
work.setId("demo_" + userId + "_" + i);
|
work.setId("demo_" + i);
|
||||||
work.setTitle("演示作品 " + (i + 1));
|
work.setTitle("演示作品 " + (i + 1));
|
||||||
work.setType(WorkItem.WorkType.IMAGE);
|
work.setType(WorkItem.WorkType.IMAGE);
|
||||||
work.setLikeCount((h + i) % 100);
|
work.setLikeCount((h + i) % 100);
|
||||||
|
|
@ -643,4 +811,23 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数值转换为整数字符串显示
|
||||||
|
*/
|
||||||
|
private String formatIntValue(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return String.valueOf(((Number) value).intValue());
|
||||||
|
}
|
||||||
|
// 尝试解析字符串
|
||||||
|
double d = Double.parseDouble(value.toString());
|
||||||
|
return String.valueOf((int) d);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
|
|
||||||
|
|
@ -44,6 +45,7 @@ public class WorkDetailActivity extends AppCompatActivity {
|
||||||
// 状态标记
|
// 状态标记
|
||||||
private boolean isLiked = false;
|
private boolean isLiked = false;
|
||||||
private boolean isFavorited = false;
|
private boolean isFavorited = false;
|
||||||
|
private boolean isFollowed = false; // 是否已关注作者
|
||||||
private int commentCount = 0;
|
private int commentCount = 0;
|
||||||
|
|
||||||
private static final String EXTRA_WORK_ID = "work_id";
|
private static final String EXTRA_WORK_ID = "work_id";
|
||||||
|
|
@ -789,6 +791,182 @@ public class WorkDetailActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置作者头像和关注按钮
|
||||||
|
*/
|
||||||
|
private void setupAuthorSection() {
|
||||||
|
if (workItem == null) return;
|
||||||
|
|
||||||
|
int authorId = workItem.getAuthorId();
|
||||||
|
String authorAvatar = workItem.getAuthorAvatar();
|
||||||
|
|
||||||
|
// 加载作者头像
|
||||||
|
if (!TextUtils.isEmpty(authorAvatar)) {
|
||||||
|
Glide.with(this)
|
||||||
|
.load(authorAvatar)
|
||||||
|
.circleCrop()
|
||||||
|
.placeholder(R.drawable.ic_account_circle_24)
|
||||||
|
.error(R.drawable.ic_account_circle_24)
|
||||||
|
.into(binding.authorAvatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 头像点击跳转到作者主页
|
||||||
|
binding.authorAvatar.setOnClickListener(new DebounceClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onDebouncedClick(View v) {
|
||||||
|
if (authorId > 0) {
|
||||||
|
// 跳转到用户主页
|
||||||
|
UserProfileReadOnlyActivity.start(WorkDetailActivity.this, authorId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否是自己的作品
|
||||||
|
String currentUserIdStr = AuthStore.getUserId(this);
|
||||||
|
int currentUserId = 0;
|
||||||
|
if (currentUserIdStr != null) {
|
||||||
|
try {
|
||||||
|
currentUserId = Integer.parseInt(currentUserIdStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 忽略解析错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentUserId > 0 && currentUserId == authorId) {
|
||||||
|
// 自己的作品,隐藏关注按钮
|
||||||
|
binding.followButton.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
// 检查关注状态
|
||||||
|
checkFollowStatus(authorId);
|
||||||
|
|
||||||
|
// 关注按钮点击事件
|
||||||
|
binding.followButton.setOnClickListener(new DebounceClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onDebouncedClick(View v) {
|
||||||
|
toggleFollow(authorId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查关注状态
|
||||||
|
*/
|
||||||
|
private void checkFollowStatus(int userId) {
|
||||||
|
if (userId <= 0) return;
|
||||||
|
|
||||||
|
// 未登录时不检查
|
||||||
|
if (AuthStore.getToken(this) == null) {
|
||||||
|
binding.followButton.setVisibility(View.VISIBLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiService apiService = ApiClient.getService(this);
|
||||||
|
Call<ApiResponse<Map<String, Object>>> call = apiService.checkFollowStatus(userId);
|
||||||
|
|
||||||
|
call.enqueue(new retrofit2.Callback<ApiResponse<Map<String, Object>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
|
||||||
|
retrofit2.Response<ApiResponse<Map<String, Object>>> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
ApiResponse<Map<String, Object>> apiResponse = response.body();
|
||||||
|
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
|
||||||
|
Object isFollowingObj = apiResponse.getData().get("isFollowing");
|
||||||
|
isFollowed = isFollowingObj != null && Boolean.parseBoolean(isFollowingObj.toString());
|
||||||
|
updateFollowButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
|
||||||
|
android.util.Log.e("WorkDetail", "检查关注状态失败", t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新关注按钮显示
|
||||||
|
*/
|
||||||
|
private void updateFollowButton() {
|
||||||
|
if (isFollowed) {
|
||||||
|
// 已关注,隐藏+号按钮
|
||||||
|
binding.followButton.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
// 未关注,显示+号按钮
|
||||||
|
binding.followButton.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换关注状态
|
||||||
|
*/
|
||||||
|
private void toggleFollow(int userId) {
|
||||||
|
// 检查登录状态
|
||||||
|
if (!AuthHelper.requireLogin(this, "关注需要登录")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId <= 0) {
|
||||||
|
Toast.makeText(this, "用户信息无效", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiService apiService = ApiClient.getService(this);
|
||||||
|
|
||||||
|
// 构建请求参数
|
||||||
|
java.util.Map<String, Object> body = new java.util.HashMap<>();
|
||||||
|
body.put("userId", userId);
|
||||||
|
|
||||||
|
Call<ApiResponse<Map<String, Object>>> call;
|
||||||
|
if (isFollowed) {
|
||||||
|
// 取消关注
|
||||||
|
call = apiService.unfollowUser(body);
|
||||||
|
} else {
|
||||||
|
// 关注
|
||||||
|
call = apiService.followUser(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 乐观更新UI
|
||||||
|
boolean oldFollowed = isFollowed;
|
||||||
|
isFollowed = !isFollowed;
|
||||||
|
updateFollowButton();
|
||||||
|
|
||||||
|
call.enqueue(new retrofit2.Callback<ApiResponse<Map<String, Object>>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
|
||||||
|
retrofit2.Response<ApiResponse<Map<String, Object>>> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
ApiResponse<Map<String, Object>> apiResponse = response.body();
|
||||||
|
if (apiResponse.getCode() == 200) {
|
||||||
|
Toast.makeText(WorkDetailActivity.this,
|
||||||
|
isFollowed ? "关注成功" : "已取消关注",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
// 恢复原状态
|
||||||
|
isFollowed = oldFollowed;
|
||||||
|
updateFollowButton();
|
||||||
|
Toast.makeText(WorkDetailActivity.this,
|
||||||
|
apiResponse.getMessage() != null ? apiResponse.getMessage() : "操作失败",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 恢复原状态
|
||||||
|
isFollowed = oldFollowed;
|
||||||
|
updateFollowButton();
|
||||||
|
Toast.makeText(WorkDetailActivity.this, "操作失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
|
||||||
|
// 恢复原状态
|
||||||
|
isFollowed = oldFollowed;
|
||||||
|
updateFollowButton();
|
||||||
|
Toast.makeText(WorkDetailActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setupActionButton() {
|
private void setupActionButton() {
|
||||||
// ============================================
|
// ============================================
|
||||||
// TODO: 判断是否是当前用户的作品
|
// TODO: 判断是否是当前用户的作品
|
||||||
|
|
@ -1051,6 +1229,7 @@ public class WorkDetailActivity extends AppCompatActivity {
|
||||||
setupContent();
|
setupContent();
|
||||||
setupActionButtons();
|
setupActionButtons();
|
||||||
setupActionButton();
|
setupActionButton();
|
||||||
|
setupAuthorSection(); // 设置作者头像和关注按钮
|
||||||
|
|
||||||
// 获取真实的评论数量
|
// 获取真实的评论数量
|
||||||
loadRealCommentCount(worksResponse.getId());
|
loadRealCommentCount(worksResponse.getId());
|
||||||
|
|
@ -1099,6 +1278,11 @@ public class WorkDetailActivity extends AppCompatActivity {
|
||||||
// 防止 publishTime 为 null 导致 NullPointerException
|
// 防止 publishTime 为 null 导致 NullPointerException
|
||||||
item.setPublishTime(response.getPublishTime() != null ? response.getPublishTime() : 0L);
|
item.setPublishTime(response.getPublishTime() != null ? response.getPublishTime() : 0L);
|
||||||
|
|
||||||
|
// 设置作者信息
|
||||||
|
item.setAuthorId(response.getUserId() != null ? response.getUserId() : 0);
|
||||||
|
item.setAuthorName(response.getAuthorName() != null ? response.getAuthorName() : response.getUserName());
|
||||||
|
item.setAuthorAvatar(response.getAuthorAvatar() != null ? response.getAuthorAvatar() : response.getUserAvatar());
|
||||||
|
|
||||||
// 设置作品类型
|
// 设置作品类型
|
||||||
if ("VIDEO".equals(response.getType())) {
|
if ("VIDEO".equals(response.getType())) {
|
||||||
item.setType(WorkItem.WorkType.VIDEO);
|
item.setType(WorkItem.WorkType.VIDEO);
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ public class WorkItem implements Parcelable {
|
||||||
private long publishTime;
|
private long publishTime;
|
||||||
private WorkType type; // 作品类型:图片或视频
|
private WorkType type; // 作品类型:图片或视频
|
||||||
|
|
||||||
|
// 作者信息
|
||||||
|
private int authorId; // 作者用户ID
|
||||||
|
private String authorName; // 作者昵称
|
||||||
|
private String authorAvatar; // 作者头像URL
|
||||||
|
|
||||||
// 本地使用的URI(发布时使用)
|
// 本地使用的URI(发布时使用)
|
||||||
private transient Uri coverUri;
|
private transient Uri coverUri;
|
||||||
private transient Uri videoUri;
|
private transient Uri videoUri;
|
||||||
|
|
@ -41,6 +46,9 @@ public class WorkItem implements Parcelable {
|
||||||
this.publishTime = System.currentTimeMillis();
|
this.publishTime = System.currentTimeMillis();
|
||||||
this.imageUrls = new ArrayList<>();
|
this.imageUrls = new ArrayList<>();
|
||||||
this.imageUris = new ArrayList<>();
|
this.imageUris = new ArrayList<>();
|
||||||
|
this.authorId = 0;
|
||||||
|
this.authorName = "";
|
||||||
|
this.authorAvatar = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorkItem(String id, String title, String description, String coverUrl,
|
public WorkItem(String id, String title, String description, String coverUrl,
|
||||||
|
|
@ -171,6 +179,30 @@ public class WorkItem implements Parcelable {
|
||||||
this.imageUris = imageUris;
|
this.imageUris = imageUris;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getAuthorId() {
|
||||||
|
return authorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorId(int authorId) {
|
||||||
|
this.authorId = authorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorName() {
|
||||||
|
return authorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorName(String authorName) {
|
||||||
|
this.authorName = authorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorAvatar() {
|
||||||
|
return authorAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorAvatar(String authorAvatar) {
|
||||||
|
this.authorAvatar = authorAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
// Parcelable implementation
|
// Parcelable implementation
|
||||||
protected WorkItem(Parcel in) {
|
protected WorkItem(Parcel in) {
|
||||||
id = in.readString();
|
id = in.readString();
|
||||||
|
|
@ -186,6 +218,9 @@ public class WorkItem implements Parcelable {
|
||||||
int typeOrdinal = in.readInt();
|
int typeOrdinal = in.readInt();
|
||||||
type = typeOrdinal >= 0 && typeOrdinal < WorkType.values().length
|
type = typeOrdinal >= 0 && typeOrdinal < WorkType.values().length
|
||||||
? WorkType.values()[typeOrdinal] : WorkType.IMAGE;
|
? WorkType.values()[typeOrdinal] : WorkType.IMAGE;
|
||||||
|
authorId = in.readInt();
|
||||||
|
authorName = in.readString();
|
||||||
|
authorAvatar = in.readString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -201,6 +236,9 @@ public class WorkItem implements Parcelable {
|
||||||
dest.writeInt(viewCount);
|
dest.writeInt(viewCount);
|
||||||
dest.writeLong(publishTime);
|
dest.writeLong(publishTime);
|
||||||
dest.writeInt(type != null ? type.ordinal() : -1);
|
dest.writeInt(type != null ? type.ordinal() : -1);
|
||||||
|
dest.writeInt(authorId);
|
||||||
|
dest.writeString(authorName);
|
||||||
|
dest.writeString(authorAvatar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -377,6 +377,23 @@ public interface ApiService {
|
||||||
@GET("api/front/works/detail/{id}")
|
@GET("api/front/works/detail/{id}")
|
||||||
Call<ApiResponse<WorksResponse>> getWorkDetail(@Path("id") long id);
|
Call<ApiResponse<WorksResponse>> getWorkDetail(@Path("id") long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户发布的作品列表
|
||||||
|
*/
|
||||||
|
@GET("api/front/works/my")
|
||||||
|
Call<ApiResponse<PageResponse<WorksResponse>>> getMyPublishedWorks(
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定用户发布的作品列表
|
||||||
|
*/
|
||||||
|
@GET("api/front/works/user/{userId}")
|
||||||
|
Call<ApiResponse<PageResponse<WorksResponse>>> getUserWorks(
|
||||||
|
@Path("userId") int userId,
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
|
|
||||||
@DELETE("api/front/works/delete/{id}")
|
@DELETE("api/front/works/delete/{id}")
|
||||||
Call<ApiResponse<Boolean>> deleteWork(@Path("id") long id);
|
Call<ApiResponse<Boolean>> deleteWork(@Path("id") long id);
|
||||||
|
|
||||||
|
|
@ -979,4 +996,28 @@ public interface ApiService {
|
||||||
@Query("categoryId") Integer categoryId,
|
@Query("categoryId") Integer categoryId,
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@Query("limit") int limit);
|
@Query("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索作品(使用POST方法)
|
||||||
|
*/
|
||||||
|
@POST("api/front/works/search")
|
||||||
|
Call<ApiResponse<PageResponse<WorksResponse>>> searchWorks(@Body Map<String, Object> request);
|
||||||
|
|
||||||
|
// ==================== 我的点赞/收藏作品接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我点赞的作品列表
|
||||||
|
*/
|
||||||
|
@GET("api/front/works/my/liked")
|
||||||
|
Call<ApiResponse<PageResponse<WorksResponse>>> getMyLikedWorks(
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我收藏的作品列表
|
||||||
|
*/
|
||||||
|
@GET("api/front/works/my/collected")
|
||||||
|
Call<ApiResponse<PageResponse<WorksResponse>>> getMyCollectedWorks(
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#F5F5F5" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
<stroke
|
<stroke
|
||||||
android:width="1dp"
|
android:width="1dp"
|
||||||
android:color="#E0E0E0" />
|
android:color="#E0E0E0" />
|
||||||
<corners android:radius="4dp" />
|
|
||||||
<solid android:color="@android:color/white" />
|
|
||||||
</shape>
|
</shape>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<solid android:color="#F8F8F8" />
|
<solid android:color="#F5F5F5" />
|
||||||
<corners android:radius="4dp" />
|
<corners android:radius="16dp" />
|
||||||
<stroke
|
<stroke
|
||||||
android:width="1dp"
|
android:width="1dp"
|
||||||
android:color="#E8E8E8" />
|
android:color="#E0E0E0" />
|
||||||
</shape>
|
</shape>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#FFFFFF" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#E0E0E0"
|
||||||
|
android:dashWidth="4dp"
|
||||||
|
android:dashGap="2dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#2196F3" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
<stroke
|
<stroke
|
||||||
android:width="1dp"
|
android:width="2dp"
|
||||||
android:color="#333333" />
|
android:color="#1976D2" />
|
||||||
<corners android:radius="4dp" />
|
|
||||||
<solid android:color="@android:color/white" />
|
|
||||||
</shape>
|
</shape>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="@android:color/white" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#E0E0E0" />
|
||||||
|
</shape>
|
||||||
|
|
@ -1,19 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
<!-- 已关注状态 -->
|
android:shape="oval">
|
||||||
<item android:state_selected="true">
|
<solid android:color="#FF4757" />
|
||||||
<shape android:shape="rectangle">
|
<size
|
||||||
<solid android:color="#F5F5F5" />
|
android:width="20dp"
|
||||||
<corners android:radius="22dp" />
|
android:height="20dp" />
|
||||||
<stroke android:width="1dp" android:color="#DDDDDD" />
|
</shape>
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
<!-- 未关注状态 -->
|
|
||||||
<item>
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<solid android:color="#FFFFFF" />
|
|
||||||
<corners android:radius="22dp" />
|
|
||||||
<stroke android:width="1dp" android:color="#FF4757" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</selector>
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#F5F5F5" />
|
||||||
|
<corners android:radius="22dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#FFFFFF" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#CCCCCC" />
|
||||||
|
<corners android:radius="22dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/background_color">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
app:elevation="0dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:navigationIcon="@drawable/ic_arrow_back_24"
|
||||||
|
app:title="我的收藏"
|
||||||
|
app:titleTextColor="@color/text_primary" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tabLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
app:tabIndicatorColor="@color/purple_500"
|
||||||
|
app:tabIndicatorFullWidth="false"
|
||||||
|
app:tabIndicatorHeight="3dp"
|
||||||
|
app:tabMode="fixed"
|
||||||
|
app:tabGravity="fill"
|
||||||
|
app:tabSelectedTextColor="@color/purple_500"
|
||||||
|
app:tabTextColor="@color/text_secondary" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/viewPager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
43
android-app/app/src/main/res/layout/activity_my_likes.xml
Normal file
43
android-app/app/src/main/res/layout/activity_my_likes.xml
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/background_color">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
app:elevation="0dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:navigationIcon="@drawable/ic_arrow_back_24"
|
||||||
|
app:title="我的点赞"
|
||||||
|
app:titleTextColor="@color/text_primary" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tabLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
app:tabIndicatorColor="@color/purple_500"
|
||||||
|
app:tabIndicatorFullWidth="false"
|
||||||
|
app:tabIndicatorHeight="3dp"
|
||||||
|
app:tabMode="fixed"
|
||||||
|
app:tabGravity="fill"
|
||||||
|
app:tabSelectedTextColor="@color/purple_500"
|
||||||
|
app:tabTextColor="@color/text_secondary" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/viewPager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
@ -70,6 +70,7 @@
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintHorizontal_bias="0"
|
app:layout_constraintHorizontal_bias="0"
|
||||||
app:layout_constraintEnd_toStartOf="@id/topActionClock"
|
app:layout_constraintEnd_toStartOf="@id/topActionClock"
|
||||||
app:layout_constraintStart_toStartOf="@id/name"
|
app:layout_constraintStart_toStartOf="@id/name"
|
||||||
|
|
@ -187,6 +188,7 @@
|
||||||
android:background="@drawable/bg_circle_white_60"
|
android:background="@drawable/bg_circle_white_60"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:src="@drawable/ic_clock_24"
|
android:src="@drawable/ic_clock_24"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toStartOf="@id/topActionMore"
|
app:layout_constraintEnd_toStartOf="@id/topActionMore"
|
||||||
app:layout_constraintTop_toTopOf="@id/topActionMore" />
|
app:layout_constraintTop_toTopOf="@id/topActionMore" />
|
||||||
|
|
||||||
|
|
@ -198,6 +200,7 @@
|
||||||
android:background="@drawable/bg_circle_white_60"
|
android:background="@drawable/bg_circle_white_60"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:src="@drawable/ic_crosshair_24"
|
android:src="@drawable/ic_crosshair_24"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toStartOf="@id/topActionClock"
|
app:layout_constraintEnd_toStartOf="@id/topActionClock"
|
||||||
app:layout_constraintTop_toTopOf="@id/topActionMore" />
|
app:layout_constraintTop_toTopOf="@id/topActionMore" />
|
||||||
|
|
||||||
|
|
@ -439,7 +442,7 @@
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/ic_like_24"
|
android:src="@drawable/ic_like_filled_24"
|
||||||
android:tint="#FF4081" />
|
android:tint="#FF4081" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
@ -453,7 +456,7 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="我的收藏"
|
android:text="我的点赞"
|
||||||
android:textColor="#111111"
|
android:textColor="#111111"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
@ -488,8 +491,8 @@
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/ic_heart_24"
|
android:src="@drawable/ic_star_24"
|
||||||
android:tint="#E91E63" />
|
android:tint="#FFA726" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
@ -502,7 +505,7 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="我的挚友"
|
android:text="我的收藏"
|
||||||
android:textColor="#111111"
|
android:textColor="#111111"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
@ -512,7 +515,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
android:text="0人"
|
android:text="0个"
|
||||||
android:textColor="#999999"
|
android:textColor="#999999"
|
||||||
android:textSize="11sp" />
|
android:textSize="11sp" />
|
||||||
|
|
||||||
|
|
@ -525,7 +528,8 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="64dp"
|
android:layout_height="64dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="44dp"
|
android:layout_width="44dp"
|
||||||
|
|
@ -686,6 +690,91 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 我的作品区域 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/myWorksSection"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/walletButton">
|
||||||
|
|
||||||
|
<!-- 标题栏 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="我的作品"
|
||||||
|
android:textColor="#111111"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/myWorksCount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0个作品"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 作品列表 -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/myWorksRecycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/myWorksEmptyState"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="200dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:alpha="0.5"
|
||||||
|
android:src="@drawable/ic_add_photo_24"
|
||||||
|
android:tint="#999999" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="还没有发布作品"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/myWorksPublishBtn"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:background="@drawable/bg_purple_20"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="去发布"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/profileTabs"
|
android:id="@+id/profileTabs"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
@ -700,7 +789,7 @@
|
||||||
app:tabTextColor="#666666"
|
app:tabTextColor="#666666"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/bottomButtons">
|
app:layout_constraintTop_toBottomOf="@id/myWorksSection">
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabItem
|
<com.google.android.material.tabs.TabItem
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
||||||
|
|
@ -348,6 +348,58 @@
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<!-- 分类选择卡片 -->
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="2dp"
|
||||||
|
app:cardBackgroundColor="#FFFFFF">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="作品分类"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="12dp" />
|
||||||
|
|
||||||
|
<!-- 分类选择下拉框 -->
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/categoryInputLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="请选择分类"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||||
|
app:boxCornerRadiusTopStart="8dp"
|
||||||
|
app:boxCornerRadiusTopEnd="8dp"
|
||||||
|
app:boxCornerRadiusBottomStart="8dp"
|
||||||
|
app:boxCornerRadiusBottomEnd="8dp"
|
||||||
|
app:hintTextColor="#999999">
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/categorySpinner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="none"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<!-- 高级设置卡片 -->
|
<!-- 高级设置卡片 -->
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,18 @@
|
||||||
android:paddingBottom="24dp"
|
android:paddingBottom="24dp"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<!-- 作品列表 -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/worksRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="24dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<!-- 旧的结果列表(兼容) -->
|
<!-- 旧的结果列表(兼容) -->
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/resultsRecyclerView"
|
android:id="@+id/resultsRecyclerView"
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@
|
||||||
android:id="@+id/space_bio_margin_top"
|
android:id="@+id/space_bio_margin_top"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintHeight_percent="0.025"
|
app:layout_constraintHeight_percent="0.015"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/avatarRing" />
|
app:layout_constraintTop_toBottomOf="@id/avatarRing" />
|
||||||
|
|
||||||
|
|
@ -271,7 +271,7 @@
|
||||||
android:id="@+id/space_addFriend_margin_top"
|
android:id="@+id/space_addFriend_margin_top"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintHeight_percent="0.028"
|
app:layout_constraintHeight_percent="0.015"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/bio" />
|
app:layout_constraintTop_toBottomOf="@id/bio" />
|
||||||
|
|
||||||
|
|
@ -292,10 +292,10 @@
|
||||||
android:layout_height="44dp"
|
android:layout_height="44dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginEnd="6dp"
|
android:layout_marginEnd="6dp"
|
||||||
android:background="@drawable/bg_follow_button"
|
android:background="@drawable/bg_follow_button_normal"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="关注"
|
android:text="关注"
|
||||||
android:textColor="#FF4757"
|
android:textColor="#333333"
|
||||||
android:textSize="15sp"
|
android:textSize="15sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<!-- 右侧操作按钮(点赞、收藏、评论) -->
|
<!-- 右侧操作按钮(用户头像、关注、点赞、收藏、评论) -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
@ -89,6 +89,40 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="center">
|
android:gravity="center">
|
||||||
|
|
||||||
|
<!-- 用户头像和关注按钮 -->
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/authorAvatarContainer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<!-- 用户头像 -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/authorAvatar"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@drawable/bg_avatar_circle"
|
||||||
|
android:clipToOutline="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/ic_account_circle_24"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true" />
|
||||||
|
|
||||||
|
<!-- 关注按钮(红色+号) -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/followButton"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:layout_marginBottom="-6dp"
|
||||||
|
android:src="@drawable/ic_add_circle_24"
|
||||||
|
android:background="@drawable/bg_follow_button"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- 点赞按钮 -->
|
<!-- 点赞按钮 -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/likeButtonContainer"
|
android:id="@+id/likeButtonContainer"
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@android:color/white"
|
|
||||||
android:paddingBottom="24dp">
|
|
||||||
|
|
||||||
<!-- 顶部标题栏 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:paddingTop="16dp"
|
|
||||||
android:paddingBottom="12dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="我的频道"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textColor="#333333"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="点击进入频道"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textColor="#999999"
|
|
||||||
android:layout_marginEnd="16dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/btnEditChannels"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="进入编辑"
|
|
||||||
android:textSize="13sp"
|
|
||||||
android:textColor="#333333"
|
|
||||||
android:paddingHorizontal="12dp"
|
|
||||||
android:paddingVertical="6dp"
|
|
||||||
android:background="@drawable/bg_channel_edit_button" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/btnCloseChannelManager"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:src="@drawable/ic_expand_more_24"
|
|
||||||
android:rotation="180"
|
|
||||||
android:contentDescription="收起"
|
|
||||||
app:tint="#999999" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 我的频道列表 -->
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/myChannelsRecycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingHorizontal="12dp"
|
|
||||||
android:paddingBottom="16dp"
|
|
||||||
android:clipToPadding="false" />
|
|
||||||
|
|
||||||
<!-- 分隔线 -->
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:background="#F0F0F0" />
|
|
||||||
|
|
||||||
<!-- 推荐频道标题 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:paddingTop="16dp"
|
|
||||||
android:paddingBottom="12dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="推荐频道"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textColor="#333333"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="点击添加频道"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textColor="#999999" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 推荐频道列表 -->
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/recommendChannelsRecycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingHorizontal="12dp"
|
|
||||||
android:clipToPadding="false" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="@android:color/white">
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="频道管理"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingBottom="16dp" />
|
||||||
|
|
||||||
|
<!-- 我的频道 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="我的频道"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/addChannelText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="点击进入频道,长按拖拽调整顺序"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#999999" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 我的频道列表 -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/myChannelsRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:paddingHorizontal="8dp" />
|
||||||
|
|
||||||
|
<!-- 推荐频道 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/recommendTitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="推荐频道"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#666666"
|
||||||
|
android:paddingBottom="8dp" />
|
||||||
|
|
||||||
|
<!-- 推荐频道列表 -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recommendChannelsRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:paddingHorizontal="8dp" />
|
||||||
|
|
||||||
|
<!-- 完成按钮 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/completeButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:text="完成"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:background="@drawable/bg_button_primary"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginTop="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/swipeRefreshLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
<!-- 空状态视图 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/emptyView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/emptyIcon"
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:alpha="0.3"
|
||||||
|
android:src="@drawable/ic_grid_24" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/emptyText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="暂无内容"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 加载视图 -->
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/loadingView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
@ -3,52 +3,31 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="4dp">
|
android:layout_margin="6dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/channelName"
|
android:id="@+id/channelName"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingHorizontal="16dp"
|
android:paddingHorizontal="16dp"
|
||||||
android:paddingVertical="8dp"
|
android:paddingVertical="10dp"
|
||||||
android:text="频道名"
|
android:text="频道名"
|
||||||
android:textSize="14sp"
|
android:textSize="13sp"
|
||||||
android:textColor="#333333"
|
android:textColor="#666666"
|
||||||
android:background="@drawable/bg_channel_tag_normal" />
|
android:background="@drawable/bg_channel_tag_normal"
|
||||||
|
android:minWidth="60dp"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
<!-- 固定标识(前4个频道显示) -->
|
<!-- 删除按钮(我的频道编辑模式显示) -->
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/fixedIcon"
|
android:id="@+id/deleteIcon"
|
||||||
android:layout_width="14dp"
|
android:layout_width="18dp"
|
||||||
android:layout_height="14dp"
|
android:layout_height="18dp"
|
||||||
android:layout_gravity="top|end"
|
android:layout_gravity="top|end"
|
||||||
android:layout_marginTop="-4dp"
|
android:layout_marginTop="-4dp"
|
||||||
android:layout_marginEnd="-4dp"
|
android:layout_marginEnd="-4dp"
|
||||||
android:src="@drawable/ic_lock_12"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:tint="#999999" />
|
|
||||||
|
|
||||||
<!-- 删除按钮(编辑模式显示) -->
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/deleteIcon"
|
|
||||||
android:layout_width="16dp"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:layout_gravity="top|end"
|
|
||||||
android:layout_marginTop="-6dp"
|
|
||||||
android:layout_marginEnd="-6dp"
|
|
||||||
android:src="@drawable/ic_close_circle_16"
|
android:src="@drawable/ic_close_circle_16"
|
||||||
|
android:background="@drawable/bg_circle_white"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<!-- 添加按钮(推荐频道显示) -->
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/addIcon"
|
|
||||||
android:layout_width="14dp"
|
|
||||||
android:layout_height="14dp"
|
|
||||||
android:layout_gravity="top|start"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:src="@drawable/ic_add_12"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:tint="#999999" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
||||||
63
android-app/搜索功能更新说明.md
Normal file
63
android-app/搜索功能更新说明.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# 搜索功能和分类管理修复说明
|
||||||
|
|
||||||
|
## 一、搜索功能修复
|
||||||
|
|
||||||
|
### 1. 用户搜索问题修复 ✅
|
||||||
|
**问题**:用户搜索结果始终为0
|
||||||
|
**修复**:恢复使用 `comprehensiveSearch` API,从 `streamers` 字段获取用户数据
|
||||||
|
|
||||||
|
### 2. 作品搜索问题修复 ✅
|
||||||
|
**问题**:搜索结果不准确
|
||||||
|
**修复**:使用正确的 `POST /api/front/works/search` 接口
|
||||||
|
|
||||||
|
## 二、分类管理功能
|
||||||
|
|
||||||
|
### 1. 后端修改
|
||||||
|
**文件**:`CategoryController.java`
|
||||||
|
- 修改 `getWorkCategories()` 方法,让作品分类使用直播间分类
|
||||||
|
- 实现统一的分类系统(直播和作品使用相同分类)
|
||||||
|
|
||||||
|
### 2. Android端修改
|
||||||
|
**文件**:`MainActivity.java`
|
||||||
|
- 添加了下拉按钮(btnExpandCategories)点击事件
|
||||||
|
- 添加了 `showCategoryManagementDialog()` 方法
|
||||||
|
- 添加了 `setupCategoryManagementDialog()` 方法
|
||||||
|
- 添加了 `loadCategoriesForDialog()` 方法
|
||||||
|
- 添加了 `updateCategoryTabsFromMyChannels()` 方法
|
||||||
|
|
||||||
|
**文件**:`dialog_category_management.xml`
|
||||||
|
- 创建了分类管理对话框布局
|
||||||
|
- 包含"我的频道"和"推荐频道"两个区域
|
||||||
|
- 支持添加/移除频道
|
||||||
|
|
||||||
|
## 三、数据库说明
|
||||||
|
|
||||||
|
### 现有数据
|
||||||
|
- **eb_live_room_category**:有5个分类(娱乐、游戏、音乐、户外、聊天)
|
||||||
|
- **eb_category**:type=8或9的数据为空
|
||||||
|
- **eb_works**:作品的category_id都为空
|
||||||
|
|
||||||
|
### 统一分类方案
|
||||||
|
- 直播和作品都使用 `eb_live_room_category` 表的分类
|
||||||
|
- 后端 `getWorkCategories()` 接口返回直播间分类
|
||||||
|
|
||||||
|
## 四、测试建议
|
||||||
|
|
||||||
|
1. **搜索功能测试**:
|
||||||
|
- 测试用户搜索
|
||||||
|
- 测试作品搜索(按标题模糊匹配)
|
||||||
|
- 测试直播间搜索
|
||||||
|
|
||||||
|
2. **分类管理测试**:
|
||||||
|
- 点击首页右上角下拉按钮
|
||||||
|
- 验证分类管理对话框显示
|
||||||
|
- 测试添加/移除频道功能
|
||||||
|
|
||||||
|
## 五、完成状态
|
||||||
|
|
||||||
|
✅ 搜索功能修复完成
|
||||||
|
✅ 后端分类统一完成
|
||||||
|
✅ 下拉按钮点击事件添加完成
|
||||||
|
✅ 分类管理对话框创建完成
|
||||||
|
|
||||||
|
**可以重新编译测试了!**
|
||||||
Loading…
Reference in New Issue
Block a user