功能:作品点赞+关注+收藏+作品搜索+作品标签
This commit is contained in:
parent
d2fa9a1055
commit
c8e22d497e
|
|
@ -1,3 +1,8 @@
|
|||
# 手动引用
|
||||
1. 你明白我的意思吗。有没有不清楚的问题,请先询问我然后进行开发。有歧义要先询问我之后再进行下一步,不能你自己猜测可能的结果
|
||||
2.
|
||||
|
||||
|
||||
# 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:
|
||||
upload:
|
||||
server:
|
||||
url: http://1.15.149.240:30005/upload # 文件上传服务器地址
|
||||
url: http://1.15.149.240:30005/api/upload # 文件上传服务器地址
|
||||
|
||||
# 配置端口
|
||||
server:
|
||||
|
|
|
|||
|
|
@ -67,14 +67,11 @@ public class CategoryController {
|
|||
@ApiOperation(value = "获取作品分类列表")
|
||||
@GetMapping("/work")
|
||||
public CommonResult<List<CategoryResponse>> getWorkCategories() {
|
||||
List<Category> categories = categoryService.getList(
|
||||
new com.zbkj.common.request.CategorySearchRequest()
|
||||
.setType(CategoryConstants.CATEGORY_TYPE_WORK)
|
||||
.setStatus(CategoryConstants.CATEGORY_STATUS_NORMAL)
|
||||
);
|
||||
// 暂时使用直播间分类作为作品分类,实现统一分类系统
|
||||
List<LiveRoomCategory> liveCategories = liveRoomCategoryService.getEnabledList();
|
||||
|
||||
List<CategoryResponse> response = categories.stream()
|
||||
.map(this::toCategoryResponse)
|
||||
List<CategoryResponse> response = liveCategories.stream()
|
||||
.map(this::toLiveRoomCategoryResponse)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
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_HTTP_PORT: 25003
|
||||
|
||||
# ============ 文件上传服务器配置 ============
|
||||
file:
|
||||
upload:
|
||||
server:
|
||||
url: http://1.15.149.240:30005/api/upload
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
# 配置的环境
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class RemoteUploadServiceImpl {
|
|||
|
||||
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 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.service.impl.ServiceImpl;
|
||||
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.works.Works;
|
||||
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.response.WorksResponse;
|
||||
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.WorksLikeService;
|
||||
import com.zbkj.service.service.WorksCollectService;
|
||||
|
|
@ -40,7 +39,7 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
|
|||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private CategoryService categoryService;
|
||||
private LiveRoomCategoryService liveRoomCategoryService;
|
||||
|
||||
@Autowired
|
||||
private WorksLikeService worksLikeService;
|
||||
|
|
@ -77,8 +76,8 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
|
|||
|
||||
// 验证分类是否存在(如果提供了分类ID)
|
||||
if (request.getCategoryId() != null) {
|
||||
Category category = categoryService.getById(request.getCategoryId());
|
||||
if (category == null || !category.getStatus()) {
|
||||
com.zbkj.common.model.live.LiveRoomCategory category = liveRoomCategoryService.getById(request.getCategoryId());
|
||||
if (category == null || category.getStatus() == null || category.getStatus() != 1) {
|
||||
throw new CrmebException("分类不存在或已禁用");
|
||||
}
|
||||
}
|
||||
|
|
@ -166,8 +165,8 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
|
|||
|
||||
// 验证分类是否存在(如果提供了分类ID)
|
||||
if (request.getCategoryId() != null) {
|
||||
Category category = categoryService.getById(request.getCategoryId());
|
||||
if (category == null || !category.getStatus()) {
|
||||
com.zbkj.common.model.live.LiveRoomCategory category = liveRoomCategoryService.getById(request.getCategoryId());
|
||||
if (category == null || category.getStatus() == null || category.getStatus() != 1) {
|
||||
throw new CrmebException("分类不存在或已禁用");
|
||||
}
|
||||
}
|
||||
|
|
@ -421,7 +420,7 @@ public class WorksServiceImpl extends ServiceImpl<WorksDao, Works> implements Wo
|
|||
|
||||
// 获取分类信息
|
||||
if (works.getCategoryId() != null) {
|
||||
Category category = categoryService.getById(works.getCategoryId());
|
||||
com.zbkj.common.model.live.LiveRoomCategory category = liveRoomCategoryService.getById(works.getCategoryId());
|
||||
if (category != null) {
|
||||
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: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
|
||||
android:name="com.example.livestreaming.LikesListActivity"
|
||||
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 {
|
||||
private final TextView tagText;
|
||||
private final View deleteIcon;
|
||||
|
||||
ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
tagText = itemView.findViewById(R.id.channelName);
|
||||
deleteIcon = itemView.findViewById(R.id.deleteIcon);
|
||||
}
|
||||
|
||||
void bind(ChannelTag tag, int position) {
|
||||
// 显示文本
|
||||
if (isRecommendMode) {
|
||||
tagText.setText("+ " + tag.getName());
|
||||
} else {
|
||||
tagText.setText(tag.getName());
|
||||
if (tag == null || tag.getName() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 选中状态
|
||||
boolean isSelected = position == selectedPosition;
|
||||
if (isSelected) {
|
||||
tagText.setBackgroundResource(R.drawable.bg_channel_tag_selected);
|
||||
tagText.setTextColor(itemView.getContext().getResources().getColor(android.R.color.black, null));
|
||||
} else {
|
||||
tagText.setBackgroundResource(R.drawable.bg_channel_tag);
|
||||
tagText.setTextColor(itemView.getContext().getResources().getColor(android.R.color.darker_gray, null));
|
||||
}
|
||||
|
||||
// 点击事件
|
||||
itemView.setOnClickListener(v -> {
|
||||
if (listener != null) {
|
||||
if (isRecommendMode) {
|
||||
listener.onTagAddClick(tag, position);
|
||||
|
||||
try {
|
||||
// 显示文本
|
||||
if (isRecommendMode) {
|
||||
tagText.setText("+ " + tag.getName());
|
||||
deleteIcon.setVisibility(View.GONE);
|
||||
} else {
|
||||
tagText.setText(tag.getName());
|
||||
// 我的频道模式下,除了"推荐"外都可以删除
|
||||
if (position == 0 && "推荐".equals(tag.getName())) {
|
||||
deleteIcon.setVisibility(View.GONE);
|
||||
} 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.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
|
@ -44,6 +45,7 @@ import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
|||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.example.livestreaming.net.ApiClient;
|
||||
import com.example.livestreaming.net.ApiResponse;
|
||||
import com.example.livestreaming.net.ApiService;
|
||||
import com.example.livestreaming.net.CommunityResponse;
|
||||
import com.example.livestreaming.net.ConversationResponse;
|
||||
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_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_SETTINGS, "设置", "账号、隐私、通知", R.drawable.ic_menu_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 {
|
||||
View notificationIcon = findViewById(R.id.notificationIcon);
|
||||
|
|
@ -1751,6 +1765,9 @@ public class MainActivity extends AppCompatActivity {
|
|||
private void loadCategoriesFromBackend() {
|
||||
Log.d(TAG, "loadCategoriesFromBackend() 开始加载直播间分类");
|
||||
|
||||
// 先从本地加载我的频道配置
|
||||
loadMyChannelsFromPrefs();
|
||||
|
||||
// 调用后端接口获取直播间分类列表
|
||||
ApiClient.getService(getApplicationContext()).getLiveRoomCategories()
|
||||
.enqueue(new Callback<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>>() {
|
||||
|
|
@ -1767,8 +1784,11 @@ public class MainActivity extends AppCompatActivity {
|
|||
|
||||
if (categories != null && !categories.isEmpty()) {
|
||||
Log.d(TAG, "loadCategoriesFromBackend() 成功获取 " + categories.size() + " 个分类");
|
||||
// 更新分类标签
|
||||
updateCategoryTabs(categories);
|
||||
// 缓存后端分类数据
|
||||
allBackendCategories.clear();
|
||||
allBackendCategories.addAll(categories);
|
||||
// 使用我的频道配置更新分类标签
|
||||
updateCategoryTabsFromMyChannels();
|
||||
} else {
|
||||
Log.w(TAG, "loadCategoriesFromBackend() 未获取到分类数据,使用默认分类");
|
||||
// 使用默认分类
|
||||
|
|
@ -1823,32 +1843,14 @@ public class MainActivity extends AppCompatActivity {
|
|||
private void useDefaultCategories() {
|
||||
if (binding == null || binding.categoryTabs == null) return;
|
||||
|
||||
// 如果我的频道配置为空,初始化默认配置
|
||||
if (myChannels.isEmpty()) {
|
||||
initDefaultMyChannels();
|
||||
}
|
||||
|
||||
runOnUiThread(() -> {
|
||||
// 清空现有标签
|
||||
binding.categoryTabs.removeAllTabs();
|
||||
|
||||
// 使用后端分类(如果已加载)
|
||||
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();
|
||||
// 使用我的频道配置更新分类标签
|
||||
updateCategoryTabsFromMyChannels();
|
||||
|
||||
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.net.ApiClient;
|
||||
import com.example.livestreaming.net.ApiResponse;
|
||||
import com.example.livestreaming.net.PageResponse;
|
||||
import com.example.livestreaming.net.UserInfoResponse;
|
||||
import com.example.livestreaming.net.WorksResponse;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
|
||||
|
|
@ -70,6 +72,7 @@ public class ProfileActivity extends AppCompatActivity {
|
|||
|
||||
private ActivityResultLauncher<Intent> editProfileLauncher;
|
||||
private UserWorksAdapter worksAdapter;
|
||||
private WorksAdapter myWorksAdapter;
|
||||
|
||||
public static void start(Context context) {
|
||||
Intent intent = new Intent(context, ProfileActivity.class);
|
||||
|
|
@ -437,13 +440,19 @@ public class ProfileActivity extends AppCompatActivity {
|
|||
startActivity(new Intent(this, FollowingActivity.class));
|
||||
});
|
||||
binding.action2.setOnClickListener(v -> {
|
||||
// 我的收藏(点赞的直播间)
|
||||
// 我的点赞(作品+直播间)
|
||||
if (!AuthHelper.requireLogin(this, "查看点赞需要登录")) {
|
||||
return;
|
||||
}
|
||||
MyLikesActivity.start(this);
|
||||
});
|
||||
binding.action3.setOnClickListener(v -> {
|
||||
// 我的收藏(作品+直播间)
|
||||
if (!AuthHelper.requireLogin(this, "查看收藏需要登录")) {
|
||||
return;
|
||||
}
|
||||
startActivity(new Intent(this, LikedRoomsActivity.class));
|
||||
MyCollectionsActivity.start(this);
|
||||
});
|
||||
binding.action3.setOnClickListener(v -> startActivity(new Intent(this, MyFriendsActivity.class)));
|
||||
binding.action4.setOnClickListener(v -> {
|
||||
// 我的记录 - 跳转到统一记录页面
|
||||
if (!AuthHelper.requireLogin(this, "查看记录需要登录")) {
|
||||
|
|
@ -474,11 +483,11 @@ public class ProfileActivity extends AppCompatActivity {
|
|||
});
|
||||
|
||||
binding.addFriendBtn.setOnClickListener(v -> {
|
||||
// 检查登录状态,添加好友需要登录
|
||||
if (!AuthHelper.requireLogin(this, "添加好友需要登录")) {
|
||||
// 我的挚友(原添加好友功能已在挚友页面内)
|
||||
if (!AuthHelper.requireLogin(this, "查看挚友需要登录")) {
|
||||
return;
|
||||
}
|
||||
AddFriendActivity.start(this);
|
||||
startActivity(new Intent(this, MyFriendsActivity.class));
|
||||
});
|
||||
|
||||
// 我的钱包按钮点击事件
|
||||
|
|
@ -554,45 +563,91 @@ public class ProfileActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
private void setupWorksRecycler() {
|
||||
worksAdapter = new UserWorksAdapter();
|
||||
worksAdapter.setOnWorkClickListener(workItem -> {
|
||||
if (workItem != null && !TextUtils.isEmpty(workItem.getId())) {
|
||||
WorkDetailActivity.start(this, workItem.getId());
|
||||
// 设置我的作品区域
|
||||
myWorksAdapter = new WorksAdapter(work -> {
|
||||
if (work != null && work.getId() != null) {
|
||||
WorkDetailActivity.start(this, String.valueOf(work.getId()));
|
||||
}
|
||||
});
|
||||
binding.worksRecycler.setLayoutManager(new GridLayoutManager(this, 3));
|
||||
binding.worksRecycler.setAdapter(worksAdapter);
|
||||
loadWorks();
|
||||
binding.myWorksRecycler.setLayoutManager(new GridLayoutManager(this, 2));
|
||||
binding.myWorksRecycler.setAdapter(myWorksAdapter);
|
||||
|
||||
// 发布按钮点击事件
|
||||
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() {
|
||||
// TODO: 接入后端接口 - 获取当前用户的作品列表
|
||||
// 接口路径: GET /api/users/{userId}/works
|
||||
// 请求方法: 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);
|
||||
}
|
||||
// 旧方法保留兼容,实际使用loadMyWorks
|
||||
loadMyWorks();
|
||||
}
|
||||
|
||||
private void showTab(int index) {
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@ public class PublishWorkActivity extends AppCompatActivity {
|
|||
private String selectedVisibility = "所有人可见"; // 可见范围
|
||||
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 ActivityResultLauncher<String[]> requestLocationPermissionLauncher;
|
||||
|
|
@ -112,6 +116,7 @@ public class PublishWorkActivity extends AppCompatActivity {
|
|||
setupMediaAdapter();
|
||||
setupLaunchers();
|
||||
setupClickListeners();
|
||||
loadCategories(); // 加载分类数据
|
||||
}
|
||||
|
||||
private void setupToolbar() {
|
||||
|
|
@ -347,6 +352,9 @@ public class PublishWorkActivity extends AppCompatActivity {
|
|||
binding.selectCoverButton.setOnClickListener(v -> showCoverPickerDialog());
|
||||
binding.publishButton.setOnClickListener(v -> publishWork());
|
||||
|
||||
// 分类选择点击事件
|
||||
binding.categorySpinner.setOnClickListener(v -> showCategoryPickerDialog());
|
||||
|
||||
// 封面预览点击也可以选择封面
|
||||
binding.coverPreview.setOnClickListener(v -> {
|
||||
showCoverPickerDialog();
|
||||
|
|
@ -964,6 +972,11 @@ public class PublishWorkActivity extends AppCompatActivity {
|
|||
commentSettingValue = "DISABLED";
|
||||
}
|
||||
request.setCommentSetting(commentSettingValue);
|
||||
|
||||
// 设置分类ID
|
||||
if (selectedCategory != null) {
|
||||
request.setCategoryId(selectedCategory.getId());
|
||||
}
|
||||
|
||||
ApiService apiService = ApiClient.getService(this);
|
||||
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.ApiClient;
|
||||
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.material.tabs.TabLayout;
|
||||
|
||||
|
|
@ -47,10 +49,14 @@ public class SearchActivity extends AppCompatActivity {
|
|||
// 主播列表
|
||||
private SearchStreamerAdapter streamersAdapter;
|
||||
private final List<Map<String, Object>> streamersList = new ArrayList<>();
|
||||
|
||||
// 作品列表
|
||||
private WorksAdapter worksAdapter;
|
||||
private final List<WorksResponse> worksList = new ArrayList<>();
|
||||
|
||||
private boolean isSearching = false;
|
||||
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";
|
||||
|
||||
|
|
@ -118,11 +124,11 @@ public class SearchActivity extends AppCompatActivity {
|
|||
streamersAdapter.setOnStreamerClickListener(new SearchStreamerAdapter.OnStreamerClickListener() {
|
||||
@Override
|
||||
public void onStreamerClick(Map<String, Object> streamer) {
|
||||
// 点击主播,跳转到主播主页
|
||||
// 点击用户,跳转到用户主页
|
||||
Object id = streamer.get("id");
|
||||
if (id != null) {
|
||||
int streamerId = ((Number) id).intValue();
|
||||
UserProfileActivity.start(SearchActivity.this, streamerId);
|
||||
int userId = ((Number) id).intValue();
|
||||
UserProfileReadOnlyActivity.start(SearchActivity.this, userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,12 +141,24 @@ public class SearchActivity extends AppCompatActivity {
|
|||
|
||||
binding.streamersRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
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() {
|
||||
// 添加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.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
|
|
@ -216,6 +234,149 @@ public class SearchActivity extends AppCompatActivity {
|
|||
binding.hotSearchContainer.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);
|
||||
Call<ApiResponse<Map<String, Object>>> call =
|
||||
apiService.comprehensiveSearch(keyword, 1, 20);
|
||||
|
|
@ -263,11 +424,12 @@ public class SearchActivity extends AppCompatActivity {
|
|||
// 获取总数
|
||||
int roomsTotal = getIntValue(data.get("roomsTotal"), 0);
|
||||
int streamersTotal = getIntValue(data.get("streamersTotal"), 0);
|
||||
int worksTotal = 0; // 旧方法没有作品数据
|
||||
|
||||
Log.d(TAG, "搜索成功,直播间: " + roomsTotal + ", 主播: " + streamersTotal);
|
||||
|
||||
// 更新Tab标题显示数量
|
||||
updateTabTitles(roomsTotal, streamersTotal);
|
||||
updateTabTitles(roomsTotal, streamersTotal, worksTotal);
|
||||
|
||||
// 显示搜索结果
|
||||
showSearchResults();
|
||||
|
|
@ -301,15 +463,19 @@ public class SearchActivity extends AppCompatActivity {
|
|||
/**
|
||||
* 更新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 streamersTab = binding.searchTabs.getTabAt(1);
|
||||
TabLayout.Tab worksTab = binding.searchTabs.getTabAt(2);
|
||||
|
||||
if (roomsTab != null) {
|
||||
roomsTab.setText("直播间 " + roomsCount);
|
||||
}
|
||||
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));
|
||||
streamersAdapter.submitList(new ArrayList<>(streamersList));
|
||||
worksAdapter.submitList(new ArrayList<>(worksList));
|
||||
|
||||
// 根据当前Tab显示对应内容
|
||||
updateTabContent();
|
||||
|
|
@ -336,6 +503,7 @@ public class SearchActivity extends AppCompatActivity {
|
|||
// 显示直播间
|
||||
binding.roomsRecyclerView.setVisibility(View.VISIBLE);
|
||||
binding.streamersRecyclerView.setVisibility(View.GONE);
|
||||
binding.worksRecyclerView.setVisibility(View.GONE);
|
||||
|
||||
if (roomsList.isEmpty()) {
|
||||
binding.emptyStateView.setNoSearchResultsState();
|
||||
|
|
@ -343,10 +511,11 @@ public class SearchActivity extends AppCompatActivity {
|
|||
} else {
|
||||
binding.emptyStateView.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
} else if (currentTab == 1) {
|
||||
// 显示主播
|
||||
binding.roomsRecyclerView.setVisibility(View.GONE);
|
||||
binding.streamersRecyclerView.setVisibility(View.VISIBLE);
|
||||
binding.worksRecyclerView.setVisibility(View.GONE);
|
||||
|
||||
if (streamersList.isEmpty()) {
|
||||
binding.emptyStateView.setNoSearchResultsState();
|
||||
|
|
@ -354,6 +523,18 @@ public class SearchActivity extends AppCompatActivity {
|
|||
} else {
|
||||
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.roomsRecyclerView.setVisibility(View.GONE);
|
||||
binding.streamersRecyclerView.setVisibility(View.GONE);
|
||||
binding.worksRecyclerView.setVisibility(View.GONE);
|
||||
binding.emptyStateView.setVisibility(View.GONE);
|
||||
binding.hotSearchContainer.setVisibility(View.VISIBLE);
|
||||
|
||||
// 重置Tab标题
|
||||
TabLayout.Tab roomsTab = binding.searchTabs.getTabAt(0);
|
||||
TabLayout.Tab streamersTab = binding.searchTabs.getTabAt(1);
|
||||
TabLayout.Tab worksTab = binding.searchTabs.getTabAt(2);
|
||||
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) {
|
||||
if (!AuthHelper.requireLogin(this, "关注主播需要登录")) {
|
||||
if (!AuthHelper.requireLogin(this, "关注用户需要登录")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object id = streamer.get("id");
|
||||
if (id == null) return;
|
||||
|
||||
int streamerId = ((Number) id).intValue();
|
||||
int userId = ((Number) id).intValue();
|
||||
Object isFollowing = streamer.get("isFollowing");
|
||||
boolean following = isFollowing instanceof Boolean && (Boolean) isFollowing;
|
||||
|
||||
String action = following ? "unfollow" : "follow";
|
||||
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("followedId", streamerId);
|
||||
body.put("userId", userId);
|
||||
|
||||
ApiService apiService = ApiClient.getService(this);
|
||||
Call<ApiResponse<Map<String, Object>>> call = following ?
|
||||
|
|
|
|||
|
|
@ -68,6 +68,15 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
|
|||
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
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
@ -122,6 +131,9 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
|
|||
// 检查关注状态
|
||||
checkFollowStatus();
|
||||
|
||||
// 初始化关注按钮显示(默认显示"关注")
|
||||
updateFollowButton();
|
||||
|
||||
// 检查好友状态
|
||||
checkFriendStatus();
|
||||
|
||||
|
|
@ -285,11 +297,13 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
|
|||
if (isFollowing) {
|
||||
binding.followButton.setText("已关注");
|
||||
binding.followButton.setSelected(true);
|
||||
binding.followButton.setBackgroundResource(R.drawable.bg_follow_button_followed);
|
||||
binding.followButton.setTextColor(getResources().getColor(android.R.color.darker_gray, null));
|
||||
} else {
|
||||
binding.followButton.setText("关注");
|
||||
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) {
|
||||
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 worksCount = 6 + (h % 18);
|
||||
|
|
@ -517,16 +692,9 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity {
|
|||
|
||||
// 创建演示作品列表
|
||||
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++) {
|
||||
WorkItem work = new WorkItem();
|
||||
work.setId("demo_" + userId + "_" + i);
|
||||
work.setId("demo_" + i);
|
||||
work.setTitle("演示作品 " + (i + 1));
|
||||
work.setType(WorkItem.WorkType.IMAGE);
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ public class WorkDetailActivity extends AppCompatActivity {
|
|||
// 状态标记
|
||||
private boolean isLiked = false;
|
||||
private boolean isFavorited = false;
|
||||
private boolean isFollowed = false; // 是否已关注作者
|
||||
private int commentCount = 0;
|
||||
|
||||
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() {
|
||||
// ============================================
|
||||
// TODO: 判断是否是当前用户的作品
|
||||
|
|
@ -1051,6 +1229,7 @@ public class WorkDetailActivity extends AppCompatActivity {
|
|||
setupContent();
|
||||
setupActionButtons();
|
||||
setupActionButton();
|
||||
setupAuthorSection(); // 设置作者头像和关注按钮
|
||||
|
||||
// 获取真实的评论数量
|
||||
loadRealCommentCount(worksResponse.getId());
|
||||
|
|
@ -1099,6 +1278,11 @@ public class WorkDetailActivity extends AppCompatActivity {
|
|||
// 防止 publishTime 为 null 导致 NullPointerException
|
||||
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())) {
|
||||
item.setType(WorkItem.WorkType.VIDEO);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ public class WorkItem implements Parcelable {
|
|||
private long publishTime;
|
||||
private WorkType type; // 作品类型:图片或视频
|
||||
|
||||
// 作者信息
|
||||
private int authorId; // 作者用户ID
|
||||
private String authorName; // 作者昵称
|
||||
private String authorAvatar; // 作者头像URL
|
||||
|
||||
// 本地使用的URI(发布时使用)
|
||||
private transient Uri coverUri;
|
||||
private transient Uri videoUri;
|
||||
|
|
@ -41,6 +46,9 @@ public class WorkItem implements Parcelable {
|
|||
this.publishTime = System.currentTimeMillis();
|
||||
this.imageUrls = new ArrayList<>();
|
||||
this.imageUris = new ArrayList<>();
|
||||
this.authorId = 0;
|
||||
this.authorName = "";
|
||||
this.authorAvatar = "";
|
||||
}
|
||||
|
||||
public WorkItem(String id, String title, String description, String coverUrl,
|
||||
|
|
@ -171,6 +179,30 @@ public class WorkItem implements Parcelable {
|
|||
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
|
||||
protected WorkItem(Parcel in) {
|
||||
id = in.readString();
|
||||
|
|
@ -186,6 +218,9 @@ public class WorkItem implements Parcelable {
|
|||
int typeOrdinal = in.readInt();
|
||||
type = typeOrdinal >= 0 && typeOrdinal < WorkType.values().length
|
||||
? WorkType.values()[typeOrdinal] : WorkType.IMAGE;
|
||||
authorId = in.readInt();
|
||||
authorName = in.readString();
|
||||
authorAvatar = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -201,6 +236,9 @@ public class WorkItem implements Parcelable {
|
|||
dest.writeInt(viewCount);
|
||||
dest.writeLong(publishTime);
|
||||
dest.writeInt(type != null ? type.ordinal() : -1);
|
||||
dest.writeInt(authorId);
|
||||
dest.writeString(authorName);
|
||||
dest.writeString(authorAvatar);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -377,6 +377,23 @@ public interface ApiService {
|
|||
@GET("api/front/works/detail/{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}")
|
||||
Call<ApiResponse<Boolean>> deleteWork(@Path("id") long id);
|
||||
|
||||
|
|
@ -979,4 +996,28 @@ public interface ApiService {
|
|||
@Query("categoryId") Integer categoryId,
|
||||
@Query("page") int page,
|
||||
@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"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#F5F5F5" />
|
||||
<corners android:radius="16dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#E0E0E0" />
|
||||
<corners android:radius="4dp" />
|
||||
<solid android:color="@android:color/white" />
|
||||
</shape>
|
||||
|
|
|
|||
|
|
@ -1,9 +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="#F8F8F8" />
|
||||
<corners android:radius="4dp" />
|
||||
<solid android:color="#F5F5F5" />
|
||||
<corners android:radius="16dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#E8E8E8" />
|
||||
android:color="#E0E0E0" />
|
||||
</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"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#2196F3" />
|
||||
<corners android:radius="16dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#333333" />
|
||||
<corners android:radius="4dp" />
|
||||
<solid android:color="@android:color/white" />
|
||||
android:width="2dp"
|
||||
android:color="#1976D2" />
|
||||
</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"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 已关注状态 -->
|
||||
<item android:state_selected="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#F5F5F5" />
|
||||
<corners android:radius="22dp" />
|
||||
<stroke android:width="1dp" android:color="#DDDDDD" />
|
||||
</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>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="#FF4757" />
|
||||
<size
|
||||
android:width="20dp"
|
||||
android:height="20dp" />
|
||||
</shape>
|
||||
|
|
|
|||
|
|
@ -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:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintEnd_toStartOf="@id/topActionClock"
|
||||
app:layout_constraintStart_toStartOf="@id/name"
|
||||
|
|
@ -187,6 +188,7 @@
|
|||
android:background="@drawable/bg_circle_white_60"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_clock_24"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/topActionMore"
|
||||
app:layout_constraintTop_toTopOf="@id/topActionMore" />
|
||||
|
||||
|
|
@ -198,6 +200,7 @@
|
|||
android:background="@drawable/bg_circle_white_60"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_crosshair_24"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/topActionClock"
|
||||
app:layout_constraintTop_toTopOf="@id/topActionMore" />
|
||||
|
||||
|
|
@ -439,7 +442,7 @@
|
|||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_like_24"
|
||||
android:src="@drawable/ic_like_filled_24"
|
||||
android:tint="#FF4081" />
|
||||
|
||||
</FrameLayout>
|
||||
|
|
@ -453,7 +456,7 @@
|
|||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="我的收藏"
|
||||
android:text="我的点赞"
|
||||
android:textColor="#111111"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
|
@ -488,8 +491,8 @@
|
|||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_heart_24"
|
||||
android:tint="#E91E63" />
|
||||
android:src="@drawable/ic_star_24"
|
||||
android:tint="#FFA726" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
|
@ -502,7 +505,7 @@
|
|||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="我的挚友"
|
||||
android:text="我的收藏"
|
||||
android:textColor="#111111"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
|
@ -512,7 +515,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="0人"
|
||||
android:text="0个"
|
||||
android:textColor="#999999"
|
||||
android:textSize="11sp" />
|
||||
|
||||
|
|
@ -525,7 +528,8 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="64dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="44dp"
|
||||
|
|
@ -686,6 +690,91 @@
|
|||
|
||||
</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
|
||||
android:id="@+id/profileTabs"
|
||||
android:layout_width="0dp"
|
||||
|
|
@ -700,7 +789,7 @@
|
|||
app:tabTextColor="#666666"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/bottomButtons">
|
||||
app:layout_constraintTop_toBottomOf="@id/myWorksSection">
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -348,6 +348,58 @@
|
|||
|
||||
</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
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -114,6 +114,18 @@
|
|||
android:paddingBottom="24dp"
|
||||
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
|
||||
android:id="@+id/resultsRecyclerView"
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@
|
|||
android:id="@+id/space_bio_margin_top"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintHeight_percent="0.025"
|
||||
app:layout_constraintHeight_percent="0.015"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/avatarRing" />
|
||||
|
||||
|
|
@ -271,7 +271,7 @@
|
|||
android:id="@+id/space_addFriend_margin_top"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintHeight_percent="0.028"
|
||||
app:layout_constraintHeight_percent="0.015"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/bio" />
|
||||
|
||||
|
|
@ -292,10 +292,10 @@
|
|||
android:layout_height="44dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:background="@drawable/bg_follow_button"
|
||||
android:background="@drawable/bg_follow_button_normal"
|
||||
android:gravity="center"
|
||||
android:text="关注"
|
||||
android:textColor="#FF4757"
|
||||
android:textColor="#333333"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- 右侧操作按钮(点赞、收藏、评论) -->
|
||||
<!-- 右侧操作按钮(用户头像、关注、点赞、收藏、评论) -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -89,6 +89,40 @@
|
|||
android:orientation="vertical"
|
||||
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
|
||||
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"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp">
|
||||
android:layout_margin="6dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/channelName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="8dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="频道名"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#333333"
|
||||
android:background="@drawable/bg_channel_tag_normal" />
|
||||
android:textSize="13sp"
|
||||
android:textColor="#666666"
|
||||
android:background="@drawable/bg_channel_tag_normal"
|
||||
android:minWidth="60dp"
|
||||
android:gravity="center" />
|
||||
|
||||
<!-- 固定标识(前4个频道显示) -->
|
||||
<!-- 删除按钮(我的频道编辑模式显示) -->
|
||||
<ImageView
|
||||
android:id="@+id/fixedIcon"
|
||||
android:layout_width="14dp"
|
||||
android:layout_height="14dp"
|
||||
android:id="@+id/deleteIcon"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginTop="-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:background="@drawable/bg_circle_white"
|
||||
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>
|
||||
|
|
|
|||
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