zhibo/android-app/app/src/main/java/com/example/livestreaming/MainActivity.java
2026-01-06 10:25:40 +08:00

3482 lines
157 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.example.livestreaming;
import android.Manifest;
import android.util.Log;
import android.content.res.AssetManager;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.bumptech.glide.Glide;
import com.example.livestreaming.databinding.ActivityMainBinding;
import com.example.livestreaming.databinding.DialogCreateRoomBinding;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.tabs.TabLayout;
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.CommunityResponse;
import com.example.livestreaming.net.ConversationResponse;
import com.example.livestreaming.net.CreateRoomRequest;
import com.example.livestreaming.net.PageResponse;
import com.example.livestreaming.net.Room;
import com.example.livestreaming.net.StreamConfig;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ActivityMainBinding binding;
private RoomsAdapter adapter;
private final List<Room> allRooms = new ArrayList<>();
private final List<Room> followRooms = new ArrayList<>(); // 关注页面的房间列表
private final List<Room> discoverRooms = new ArrayList<>(); // 发现页面的房间列表
private final List<NearbyUser> nearbyUsers = new ArrayList<>(); // 附近页面的用户列表
private NearbyUsersAdapter nearbyUsersAdapter; // 附近页面的适配器
// 新Tab布局相关
private RecommendUserAdapter recommendUserAdapter; // 关注页面推荐用户适配器
private ChannelTagAdapter myChannelAdapter; // 发现页面我的频道适配器
private ChannelTagAdapter recommendChannelAdapter; // 发现页面推荐频道适配器
private final List<RecommendUserAdapter.RecommendUser> recommendUsers = new ArrayList<>(); // 推荐用户列表
private final List<ChannelTagAdapter.ChannelTag> myChannels = new ArrayList<>(); // 我的频道列表
private final List<ChannelTagAdapter.ChannelTag> recommendChannels = new ArrayList<>(); // 推荐频道列表
private StaggeredGridLayoutManager roomsLayoutManager; // 房间列表的布局管理器
private LinearLayoutManager nearbyLayoutManager; // 附近用户列表的布局管理器
private String currentCategory = "推荐";
private String currentTopTab = "发现"; // 当前选中的顶部标签:关注、发现、附近
private CategoryFilterManager filterManager;
private int filterRequestId = 0; // 用于防止旧的筛选结果覆盖新的结果
// 缓存后端返回的所有直播间分类
private final List<com.example.livestreaming.net.CategoryResponse> allBackendCategories = new ArrayList<>();
private final Handler handler = new Handler(Looper.getMainLooper());
private Runnable pollRunnable;
private boolean isFetching;
private long lastFetchMs;
private static final int REQUEST_RECORD_AUDIO_PERMISSION = 200;
private static final int REQUEST_LOCATION_PERMISSION = 201;
private SpeechRecognizer speechRecognizer;
private Intent speechRecognizerIntent;
private boolean isListening = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 用户打开APP时不需要强制登录可以直接使用APP
// 只有在使用需要登录的功能时(如加好友、发送弹幕等),才检查登录状态
// 登录和注册功能已在LoginActivity中实现
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 清除之前缓存的自定义API地址使用BuildConfig中的配置
ApiClient.clearCustomBaseUrl(getApplicationContext());
ApiClient.getService(getApplicationContext());
// 调试:打印当前使用的 API 地址
String currentApiUrl = ApiClient.getCurrentBaseUrl(getApplicationContext());
Log.d(TAG, "========== API 配置 ==========");
Log.d(TAG, "当前 API 地址: " + currentApiUrl);
Log.d(TAG, "BuildConfig EMULATOR: " + com.example.livestreaming.BuildConfig.API_BASE_URL_EMULATOR);
Log.d(TAG, "BuildConfig DEVICE: " + com.example.livestreaming.BuildConfig.API_BASE_URL_DEVICE);
Log.d(TAG, "==============================");
// 立即显示缓存数据,提升启动速度
setupRecyclerView();
setupUI();
loadAvatarFromPrefs();
setupSpeechRecognizer();
// 初始化未读消息数量
if (UnreadMessageManager.getUnreadCount(this) == 0) {
// 从会话列表获取总未读数量
fetchUnreadMessageCount();
}
// 初始化顶部标签页数据
initializeTopTabData();
// 默认显示"发现"界面
if (binding != null && binding.topTabs != null) {
// 在布局完成后,默认选中"发现"标签索引为1关注=0, 发现=1, 附近=2
binding.topTabs.post(() -> {
TabLayout.Tab discoverTab = binding.topTabs.getTabAt(1); // "发现"标签的索引
if (discoverTab != null) {
// 选中"发现"标签,这会触发 onTabSelected 监听器,自动调用 showDiscoverTab()
discoverTab.select();
} else {
// 如果找不到标签,直接显示发现页面
showDiscoverTab();
}
});
} else {
// 如果 topTabs 为空,直接显示发现页面
showDiscoverTab();
}
// 异步加载资源文件,避免阻塞主线程
loadCoverAssetsAsync();
}
private void setupDrawerCards() {
if (binding == null || binding.drawerCards == null) return;
DrawerCardsAdapter adapter = new DrawerCardsAdapter(item -> {
if (item == null) return;
handleDrawerAction(item);
DrawerLayout drawerLayout = binding.drawerLayout;
if (drawerLayout != null) {
drawerLayout.closeDrawer(GravityCompat.START);
}
});
binding.drawerCards.drawerRecyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.drawerCards.drawerRecyclerView.setAdapter(adapter);
List<DrawerCardItem> items = new ArrayList<>();
items.add(new DrawerCardItem(DrawerCardItem.ACTION_PROFILE, "个人主页", "资料、作品、粉丝", R.drawable.ic_person_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_MESSAGES, "消息", "私信、互动通知", R.drawable.ic_chat_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_MY_FRIENDS, "我的好友", "通讯录与挚友", R.drawable.ic_people_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_GROUPS, "我的群组", "群聊与群组管理", R.drawable.ic_group_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_FISH_POND, "缘池", "附近与社交圈", R.drawable.ic_people_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_FOLLOWING, "我的关注", "你关注的主播", R.drawable.ic_people_24));
// 隐藏粉丝和获赞菜单项
// items.add(new DrawerCardItem(DrawerCardItem.ACTION_FANS, "粉丝", "关注你的人", R.drawable.ic_people_24));
// items.add(new DrawerCardItem(DrawerCardItem.ACTION_LIKES, "获赞", "收到的点赞", R.drawable.ic_heart_24));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_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));
items.add(new DrawerCardItem(DrawerCardItem.ACTION_ABOUT, "关于", "版本信息与协议", R.drawable.ic_menu_24));
adapter.submitList(items);
}
private void handleDrawerAction(DrawerCardItem item) {
int action = item.getAction();
if (action == DrawerCardItem.ACTION_PROFILE) {
// 检查登录状态,个人主页需要登录
if (!AuthHelper.requireLogin(this, "查看个人主页需要登录")) {
return;
}
ProfileActivity.start(this);
finish();
return;
}
if (action == DrawerCardItem.ACTION_MESSAGES) {
// MessagesActivity内部已检查登录这里直接跳转
MessagesActivity.start(this);
finish();
return;
}
if (action == DrawerCardItem.ACTION_MY_FRIENDS) {
// 检查登录状态,我的好友需要登录
if (!AuthHelper.requireLogin(this, "查看好友列表需要登录")) {
return;
}
startActivity(new Intent(this, MyFriendsActivity.class));
return;
}
if (action == DrawerCardItem.ACTION_GROUPS) {
// 检查登录状态,我的群组需要登录
if (!AuthHelper.requireLogin(this, "查看群组列表需要登录")) {
return;
}
GroupListActivity.start(this);
return;
}
if (action == DrawerCardItem.ACTION_FISH_POND) {
// 检查登录状态,缘池功能需要登录
if (!AuthHelper.requireLogin(this, "缘池功能需要登录")) {
return;
}
startActivity(new Intent(this, FishPondActivity.class));
finish();
return;
}
if (action == DrawerCardItem.ACTION_FOLLOWING) {
// 检查登录状态,我的关注需要登录
if (!AuthHelper.requireLogin(this, "查看关注列表需要登录")) {
return;
}
FollowingListActivity.start(this);
return;
}
if (action == DrawerCardItem.ACTION_FANS) {
// 检查登录状态,我的粉丝需要登录
if (!AuthHelper.requireLogin(this, "查看粉丝列表需要登录")) {
return;
}
FansListActivity.start(this);
return;
}
if (action == DrawerCardItem.ACTION_LIKES) {
// 检查登录状态,获赞列表需要登录
if (!AuthHelper.requireLogin(this, "查看获赞列表需要登录")) {
return;
}
LikesListActivity.start(this);
return;
}
if (action == DrawerCardItem.ACTION_HISTORY) {
// 检查登录状态,观看历史需要登录
if (!AuthHelper.requireLogin(this, "查看观看历史需要登录")) {
return;
}
MyRecordsActivity.start(this);
return;
}
if (action == DrawerCardItem.ACTION_SEARCH) {
// 搜索功能不需要登录,游客也可以搜索
SearchActivity.start(this);
return;
}
if (action == DrawerCardItem.ACTION_SETTINGS) {
// SettingsPageActivity内部已检查登录这里直接跳转
SettingsPageActivity.start(this, "");
return;
}
if (action == DrawerCardItem.ACTION_HELP) {
// 帮助页面不需要登录
SettingsPageActivity.start(this, SettingsPageActivity.PAGE_HELP);
return;
}
if (action == DrawerCardItem.ACTION_ABOUT) {
SettingsPageActivity.start(this, SettingsPageActivity.PAGE_ABOUT);
return;
}
String t = item.getTitle() != null ? item.getTitle() : "";
Toast.makeText(this, "暂未接入:" + t, Toast.LENGTH_SHORT).show();
}
private void setupRecyclerView() {
adapter = new RoomsAdapter(room -> {
if (room == null) return;
Intent intent = new Intent(MainActivity.this, RoomDetailActivity.class);
intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
startActivity(intent);
});
// 保存房间列表的布局管理器
roomsLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
roomsLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
// 创建附近用户列表的布局管理器
nearbyLayoutManager = new LinearLayoutManager(this);
binding.roomsRecyclerView.setLayoutManager(roomsLayoutManager);
binding.roomsRecyclerView.setAdapter(adapter);
// 配置RecyclerView动画优化筛选时的过渡效果
androidx.recyclerview.widget.DefaultItemAnimator animator = new androidx.recyclerview.widget.DefaultItemAnimator();
animator.setAddDuration(200); // 添加动画时长
animator.setRemoveDuration(200); // 删除动画时长
animator.setMoveDuration(200); // 移动动画时长
animator.setChangeDuration(200); // 变更动画时长
binding.roomsRecyclerView.setItemAnimator(animator);
// 注意:房间数据会在 showDiscoverTab() 中从 discoverRooms 加载
// 这里不再直接显示数据,而是等待顶部标签初始化完成后再显示
}
private void setupUI() {
// 启用下拉刷新
if (binding.swipeRefresh != null) {
binding.swipeRefresh.setOnRefreshListener(() -> {
fetchDiscoverRooms();
});
}
setupDrawerCards();
binding.menuButton.setOnClickListener(new DebounceClickListener() {
@Override
public void onDebouncedClick(View v) {
DrawerLayout drawerLayout = binding.drawerLayout;
if (drawerLayout == null) return;
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
} else {
drawerLayout.openDrawer(GravityCompat.START);
}
}
});
binding.avatarButton.setOnClickListener(new DebounceClickListener() {
@Override
public void onDebouncedClick(View v) {
// 检查登录状态,个人主页需要登录
if (!AuthHelper.requireLogin(MainActivity.this, "查看个人主页需要登录")) {
return;
}
ProfileActivity.start(MainActivity.this);
finish();
}
});
// 设置搜索按钮点击事件
View searchButton = findViewById(R.id.searchButton);
if (searchButton != null) {
searchButton.setOnClickListener(new DebounceClickListener() {
@Override
public void onDebouncedClick(View v) {
SearchActivity.start(MainActivity.this);
}
});
}
// 设置通知图标点击事件(如果存在)
try {
View notificationIcon = findViewById(R.id.notificationIcon);
if (notificationIcon != null) {
notificationIcon.setOnClickListener(new DebounceClickListener() {
@Override
public void onDebouncedClick(View v) {
NotificationsActivity.start(MainActivity.this);
}
});
}
} catch (Exception e) {
// 如果通知图标不存在,忽略错误
}
binding.topTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
if (tab == null) return;
// 用户点击后,显示选中样式(显示指示器,改变文字颜色)
binding.topTabs.setSelectedTabIndicatorHeight(2); // 显示指示器
// 更新布局属性以显示选中样式
binding.topTabs.setTabTextColors(
getResources().getColor(android.R.color.darker_gray, null), // 未选中颜色
getResources().getColor(R.color.purple_500, null) // 选中颜色
);
CharSequence title = tab.getText();
currentTopTab = title != null ? title.toString() : "发现";
// 切换显示的内容
switchTopTabContent(currentTopTab);
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
// 取消选中时,恢复默认样式
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
if (tab == null) return;
CharSequence title = tab.getText();
currentTopTab = title != null ? title.toString() : "发现";
// 切换显示的内容
switchTopTabContent(currentTopTab);
}
});
// 从后端加载分类数据
loadCategoriesFromBackend();
binding.categoryTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
if (tab == null) return;
CharSequence title = tab.getText();
currentCategory = title != null ? title.toString() : "推荐";
// 保存选中的分类
CategoryFilterManager.saveLastCategory(MainActivity.this, currentCategory);
// 应用筛选(带动画)
applyCategoryFilterWithAnimation(currentCategory);
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
if (tab == null) return;
CharSequence title = tab.getText();
currentCategory = title != null ? title.toString() : "推荐";
// 保存选中的分类
CategoryFilterManager.saveLastCategory(MainActivity.this, currentCategory);
// 应用筛选(带动画)
applyCategoryFilterWithAnimation(currentCategory);
}
});
binding.roomsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (dy <= 0) return;
RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
if (!(lm instanceof StaggeredGridLayoutManager)) return;
StaggeredGridLayoutManager sglm = (StaggeredGridLayoutManager) lm;
int total = sglm.getItemCount();
int[] last = sglm.findLastVisibleItemPositions(null);
int lastVisible = -1;
if (last != null) {
for (int v : last) {
if (v > lastVisible) lastVisible = v;
}
}
if (total <= 0) return;
if (lastVisible >= total - 4) {
long now = System.currentTimeMillis();
if (!isFetching && now - lastFetchMs > 1500) {
// 注释掉加载更多,使用静态数据
// fetchRooms();
}
}
}
});
// 设置添加直播按钮点击事件(添加防抖)
binding.fabAddLive.setOnClickListener(new DebounceClickListener() {
@Override
public void onDebouncedClick(View v) {
showCreateRoomDialog();
}
});
// 设置搜索框文本监听和图标切换
setupSearchBox();
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
bottomNavigation.setSelectedItemId(R.id.nav_home);
// 更新未读消息徽章
UnreadMessageManager.updateBadge(bottomNavigation);
bottomNavigation.setOnItemSelectedListener(item -> {
int id = item.getItemId();
if (id == R.id.nav_home) {
return true;
}
if (id == R.id.nav_friends) {
startActivity(new Intent(this, FishPondActivity.class));
finish();
return true;
}
if (id == R.id.nav_wish_tree) {
WishTreeActivity.start(this);
finish();
return true;
}
if (id == R.id.nav_messages) {
MessagesActivity.start(this);
finish();
return true;
}
if (id == R.id.nav_profile) {
// 检查登录状态,个人主页需要登录
if (!AuthHelper.requireLogin(this, "查看个人主页需要登录")) {
return false;
}
ProfileActivity.start(this);
finish();
return true;
}
return true;
});
}
private void setupSearchBox() {
// 监听搜索框文本变化
binding.searchEdit.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 根据文本是否为空切换图标
String text = s != null ? s.toString().trim() : "";
if (text.isEmpty()) {
// 文本为空,显示麦克风图标
binding.micIcon.setImageResource(R.drawable.ic_mic_24);
binding.micIcon.setContentDescription("mic");
// 清除搜索筛选,显示所有房间
applySearchFilter("");
} else {
// 文本不为空,显示搜索图标
binding.micIcon.setImageResource(R.drawable.ic_search_24);
binding.micIcon.setContentDescription("search");
// 实时筛选房间
applySearchFilter(text);
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
// 设置麦克风/搜索图标点击事件
binding.micIcon.setOnClickListener(v -> {
String searchText = binding.searchEdit.getText() != null ?
binding.searchEdit.getText().toString().trim() : "";
if (searchText.isEmpty()) {
// 如果文本为空,启动语音识别
startVoiceRecognition();
} else {
// 如果文本不为空,跳转到搜索页面
SearchActivity.start(MainActivity.this, searchText);
}
});
}
private void setupSpeechRecognizer() {
// 检查设备是否支持语音识别
if (!SpeechRecognizer.isRecognitionAvailable(this)) {
return;
}
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
speechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "zh-CN"); // 设置为中文
speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
speechRecognizer.setRecognitionListener(new RecognitionListener() {
@Override
public void onReadyForSpeech(Bundle params) {
isListening = true;
// 可以在这里显示"正在聆听..."的提示
binding.micIcon.setAlpha(0.5f);
}
@Override
public void onBeginningOfSpeech() {
// 开始说话
}
@Override
public void onRmsChanged(float rmsdB) {
// 音量变化,可以用来显示音量动画
}
@Override
public void onBufferReceived(byte[] buffer) {
// 接收音频数据
}
@Override
public void onEndOfSpeech() {
isListening = false;
binding.micIcon.setAlpha(1.0f);
}
@Override
public void onError(int error) {
isListening = false;
binding.micIcon.setAlpha(1.0f);
String errorMessage;
switch (error) {
case SpeechRecognizer.ERROR_AUDIO:
errorMessage = "音频错误";
break;
case SpeechRecognizer.ERROR_CLIENT:
errorMessage = "客户端错误";
break;
case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
errorMessage = "权限不足";
requestAudioPermission();
return;
case SpeechRecognizer.ERROR_NETWORK:
errorMessage = "网络错误";
break;
case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
errorMessage = "网络超时";
break;
case SpeechRecognizer.ERROR_NO_MATCH:
errorMessage = "未识别到语音";
break;
case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
errorMessage = "识别器忙碌";
break;
case SpeechRecognizer.ERROR_SERVER:
errorMessage = "服务器错误";
break;
case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
errorMessage = "未检测到语音";
break;
default:
errorMessage = "未知错误";
break;
}
// 只在非用户取消的情况下显示错误提示
if (error != SpeechRecognizer.ERROR_CLIENT) {
Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onResults(Bundle results) {
isListening = false;
binding.micIcon.setAlpha(1.0f);
ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
if (matches != null && !matches.isEmpty()) {
String spokenText = matches.get(0);
// 将识别结果填充到搜索框
binding.searchEdit.setText(spokenText);
binding.searchEdit.setSelection(spokenText.length());
}
}
@Override
public void onPartialResults(Bundle partialResults) {
// 部分结果,可以实时显示
ArrayList<String> matches = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
if (matches != null && !matches.isEmpty()) {
String partialText = matches.get(0);
// 可以在这里实时更新搜索框(可选)
// binding.searchEdit.setText(partialText);
}
}
@Override
public void onEvent(int eventType, Bundle params) {
// 事件回调
}
});
}
private void startVoiceRecognition() {
// 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
requestAudioPermission();
return;
}
// 检查是否正在监听
if (isListening) {
stopVoiceRecognition();
return;
}
// 检查SpeechRecognizer是否可用
if (speechRecognizer == null || speechRecognizerIntent == null) {
Toast.makeText(this, "语音识别功能不可用", Toast.LENGTH_SHORT).show();
return;
}
try {
speechRecognizer.startListening(speechRecognizerIntent);
} catch (Exception e) {
Toast.makeText(this, "启动语音识别失败", Toast.LENGTH_SHORT).show();
}
}
private void stopVoiceRecognition() {
if (speechRecognizer != null && isListening) {
speechRecognizer.stopListening();
isListening = false;
binding.micIcon.setAlpha(1.0f);
}
}
private void requestAudioPermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
new AlertDialog.Builder(this)
.setTitle("需要麦克风权限")
.setMessage("语音搜索需要访问麦克风权限,请在设置中允许")
.setPositiveButton("确定", (dialog, which) -> {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO},
REQUEST_RECORD_AUDIO_PERMISSION);
})
.setNegativeButton("取消", null)
.show();
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO},
REQUEST_RECORD_AUDIO_PERMISSION);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限已授予,可以开始语音识别
startVoiceRecognition();
} else {
Toast.makeText(this, "需要麦克风权限才能使用语音搜索", Toast.LENGTH_SHORT).show();
}
} else if (requestCode == REQUEST_LOCATION_PERMISSION) {
// 处理位置权限请求结果
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限已授予,刷新附近页面显示数据
showNearbyTab();
} else {
// 权限被拒绝
// 检查是否是因为用户选择了"不再询问"
// 如果 shouldShowRequestPermissionRationale 返回 false说明用户可能选择了"不再询问"
boolean shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION);
if (!shouldShowRationale) {
// 用户可能选择了"不再询问",提示去设置中开启
new AlertDialog.Builder(this)
.setTitle("需要位置权限")
.setMessage("您已拒绝位置权限,如需使用附近功能,请在设置中手动开启位置权限。")
.setPositiveButton("去设置", (dialog, which) -> {
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(android.net.Uri.parse("package:" + getPackageName()));
startActivity(intent);
})
.setNegativeButton("取消", null)
.show();
}
// 不切换页面,让用户停留在附近页面
// 界面已经显示了需要权限的提示,用户可以再次点击"授权"按钮
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (speechRecognizer != null) {
speechRecognizer.destroy();
speechRecognizer = null;
}
}
private void loadAvatarFromPrefs() {
try {
String avatarUri = getSharedPreferences("profile_prefs", MODE_PRIVATE)
.getString("profile_avatar_uri", null);
if (!TextUtils.isEmpty(avatarUri)) {
Uri uri = Uri.parse(avatarUri);
// 如果是 file:// 协议,尝试转换为 FileProvider URI
if ("file".equals(uri.getScheme())) {
try {
java.io.File file = new java.io.File(uri.getPath());
if (file.exists()) {
uri = FileProvider.getUriForFile(
this,
getPackageName() + ".fileprovider",
file
);
}
} catch (Exception e) {
// 如果转换失败,使用原始 URI
}
}
Glide.with(this)
.load(uri)
.circleCrop()
.error(R.drawable.ic_account_circle_24)
.placeholder(R.drawable.ic_account_circle_24)
.into(binding.avatarButton);
return;
}
int avatarRes = getSharedPreferences("profile_prefs", MODE_PRIVATE)
.getInt("profile_avatar_res", 0);
if (avatarRes != 0) {
Glide.with(this)
.load(avatarRes)
.circleCrop()
.error(R.drawable.ic_account_circle_24)
.into(binding.avatarButton);
} else {
binding.avatarButton.setImageResource(R.drawable.ic_account_circle_24);
}
} catch (Exception ignored) {
if (binding != null) {
binding.avatarButton.setImageResource(R.drawable.ic_account_circle_24);
}
}
}
@Override
protected void onResume() {
super.onResume();
if (binding != null) {
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
bottomNavigation.setSelectedItemId(R.id.nav_home);
loadAvatarFromPrefs();
// 更新未读消息徽章
UnreadMessageManager.updateBadge(bottomNavigation);
}
// 确保通话信令 WebSocket 保持连接(用于接收来电通知)
LiveStreamingApplication app = (LiveStreamingApplication) getApplication();
app.connectCallSignalingIfLoggedIn();
// 检查主播状态并显示/隐藏开播按钮
checkAndUpdateStreamerButton();
}
/**
* 检查主播状态并更新开播按钮的显示
*/
private void checkAndUpdateStreamerButton() {
// 如果用户未登录,隐藏开播按钮
if (!AuthHelper.isLoggedIn(this)) {
if (binding.fabAddLive != null) {
binding.fabAddLive.setVisibility(View.GONE);
}
return;
}
// 检查主播资格
ApiClient.getService(getApplicationContext()).checkStreamerStatus()
.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) {
// 接口调用失败,隐藏按钮
if (binding.fabAddLive != null) {
binding.fabAddLive.setVisibility(View.GONE);
}
return;
}
ApiResponse<Map<String, Object>> body = response.body();
if (body.getCode() != 200 || body.getData() == null) {
// 接口返回错误,隐藏按钮
if (binding.fabAddLive != null) {
binding.fabAddLive.setVisibility(View.GONE);
}
return;
}
Map<String, Object> data = body.getData();
Boolean isStreamer = data.get("isStreamer") != null && (Boolean) data.get("isStreamer");
Boolean isBanned = data.get("isBanned") != null && (Boolean) data.get("isBanned");
// 只有认证主播且未被封禁才显示开播按钮
if (isStreamer && !isBanned && binding.fabAddLive != null) {
binding.fabAddLive.setVisibility(View.VISIBLE);
} else if (binding.fabAddLive != null) {
binding.fabAddLive.setVisibility(View.GONE);
}
}
@Override
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
// 网络错误,隐藏按钮
if (binding.fabAddLive != null) {
binding.fabAddLive.setVisibility(View.GONE);
}
}
});
}
/**
* 从后端获取未读消息总数
*/
private void fetchUnreadMessageCount() {
// 检查登录状态
if (!AuthHelper.isLoggedIn(this)) {
return;
}
// 从会话列表接口获取未读消息总数
ApiClient.getService(getApplicationContext()).getConversations()
.enqueue(new Callback<ApiResponse<List<ConversationResponse>>>() {
@Override
public void onResponse(Call<ApiResponse<List<ConversationResponse>>> call,
Response<ApiResponse<List<ConversationResponse>>> response) {
ApiResponse<List<ConversationResponse>> body = response.body();
if (response.isSuccessful() && body != null && body.isOk() && body.getData() != null) {
int totalUnread = 0;
for (ConversationResponse conv : body.getData()) {
if (conv != null && conv.getUnreadCount() != null) {
totalUnread += conv.getUnreadCount();
}
}
UnreadMessageManager.setUnreadCount(MainActivity.this, totalUnread);
// 更新底部导航栏徽章
if (binding != null && binding.bottomNavInclude != null) {
UnreadMessageManager.updateBadge(binding.bottomNavInclude.bottomNavigation);
}
}
}
@Override
public void onFailure(Call<ApiResponse<List<ConversationResponse>>> call, Throwable t) {
// 网络错误,忽略
}
});
}
@Override
protected void onPause() {
super.onPause();
// 暂停时停止语音识别
stopVoiceRecognition();
}
@Override
protected void onStart() {
super.onStart();
// 注释掉网络轮询,使用静态数据
// startPolling();
}
@Override
protected void onStop() {
super.onStop();
stopPolling();
}
@Override
public void onBackPressed() {
DrawerLayout drawerLayout = binding != null ? binding.drawerLayout : null;
if (drawerLayout != null && drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
return;
}
super.onBackPressed();
}
private void startPolling() {
stopPolling();
pollRunnable = () -> {
// 检查Activity是否还在前台
if (!isFinishing() && !isDestroyed()) {
fetchRooms();
// 增加轮询间隔,减少网络请求频率
handler.postDelayed(pollRunnable, 15000);
}
};
// 延迟首次请求,让界面先显示
handler.postDelayed(pollRunnable, 2000);
}
private void stopPolling() {
if (pollRunnable != null) {
handler.removeCallbacks(pollRunnable);
pollRunnable = null;
}
}
private void showCreateRoomDialog() {
// 先检查登录状态
if (!AuthHelper.requireLogin(this, "创建直播间需要登录")) {
return;
}
// 检查主播资格
checkStreamerStatusAndShowDialog();
}
private void checkStreamerStatusAndShowDialog() {
// 显示加载提示
Toast.makeText(this, "正在检查主播资格...", Toast.LENGTH_SHORT).show();
ApiClient.getService(getApplicationContext()).checkStreamerStatus()
.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) {
// 接口调用失败,可能是旧版本后端,允许继续创建
showCreateRoomDialogInternal();
return;
}
ApiResponse<Map<String, Object>> body = response.body();
if (body.getCode() != 200 || body.getData() == null) {
// 接口返回错误,可能是旧版本后端,允许继续创建
showCreateRoomDialogInternal();
return;
}
Map<String, Object> data = body.getData();
Boolean isStreamer = data.get("isStreamer") != null && (Boolean) data.get("isStreamer");
Boolean isBanned = data.get("isBanned") != null && (Boolean) data.get("isBanned");
Boolean hasApplication = data.get("hasApplication") != null && (Boolean) data.get("hasApplication");
Object appStatusObj = data.get("applicationStatus");
Integer applicationStatus = appStatusObj != null ? ((Number) appStatusObj).intValue() : null;
if (isBanned) {
// 被封禁
String banReason = (String) data.get("banReason");
new AlertDialog.Builder(MainActivity.this)
.setTitle("无法开播")
.setMessage("您的主播资格已被封禁" + (banReason != null ? "" + banReason : ""))
.setPositiveButton("确定", null)
.show();
return;
}
if (!isStreamer) {
// 不是主播
if (hasApplication && applicationStatus != null && applicationStatus == 0) {
// 有待审核的申请
new AlertDialog.Builder(MainActivity.this)
.setTitle("申请审核中")
.setMessage("您的主播认证申请正在审核中,请耐心等待")
.setPositiveButton("确定", null)
.show();
} else {
// 没有申请或申请被拒绝,提示申请认证
new AlertDialog.Builder(MainActivity.this)
.setTitle("需要主播认证")
.setMessage("只有认证主播才能开播,是否现在申请主播认证?")
.setPositiveButton("去申请", (d, w) -> {
// 跳转到主播认证申请页面
StreamerApplyActivity.start(MainActivity.this);
})
.setNegativeButton("取消", null)
.show();
}
return;
}
// 是认证主播,显示创建直播间对话框
showCreateRoomDialogInternal();
}
@Override
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
// 网络错误,可能是旧版本后端,允许继续创建
Log.w(TAG, "检查主播资格失败,允许继续创建", t);
showCreateRoomDialogInternal();
}
});
}
private void showCreateRoomDialogInternal() {
// 显示选择对话框:手机开播 或 OBS推流
new AlertDialog.Builder(this)
.setTitle("选择开播方式")
.setItems(new String[]{"📱 手机开播", "💻 OBS推流"}, (dialog, which) -> {
if (which == 0) {
// 手机开播 - 跳转到 BroadcastActivity
Intent intent = new Intent(MainActivity.this, BroadcastActivity.class);
startActivity(intent);
} else {
// OBS推流 - 显示创建直播间对话框
showOBSCreateRoomDialog();
}
})
.setNegativeButton("取消", null)
.show();
}
private void showOBSCreateRoomDialog() {
View dialogView = getLayoutInflater().inflate(R.layout.dialog_create_room, null);
DialogCreateRoomBinding dialogBinding = DialogCreateRoomBinding.bind(dialogView);
// 使用正确的资源ID获取typeSpinner
int typeSpinnerId = getResources().getIdentifier("typeSpinner", "id", getPackageName());
MaterialAutoCompleteTextView typeSpinner = dialogView.findViewById(typeSpinnerId);
// 从后端加载直播类型分类
loadLiveTypesForDialog(typeSpinner);
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("创建直播间")
.setView(dialogView)
.setNegativeButton("取消", null)
.setPositiveButton("创建", null)
.create();
dialog.setOnShowListener(d -> {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
String title = dialogBinding.titleEdit.getText() != null ? dialogBinding.titleEdit.getText().toString().trim() : "";
String typeDisplay = (typeSpinner != null && typeSpinner.getText() != null) ? typeSpinner.getText().toString().trim() : "";
if (TextUtils.isEmpty(title)) {
dialogBinding.titleLayout.setError("标题不能为空");
return;
} else {
dialogBinding.titleLayout.setError(null);
}
if (TextUtils.isEmpty(typeDisplay)) {
int typeLayoutId = getResources().getIdentifier("typeLayout", "id", getPackageName());
TextInputLayout typeLayout = dialogView.findViewById(typeLayoutId);
if (typeLayout != null) {
typeLayout.setError("请先选择直播类型");
}
return;
} else {
int typeLayoutId = getResources().getIdentifier("typeLayout", "id", getPackageName());
TextInputLayout typeLayout = dialogView.findViewById(typeLayoutId);
if (typeLayout != null) {
typeLayout.setError(null);
}
}
// 从后端返回的类型列表中查找对应的类型编码
String typeCode = "game"; // 默认值:游戏
Object tag = typeSpinner.getTag();
Log.d(TAG, "创建直播间 - 用户选择的类型显示名称: " + typeDisplay);
Log.d(TAG, "创建直播间 - typeSpinner.getTag() 类型: " + (tag != null ? tag.getClass().getName() : "null"));
if (tag instanceof List) {
@SuppressWarnings("unchecked")
List<com.example.livestreaming.net.LiveTypeResponse> types =
(List<com.example.livestreaming.net.LiveTypeResponse>) tag;
Log.d(TAG, "创建直播间 - 从Tag中获取到 " + types.size() + " 个类型");
boolean found = false;
for (com.example.livestreaming.net.LiveTypeResponse type : types) {
Log.d(TAG, "创建直播间 - 比对类型: " + type.getName() + " vs " + typeDisplay);
if (type.getName().equals(typeDisplay)) {
typeCode = type.getCode();
found = true;
Log.d(TAG, "创建直播间 - 匹配成功!使用类型编码: " + typeCode);
break;
}
}
if (!found) {
Log.w(TAG, "创建直播间 - 未找到匹配的类型,使用默认值: " + typeCode);
}
} else {
Log.w(TAG, "创建直播间 - Tag不是List类型使用默认映射");
// 如果没有从后端获取到类型列表,使用默认映射
switch (typeDisplay) {
case "游戏":
typeCode = "game";
break;
case "才艺":
typeCode = "talent";
break;
case "户外":
typeCode = "outdoor";
break;
case "音乐":
typeCode = "music";
break;
case "美食":
typeCode = "food";
break;
case "聊天":
typeCode = "chat";
break;
default:
Log.w(TAG, "创建直播间 - 未知的类型名称: " + typeDisplay + ",使用默认值: game");
typeCode = "game";
break;
}
Log.d(TAG, "创建直播间 - 默认映射结果: " + typeDisplay + " -> " + typeCode);
}
// 获取用户昵称
String streamerName = getSharedPreferences("profile_prefs", MODE_PRIVATE)
.getString("profile_name", "未知用户");
Log.d(TAG, "创建直播间 - 最终参数: title=" + title + ", streamerName=" + streamerName + ", typeCode=" + typeCode);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
// 调用后端接口创建直播间,使用从后端获取的类型编码
ApiClient.getService(getApplicationContext()).createRoom(new CreateRoomRequest(title, streamerName, typeCode))
.enqueue(new Callback<ApiResponse<Room>>() {
@Override
public void onResponse(Call<ApiResponse<Room>> call, Response<ApiResponse<Room>> response) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
ApiResponse<Room> body = response.body();
Room room = body != null ? body.getData() : null;
if (!response.isSuccessful() || body == null || !body.isOk() || room == null) {
String msg;
if (!response.isSuccessful()) {
String err = null;
try {
okhttp3.ResponseBody eb = response.errorBody();
err = eb != null ? eb.string() : null;
} catch (Exception ignored) {
}
if (!TextUtils.isEmpty(err)) {
msg = "HTTP " + response.code() + "" + err;
} else {
msg = "HTTP " + response.code() + ":创建失败";
}
} else if (body == null) {
msg = "服务返回空数据:创建失败";
} else if (!body.isOk()) {
String m = body.getMessage();
if (!TextUtils.isEmpty(m)) {
msg = m;
} else {
msg = "接口返回异常(code=" + body.getCode() + ")";
}
} else {
msg = "创建失败:返回无房间数据";
}
Toast.makeText(MainActivity.this, "创建失败: " + msg, Toast.LENGTH_SHORT).show();
return;
}
dialog.dismiss();
fetchRooms();
// 显示优化的推流信息弹窗
showOptimizedStreamInfo(room);
// 可选:跳转到房间详情
// Intent intent = new Intent(MainActivity.this, RoomDetailActivity.class);
// intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
// startActivity(intent);
}
@Override
public void onFailure(Call<ApiResponse<Room>> call, Throwable t) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
String errorMsg = "网络错误";
if (t != null) {
if (t.getMessage() != null && t.getMessage().contains("Unable to resolve host")) {
errorMsg = "无法连接服务器,请检查网络";
} else if (t.getMessage() != null && t.getMessage().contains("timeout")) {
errorMsg = "连接超时,请检查服务器是否运行";
} else if (t.getMessage() != null && t.getMessage().contains("Connection refused")) {
errorMsg = "连接被拒绝,请确保后端服务已启动";
} else {
errorMsg = "网络错误:" + t.getMessage();
}
}
Toast.makeText(MainActivity.this, errorMsg, Toast.LENGTH_LONG).show();
}
});
});
});
dialog.show();
}
private void showOptimizedStreamInfo(Room room) {
String streamKey = room != null ? room.getStreamKey() : null;
String rtmp = room != null && room.getStreamUrls() != null ? room.getStreamUrls().getRtmp() : null;
// 直接使用服务器返回的RTMP地址
String rtmpForObs = rtmp;
// 创建自定义弹窗布局
View dialogView = getLayoutInflater().inflate(R.layout.dialog_stream_info, null);
// 找到视图组件
TextView addressText = dialogView.findViewById(R.id.addressText);
TextView keyText = dialogView.findViewById(R.id.keyText);
// 设置文本内容
String displayAddress = !TextUtils.isEmpty(rtmpForObs) ? rtmpForObs : rtmp;
addressText.setText(displayAddress != null ? displayAddress : "");
keyText.setText(streamKey != null ? streamKey : "");
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("🎉 直播间创建成功")
.setView(dialogView)
.setPositiveButton("开始直播", (d, w) -> {
d.dismiss();
// 跳转到直播播放页面
Intent intent = new Intent(MainActivity.this, RoomDetailActivity.class);
intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
startActivity(intent);
})
.setNegativeButton("稍后开始", (d, w) -> d.dismiss())
.create();
dialog.show();
// 设置复制按钮点击事件
dialogView.findViewById(R.id.copyAddressBtn).setOnClickListener(v -> {
copyToClipboard("推流地址", displayAddress);
Toast.makeText(this, "推流地址已复制", Toast.LENGTH_SHORT).show();
});
dialogView.findViewById(R.id.copyKeyBtn).setOnClickListener(v -> {
copyToClipboard("推流密钥", streamKey);
Toast.makeText(this, "推流密钥已复制", Toast.LENGTH_SHORT).show();
});
}
private void copyToClipboard(String label, String text) {
if (TextUtils.isEmpty(text)) {
Toast.makeText(this, "内容为空", Toast.LENGTH_SHORT).show();
return;
}
ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
if (cm == null) return;
cm.setPrimaryClip(ClipData.newPlainText(label, text));
Toast.makeText(this, "已复制", Toast.LENGTH_SHORT).show();
}
private void fetchRooms() {
Log.d(TAG, "fetchRooms() 开始");
// 避免重复请求
if (isFetching) {
Log.d(TAG, "fetchRooms() 已在请求中,跳过");
return;
}
isFetching = true;
lastFetchMs = System.currentTimeMillis();
// 隐藏空状态和错误状态
hideEmptyState();
hideErrorState();
// 只在没有数据时显示骨架屏
if (adapter.getItemCount() == 0) {
LoadingStateManager.showSkeleton(binding.roomsRecyclerView, 6);
binding.loading.setVisibility(View.GONE);
} else {
binding.loading.setVisibility(View.GONE);
}
String baseUrl = ApiClient.getCurrentBaseUrl(getApplicationContext());
Log.d(TAG, "fetchRooms() 请求 API: " + baseUrl + "api/front/live/public/rooms");
ApiClient.getService(getApplicationContext()).getRooms().enqueue(new Callback<ApiResponse<List<Room>>>() {
@Override
public void onResponse(Call<ApiResponse<List<Room>>> call, Response<ApiResponse<List<Room>>> response) {
Log.d(TAG, "fetchRooms() onResponse: code=" + response.code());
// 先恢复真实适配器
binding.roomsRecyclerView.setAdapter(adapter);
binding.loading.setVisibility(View.GONE);
LoadingStateManager.stopRefreshing(binding.swipeRefresh);
isFetching = false;
ApiResponse<List<Room>> body = response.body();
Log.d(TAG, "fetchRooms() body=" + (body != null ? "not null, isOk=" + body.isOk() : "null"));
List<Room> rooms = response.isSuccessful() && body != null && body.isOk() && body.getData() != null
? body.getData()
: Collections.emptyList();
Log.d(TAG, "fetchRooms() rooms count: " + (rooms != null ? rooms.size() : 0));
if (rooms == null || rooms.isEmpty()) {
showNoRoomsState();
} else {
hideEmptyState();
}
allRooms.clear();
if (rooms != null) allRooms.addAll(rooms);
adapter.submitList(new ArrayList<>(allRooms));
}
@Override
public void onFailure(Call<ApiResponse<List<Room>>> call, Throwable t) {
Log.e(TAG, "fetchRooms() onFailure: " + t.getMessage(), t);
// 先恢复真实适配器
binding.roomsRecyclerView.setAdapter(adapter);
binding.loading.setVisibility(View.GONE);
LoadingStateManager.stopRefreshing(binding.swipeRefresh);
isFetching = false;
// 显示网络错误
ErrorHandler.handleApiError(binding.getRoot(), t, () -> fetchRooms());
showNetworkErrorState();
allRooms.clear();
adapter.submitList(new ArrayList<>());
}
});
}
/**
* 显示无直播间空状态
*/
private void showNoRoomsState() {
// 发现页面使用专用的空状态容器,避免重复显示
if ("发现".equals(currentTopTab)) {
updateDiscoverEmptyState();
return;
}
if (binding.emptyStateView != null) {
binding.emptyStateView.setNoRoomsState();
binding.emptyStateView.setOnActionClickListener(v -> fetchRooms());
binding.emptyStateView.setVisibility(View.VISIBLE);
}
}
/**
* 显示网络错误状态
*/
private void showNetworkErrorState() {
if (binding.emptyStateView != null) {
binding.emptyStateView.setNetworkErrorState();
binding.emptyStateView.setOnActionClickListener(v -> fetchRooms());
binding.emptyStateView.setVisibility(View.VISIBLE);
}
}
/**
* 隐藏空状态视图
*/
private void hideEmptyState() {
if (binding.emptyStateView != null) {
binding.emptyStateView.setVisibility(View.GONE);
}
}
/**
* 隐藏错误状态实际上和hideEmptyState一样
*/
private void hideErrorState() {
hideEmptyState();
}
/**
* 应用分类筛选(带动画效果)
* 使用异步筛选提升性能,并添加平滑的过渡动画
* 使用filterRequestId防止旧的筛选结果覆盖新的结果
*/
private void applyCategoryFilterWithAnimation(String category) {
String c = category != null ? category : "推荐";
// 增加请求ID确保只有最新的筛选结果被应用
final int requestId = ++filterRequestId;
// 显示加载状态(如果数据量较大)
if (allRooms.size() > 50) {
binding.loading.setVisibility(View.VISIBLE);
}
// 使用筛选管理器异步筛选
if (filterManager != null) {
filterManager.filterRoomsAsync(allRooms, c, filteredRooms -> {
// 检查这个结果是否仍然是最新的请求
if (requestId != filterRequestId) {
// 这是一个旧的请求结果,忽略它
return;
}
// 隐藏加载状态
binding.loading.setVisibility(View.GONE);
// 添加淡入动画
binding.roomsRecyclerView.animate()
.alpha(0.7f)
.setDuration(100)
.withEndAction(() -> {
// 再次检查请求ID防止在动画期间又有新的筛选请求
if (requestId != filterRequestId) {
return;
}
// 更新列表数据ListAdapter会自动处理DiffUtil动画
adapter.submitList(filteredRooms, () -> {
// 最后一次检查请求ID
if (requestId != filterRequestId) {
return;
}
// 数据更新完成后,恢复透明度并添加淡入效果
binding.roomsRecyclerView.animate()
.alpha(1.0f)
.setDuration(200)
.start();
});
// 更新空状态
updateEmptyStateForList(filteredRooms);
})
.start();
});
} else {
// 降级到同步筛选(如果筛选管理器未初始化)
applyCategoryFilterSync(c);
}
}
/**
* 同步筛选(降级方案)
*/
private void applyCategoryFilterSync(String category) {
String c = category != null ? category : "推荐";
if ("推荐".equals(c) || "全部".equals(c)) {
// 添加淡入动画,保持与其他筛选场景的一致性
binding.roomsRecyclerView.animate()
.alpha(0.7f)
.setDuration(100)
.withEndAction(() -> {
adapter.submitList(new ArrayList<>(allRooms), () -> {
binding.roomsRecyclerView.animate()
.alpha(1.0f)
.setDuration(200)
.start();
});
updateEmptyStateForList(allRooms);
})
.start();
return;
}
List<Room> filtered = new ArrayList<>();
for (Room r : allRooms) {
if (r == null) continue;
// 优先使用 categoryName 筛选,如果没有则使用 type
String roomCategory = r.getCategoryName();
if (roomCategory == null || roomCategory.isEmpty()) {
roomCategory = r.getType();
}
if (c.equals(roomCategory)) {
filtered.add(r);
}
}
// 添加淡入动画
binding.roomsRecyclerView.animate()
.alpha(0.7f)
.setDuration(100)
.withEndAction(() -> {
adapter.submitList(filtered, () -> {
binding.roomsRecyclerView.animate()
.alpha(1.0f)
.setDuration(200)
.start();
});
updateEmptyStateForList(filtered);
})
.start();
}
/**
* 应用搜索筛选
* 根据搜索文本筛选房间列表(仅按房间标题和主播名称,不按分类筛选)
*/
private void applySearchFilter(String searchQuery) {
String query = searchQuery != null ? searchQuery.trim().toLowerCase() : "";
// 如果搜索文本为空,恢复分类筛选
if (query.isEmpty()) {
applyCategoryFilterWithAnimation(currentCategory);
return;
}
// 根据当前顶部标签选择要筛选的数据源
List<Room> sourceRooms;
if ("关注".equals(currentTopTab)) {
sourceRooms = followRooms;
} else if ("附近".equals(currentTopTab)) {
// 附近页面不显示房间,不需要搜索筛选
return;
} else {
// 发现页面
sourceRooms = discoverRooms.isEmpty() ? allRooms : discoverRooms;
}
// 只根据搜索文本筛选(按房间标题和主播名称),不考虑分类
List<Room> searchFiltered = new ArrayList<>();
for (Room r : sourceRooms) {
if (r == null) continue;
String title = r.getTitle() != null ? r.getTitle().toLowerCase() : "";
String streamer = r.getStreamerName() != null ? r.getStreamerName().toLowerCase() : "";
if (title.contains(query) || streamer.contains(query)) {
searchFiltered.add(r);
}
}
// 更新列表
adapter.submitList(searchFiltered);
updateEmptyStateForList(searchFiltered);
}
/**
* 恢复分类标签的选中状态
*/
private void restoreCategoryTabSelection() {
if (binding == null || binding.categoryTabs == null) return;
// 延迟执行确保TabLayout已完全初始化
binding.categoryTabs.post(() -> {
for (int i = 0; i < binding.categoryTabs.getTabCount(); i++) {
TabLayout.Tab tab = binding.categoryTabs.getTabAt(i);
if (tab != null) {
CharSequence tabText = tab.getText();
if (tabText != null && currentCategory.equals(tabText.toString())) {
tab.select();
break;
}
}
}
});
}
/**
* 根据列表数据更新空状态显示
*/
private void updateEmptyStateForList(List<Room> rooms) {
// 更新发现页面的空状态
View discoverEmptyContainer = findViewById(R.id.discoverEmptyContainer);
if (rooms == null || rooms.isEmpty()) {
// 发现页面只使用 discoverEmptyContainer避免重复显示
if ("发现".equals(currentTopTab)) {
// 隐藏通用空状态
hideEmptyState();
// 显示发现页面专用空状态
if (discoverEmptyContainer != null) {
discoverEmptyContainer.setVisibility(View.VISIBLE);
}
} else {
// 其他页面使用通用空状态
if (binding.emptyStateView != null) {
binding.emptyStateView.setIcon(R.drawable.ic_search_24);
binding.emptyStateView.setTitle("该分类下暂无直播间");
binding.emptyStateView.setMessage("换个分类看看吧");
binding.emptyStateView.hideActionButton();
binding.emptyStateView.setVisibility(View.VISIBLE);
}
}
if (binding.roomsRecyclerView != null) {
binding.roomsRecyclerView.setVisibility(View.GONE);
}
} else {
hideEmptyState();
// 隐藏发现页面的空状态
if (discoverEmptyContainer != null) {
discoverEmptyContainer.setVisibility(View.GONE);
}
if (binding.roomsRecyclerView != null) {
binding.roomsRecyclerView.setVisibility(View.VISIBLE);
}
}
}
/**
* 从后端加载分类数据
*/
private void loadCategoriesFromBackend() {
Log.d(TAG, "loadCategoriesFromBackend() 开始加载直播间分类");
// 调用后端接口获取直播间分类列表
ApiClient.getService(getApplicationContext()).getLiveRoomCategories()
.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) {
Log.d(TAG, "loadCategoriesFromBackend() onResponse: code=" + response.code());
ApiResponse<List<com.example.livestreaming.net.CategoryResponse>> body = response.body();
List<com.example.livestreaming.net.CategoryResponse> categories =
response.isSuccessful() && body != null && body.isOk() && body.getData() != null
? body.getData()
: null;
if (categories != null && !categories.isEmpty()) {
Log.d(TAG, "loadCategoriesFromBackend() 成功获取 " + categories.size() + " 个分类");
// 更新分类标签
updateCategoryTabs(categories);
} else {
Log.w(TAG, "loadCategoriesFromBackend() 未获取到分类数据,使用默认分类");
// 使用默认分类
useDefaultCategories();
}
}
@Override
public void onFailure(Call<ApiResponse<List<com.example.livestreaming.net.CategoryResponse>>> call, Throwable t) {
Log.e(TAG, "loadCategoriesFromBackend() onFailure: " + t.getMessage(), t);
// 网络错误,使用默认分类
useDefaultCategories();
}
});
}
/**
* 更新分类标签
*/
private void updateCategoryTabs(List<com.example.livestreaming.net.CategoryResponse> categories) {
if (binding == null || binding.categoryTabs == null) return;
// 缓存后端分类数据,供频道管理对话框使用
allBackendCategories.clear();
allBackendCategories.addAll(categories);
runOnUiThread(() -> {
// 清空现有标签
binding.categoryTabs.removeAllTabs();
// 添加"推荐"作为第一个标签
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText("推荐"));
// 直接使用后端返回的分类
for (com.example.livestreaming.net.CategoryResponse cat : categories) {
if (cat != null && cat.getName() != null) {
binding.categoryTabs.addTab(binding.categoryTabs.newTab().setText(cat.getName()));
}
}
// 恢复上次选中的分类
String lastCategory = CategoryFilterManager.getLastCategory(this);
restoreCategoryTabSelection();
Log.d(TAG, "updateCategoryTabs() 分类标签更新完成,共 " + binding.categoryTabs.getTabCount() + " 个标签(直接使用后端分类)");
});
}
/**
* 使用默认分类(降级方案)
*/
private void useDefaultCategories() {
if (binding == null || binding.categoryTabs == null) return;
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();
Log.d(TAG, "useDefaultCategories() 使用默认分类,共 " + binding.categoryTabs.getTabCount() + " 个标签");
});
}
/**
* 为创建直播间对话框加载直播类型
* 从后端数据库获取直播类型列表(游戏、才艺、户外、音乐、美食、聊天等)
*/
private void loadLiveTypesForDialog(MaterialAutoCompleteTextView typeSpinner) {
if (typeSpinner == null) {
Log.w(TAG, "loadLiveTypesForDialog() typeSpinner is null");
return;
}
Log.d(TAG, "loadLiveTypesForDialog() 开始从后端加载直播类型");
// 调用后端接口获取直播类型列表
ApiClient.getService(getApplicationContext()).getLiveTypes()
.enqueue(new Callback<ApiResponse<List<com.example.livestreaming.net.LiveTypeResponse>>>() {
@Override
public void onResponse(Call<ApiResponse<List<com.example.livestreaming.net.LiveTypeResponse>>> call,
Response<ApiResponse<List<com.example.livestreaming.net.LiveTypeResponse>>> response) {
Log.d(TAG, "loadLiveTypesForDialog() onResponse: code=" + response.code());
ApiResponse<List<com.example.livestreaming.net.LiveTypeResponse>> body = response.body();
List<com.example.livestreaming.net.LiveTypeResponse> types =
response.isSuccessful() && body != null && body.isOk() && body.getData() != null
? body.getData()
: null;
runOnUiThread(() -> {
if (types != null && !types.isEmpty()) {
Log.d(TAG, "loadLiveTypesForDialog() 成功获取 " + types.size() + " 个直播类型");
// 打印所有类型信息,便于调试
for (com.example.livestreaming.net.LiveTypeResponse type : types) {
Log.d(TAG, " 类型: " + type.getName() + " (code=" + type.getCode() + ", sort=" + type.getSort() + ")");
}
// 提取类型名称
String[] typeNames = new String[types.size()];
for (int i = 0; i < types.size(); i++) {
typeNames[i] = types.get(i).getName();
}
// 使用 simple_list_item_1 而不是 simple_dropdown_item_1line
ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this,
android.R.layout.simple_list_item_1, typeNames);
typeSpinner.setAdapter(adapter);
// 设置默认选中第一项,使用 false 参数避免触发过滤
if (typeNames.length > 0) {
typeSpinner.setText(typeNames[0], false);
Log.d(TAG, "loadLiveTypesForDialog() 默认选中: " + typeNames[0]);
}
// 保存类型列表供后续使用
typeSpinner.setTag(types);
Log.d(TAG, "loadLiveTypesForDialog() 类型列表已保存到Tag中");
} else {
Log.w(TAG, "loadLiveTypesForDialog() 未获取到类型数据,使用默认类型");
useDefaultLiveTypes(typeSpinner);
}
});
}
@Override
public void onFailure(Call<ApiResponse<List<com.example.livestreaming.net.LiveTypeResponse>>> call, Throwable t) {
Log.e(TAG, "loadLiveTypesForDialog() onFailure: " + t.getMessage(), t);
// 网络错误,使用默认类型
runOnUiThread(() -> {
useDefaultLiveTypes(typeSpinner);
});
}
});
}
/**
* 使用默认的直播类型(当后端接口失败时的备用方案)
*/
private void useDefaultLiveTypes(MaterialAutoCompleteTextView typeSpinner) {
String[] defaultTypes = {"游戏", "才艺", "户外", "音乐", "美食", "聊天"};
ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this,
android.R.layout.simple_list_item_1, defaultTypes);
typeSpinner.setAdapter(adapter);
// 设置默认选中第一项
if (defaultTypes.length > 0) {
typeSpinner.setText(defaultTypes[0], false);
}
Log.d(TAG, "useDefaultLiveTypes() 使用默认类型,共 " + defaultTypes.length + "");
}
private void loadCoverAssetsAsync() {
// 在后台线程加载资源文件避免阻塞UI
new Thread(() -> {
AssetManager am = getAssets();
List<String> files = new ArrayList<>();
try {
String[] list = am.list("img");
if (list != null) {
for (String f : list) {
if (f == null) continue;
String lower = f.toLowerCase();
if (lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".webp") || lower.endsWith(".gif")) {
files.add(f);
}
}
}
} catch (IOException ignored) {
}
// 回到主线程更新UI
runOnUiThread(() -> {
if (adapter != null) {
adapter.setCoverAssetFiles(files);
}
});
}).start();
}
/**
* 初始化顶部标签页数据
* 顶部标签页(关注/发现/附近)为固定配置,不需要从后端动态获取
*/
private void initializeTopTabData() {
// 初始化关注页面数据
followRooms.clear();
// 初始化发现页面数据 - 从后端获取真实直播间
fetchDiscoverRooms();
// 初始化附近页面数据
nearbyUsers.clear();
}
/**
* 从后端获取发现页面的直播间列表
*/
private void fetchDiscoverRooms() {
Log.d(TAG, "fetchDiscoverRooms() 开始API: " + ApiClient.getCurrentBaseUrl(getApplicationContext()));
// 不显示骨架屏,直接用空列表初始化
binding.roomsRecyclerView.setAdapter(adapter);
adapter.submitList(new ArrayList<>());
ApiClient.getService(getApplicationContext()).getRooms().enqueue(new Callback<ApiResponse<List<Room>>>() {
@Override
public void onResponse(Call<ApiResponse<List<Room>>> call, Response<ApiResponse<List<Room>>> response) {
Log.d(TAG, "fetchDiscoverRooms() onResponse: code=" + response.code());
// 停止刷新动画
LoadingStateManager.stopRefreshing(binding.swipeRefresh);
// 恢复真实适配器
binding.roomsRecyclerView.setAdapter(adapter);
ApiResponse<List<Room>> body = response.body();
List<Room> rooms = response.isSuccessful() && body != null && body.isOk() && body.getData() != null
? body.getData()
: Collections.emptyList();
discoverRooms.clear();
allRooms.clear();
Log.d(TAG, "fetchDiscoverRooms() 获取到 " + (rooms != null ? rooms.size() : 0) + " 个直播间");
if (rooms != null && !rooms.isEmpty()) {
discoverRooms.addAll(rooms);
allRooms.addAll(rooms);
hideEmptyState();
// 隐藏加载视图
if (binding.loading != null) binding.loading.setVisibility(View.GONE);
} else {
// 没有直播间时立即显示空状态
Log.d(TAG, "fetchDiscoverRooms() 无直播间,显示空状态");
showNoRoomsState();
// 隐藏加载视图
if (binding.loading != null) binding.loading.setVisibility(View.GONE);
}
// 提交空列表或数据列表
adapter.submitList(new ArrayList<>(allRooms));
}
@Override
public void onFailure(Call<ApiResponse<List<Room>>> call, Throwable t) {
Log.e(TAG, "fetchDiscoverRooms() onFailure: " + t.getMessage(), t);
// 停止刷新动画
LoadingStateManager.stopRefreshing(binding.swipeRefresh);
// 无论结果如何,先恢复真实适配器
binding.roomsRecyclerView.setAdapter(adapter);
// 网络错误,显示错误状态
discoverRooms.clear();
if ("发现".equals(currentTopTab)) {
allRooms.clear();
adapter.submitList(new ArrayList<>());
showNetworkErrorState();
}
ErrorHandler.handleApiError(binding.getRoot(), t, () -> fetchDiscoverRooms());
}
});
}
/**
* 切换顶部标签页内容
*/
private void switchTopTabContent(String tabName) {
if (tabName == null) tabName = "发现";
if ("关注".equals(tabName)) {
showFollowTab();
} else if ("发现".equals(tabName)) {
showDiscoverTab();
} else if ("附近".equals(tabName)) {
showNearbyTab();
} else {
// 默认显示发现页面
showDiscoverTab();
}
}
/**
* 显示关注页面 - 显示已关注主播的直播间
*/
private void showFollowTab() {
// 隐藏分类标签容器(关注页面不需要分类筛选)
View categoryTabsContainer = findViewById(R.id.categoryTabsContainer);
if (categoryTabsContainer != null) {
categoryTabsContainer.setVisibility(View.GONE);
}
// 隐藏发布按钮
if (binding.btnPublish != null) {
binding.btnPublish.setVisibility(View.GONE);
}
// 隐藏其他Tab内容
View discoverTab = findViewById(R.id.discoverTabContent);
View nearbyTab = findViewById(R.id.nearbyTabContent);
View followTab = findViewById(R.id.followTabContent);
if (discoverTab != null) {
discoverTab.setVisibility(View.GONE);
}
if (nearbyTab != null) {
nearbyTab.setVisibility(View.GONE);
}
if (followTab != null) {
followTab.setVisibility(View.GONE);
}
// 显示房间列表容器(使用发现页面的容器)
if (binding.swipeRefresh != null) {
binding.swipeRefresh.setVisibility(View.VISIBLE);
}
hideEmptyState();
// 恢复房间列表的布局管理器和适配器
if (binding.roomsRecyclerView != null) {
binding.roomsRecyclerView.setLayoutManager(roomsLayoutManager);
binding.roomsRecyclerView.setAdapter(adapter);
binding.roomsRecyclerView.setVisibility(View.VISIBLE);
}
// 设置下拉刷新
if (binding.swipeRefresh != null) {
binding.swipeRefresh.setOnRefreshListener(() -> {
fetchFollowedStreamersRooms();
});
}
// 加载已关注主播的直播间
fetchFollowedStreamersRooms();
}
/**
* 获取已关注主播的直播间列表
*/
private void fetchFollowedStreamersRooms() {
Log.d(TAG, "fetchFollowedStreamersRooms() 开始");
// 检查登录状态
if (!AuthHelper.isLoggedIn(this)) {
// 未登录,显示空状态
followRooms.clear();
allRooms.clear();
adapter.submitList(new ArrayList<>());
LoadingStateManager.stopRefreshing(binding.swipeRefresh);
if (binding.emptyStateView != null) {
binding.emptyStateView.setCustomState(
"请先登录",
"登录后可以查看已关注主播的直播间",
"去登录",
v -> {
LoginActivity.start(this);
}
);
binding.emptyStateView.setVisibility(View.VISIBLE);
}
return;
}
// 显示加载状态
if (adapter.getItemCount() == 0) {
LoadingStateManager.showSkeleton(binding.roomsRecyclerView, 6);
}
// 获取所有直播间
ApiClient.getService(getApplicationContext()).getRooms()
.enqueue(new Callback<ApiResponse<List<Room>>>() {
@Override
public void onResponse(Call<ApiResponse<List<Room>>> call,
Response<ApiResponse<List<Room>>> response) {
Log.d(TAG, "fetchFollowedStreamersRooms() onResponse");
// 恢复真实适配器
binding.roomsRecyclerView.setAdapter(adapter);
LoadingStateManager.stopRefreshing(binding.swipeRefresh);
ApiResponse<List<Room>> body = response.body();
if (!response.isSuccessful() || body == null || !body.isOk() || body.getData() == null) {
// 请求失败
followRooms.clear();
allRooms.clear();
adapter.submitList(new ArrayList<>());
if (binding.emptyStateView != null) {
binding.emptyStateView.setNetworkErrorState();
binding.emptyStateView.setOnActionClickListener(v -> fetchFollowedStreamersRooms());
binding.emptyStateView.setVisibility(View.VISIBLE);
}
return;
}
List<Room> allRoomsList = body.getData();
// 获取已关注的用户列表
ApiClient.getService(getApplicationContext())
.getFollowingList(1, 1000)
.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) {
ApiResponse<PageResponse<Map<String, Object>>> body = response.body();
if (!response.isSuccessful() || body == null || !body.isOk() ||
body.getData() == null || body.getData().getList() == null) {
// 获取关注列表失败,显示空状态
showNoFollowedStreamersState();
return;
}
List<Map<String, Object>> followingList = body.getData().getList();
Log.d(TAG, "获取到关注列表,数量: " + followingList.size());
if (followingList.isEmpty()) {
// 没有关注任何人
showNoFollowedStreamersState();
return;
}
// 提取已关注用户的ID列表
List<Integer> followedUserIds = new ArrayList<>();
for (Map<String, Object> user : followingList) {
Object uidObj = user.get("uid");
if (uidObj instanceof Number) {
int uid = ((Number) uidObj).intValue();
followedUserIds.add(uid);
Log.d(TAG, "关注的用户ID: " + uid + ", 昵称: " + user.get("nickname"));
}
}
Log.d(TAG, "所有直播间数量: " + allRoomsList.size());
// 筛选出已关注主播的直播间
List<Room> filteredRooms = new ArrayList<>();
for (Room room : allRoomsList) {
if (room != null) {
Integer roomUid = room.getUid();
Log.d(TAG, "直播间: " + room.getTitle() + ", uid=" + roomUid +
", isLive=" + room.isLive() +
", 是否匹配=" + (roomUid != null && followedUserIds.contains(roomUid)));
if (roomUid != null && followedUserIds.contains(roomUid)) {
filteredRooms.add(room);
Log.d(TAG, "匹配成功!添加直播间: " + room.getTitle());
}
}
}
Log.d(TAG, "筛选后的直播间数量: " + filteredRooms.size());
// 更新数据
followRooms.clear();
followRooms.addAll(filteredRooms);
allRooms.clear();
allRooms.addAll(filteredRooms);
adapter.submitList(new ArrayList<>(filteredRooms));
// 更新空状态
if (filteredRooms.isEmpty()) {
showNoLiveFollowedStreamersState();
} else {
hideEmptyState();
}
}
@Override
public void onFailure(Call<ApiResponse<PageResponse<Map<String, Object>>>> call,
Throwable t) {
Log.e(TAG, "获取关注列表失败", t);
showNoFollowedStreamersState();
}
});
}
@Override
public void onFailure(Call<ApiResponse<List<Room>>> call, Throwable t) {
Log.e(TAG, "fetchFollowedStreamersRooms() onFailure", t);
// 恢复真实适配器
binding.roomsRecyclerView.setAdapter(adapter);
LoadingStateManager.stopRefreshing(binding.swipeRefresh);
followRooms.clear();
allRooms.clear();
adapter.submitList(new ArrayList<>());
if (binding.emptyStateView != null) {
binding.emptyStateView.setNetworkErrorState();
binding.emptyStateView.setOnActionClickListener(v -> fetchFollowedStreamersRooms());
binding.emptyStateView.setVisibility(View.VISIBLE);
}
}
});
}
/**
* 显示没有关注任何主播的空状态
*/
private void showNoFollowedStreamersState() {
followRooms.clear();
allRooms.clear();
adapter.submitList(new ArrayList<>());
if (binding.emptyStateView != null) {
binding.emptyStateView.setCustomState(
"还没有关注主播",
"快去发现页面关注你喜欢的主播吧",
"去发现",
v -> {
// 切换到发现页面
TabLayout.Tab discoverTab = binding.topTabs.getTabAt(1);
if (discoverTab != null) {
discoverTab.select();
}
}
);
binding.emptyStateView.setVisibility(View.VISIBLE);
}
}
/**
* 显示已关注的主播都没有在直播的空状态
*/
private void showNoLiveFollowedStreamersState() {
if (binding.emptyStateView != null) {
binding.emptyStateView.setCustomState(
"关注的主播都不在线",
"你关注的主播暂时都没有开播",
"刷新",
v -> fetchFollowedStreamersRooms()
);
binding.emptyStateView.setVisibility(View.VISIBLE);
}
}
/**
* 显示发现页面
*/
private void showDiscoverTab() {
// 显示分类标签容器(包含标签和展开箭头)
View categoryTabsContainer = findViewById(R.id.categoryTabsContainer);
if (categoryTabsContainer != null) {
categoryTabsContainer.setVisibility(View.VISIBLE);
}
// 显示发布按钮
if (binding.btnPublish != null) {
binding.btnPublish.setVisibility(View.VISIBLE);
}
// 设置展开箭头按钮点击事件
View btnExpandCategories = findViewById(R.id.btnExpandCategories);
if (btnExpandCategories != null) {
btnExpandCategories.setOnClickListener(v -> showChannelManagerDialog());
}
// 设置发布按钮点击事件
if (binding.btnPublish != null) {
binding.btnPublish.setOnClickListener(v -> {
// 检查登录状态
if (!AuthHelper.requireLogin(MainActivity.this, "发布内容需要登录")) {
return;
}
// 跳转到发布页面
showCreateRoomDialog();
});
}
// 隐藏其他Tab内容
View followTab = findViewById(R.id.followTabContent);
View nearbyTab = findViewById(R.id.nearbyTabContent);
View discoverTab = findViewById(R.id.discoverTabContent);
if (followTab != null) {
followTab.setVisibility(View.GONE);
}
if (nearbyTab != null) {
nearbyTab.setVisibility(View.GONE);
}
// 发现页面的空状态提示容器
if (discoverTab != null) {
discoverTab.setVisibility(View.VISIBLE);
}
if (binding.swipeRefresh != null) {
binding.swipeRefresh.setVisibility(View.VISIBLE);
}
hideEmptyState();
// 设置发现页面空状态刷新按钮点击事件
View btnRefreshDiscover = findViewById(R.id.btnRefreshDiscover);
if (btnRefreshDiscover != null) {
btnRefreshDiscover.setOnClickListener(v -> fetchDiscoverRooms());
}
// 恢复房间列表的布局管理器和适配器
if (binding.roomsRecyclerView != null) {
binding.roomsRecyclerView.setLayoutManager(roomsLayoutManager);
binding.roomsRecyclerView.setAdapter(adapter);
binding.roomsRecyclerView.setVisibility(View.VISIBLE);
}
// 使用房间适配器显示推荐内容
if (adapter != null) {
// 先显示所有推荐房间,然后应用分类筛选
allRooms.clear();
allRooms.addAll(discoverRooms);
applyCategoryFilterWithAnimation(currentCategory);
}
// 更新空状态
updateDiscoverEmptyState();
}
/**
* 更新发现页面的空状态
*/
private void updateDiscoverEmptyState() {
View discoverEmptyContainer = findViewById(R.id.discoverEmptyContainer);
TextView discoverEmptyText = findViewById(R.id.discoverEmptyText);
if (allRooms.isEmpty()) {
// 显示空状态
if (discoverEmptyContainer != null) {
discoverEmptyContainer.setVisibility(View.VISIBLE);
}
if (discoverEmptyText != null) {
discoverEmptyText.setText("该分类下暂无直播间");
}
if (binding.roomsRecyclerView != null) {
binding.roomsRecyclerView.setVisibility(View.GONE);
}
} else {
// 隐藏空状态
if (discoverEmptyContainer != null) {
discoverEmptyContainer.setVisibility(View.GONE);
}
if (binding.roomsRecyclerView != null) {
binding.roomsRecyclerView.setVisibility(View.VISIBLE);
}
}
}
/**
* 显示附近页面
*/
private void showNearbyTab() {
// 隐藏分类标签容器(附近页面不需要分类筛选)
View categoryTabsContainer = findViewById(R.id.categoryTabsContainer);
if (categoryTabsContainer != null) {
categoryTabsContainer.setVisibility(View.GONE);
}
// 隐藏发布按钮
if (binding.btnPublish != null) {
binding.btnPublish.setVisibility(View.GONE);
}
// 隐藏其他Tab内容显示附近Tab
View followTab = findViewById(R.id.followTabContent);
View discoverTab = findViewById(R.id.discoverTabContent);
View nearbyTab = findViewById(R.id.nearbyTabContent);
if (followTab != null) {
followTab.setVisibility(View.GONE);
}
if (discoverTab != null) {
discoverTab.setVisibility(View.GONE);
}
if (binding.swipeRefresh != null) {
binding.swipeRefresh.setVisibility(View.GONE);
}
if (nearbyTab != null) {
nearbyTab.setVisibility(View.VISIBLE);
}
hideEmptyState();
// 初始化附近用户适配器(如果还没有)
if (nearbyUsersAdapter == null) {
nearbyUsersAdapter = new NearbyUsersAdapter(user -> {
if (user == null) return;
// 点击附近用户,跳转到用户主页
UserProfileReadOnlyActivity.start(MainActivity.this,
user.getId(),
user.getName(),
user.getDistanceText(), // 位置信息
"", // bio
""); // avatarUrl
});
// 设置添加好友按钮点击事件
nearbyUsersAdapter.setOnAddFriendClickListener(user -> {
if (user == null) return;
sendFriendRequestToNearbyUser(user);
});
}
// 设置附近内容RecyclerView
RecyclerView nearbyContentList = findViewById(R.id.nearbyContentList);
if (nearbyContentList != null && nearbyContentList.getAdapter() == null) {
nearbyContentList.setLayoutManager(new LinearLayoutManager(this));
nearbyContentList.setAdapter(nearbyUsersAdapter);
}
// 设置附近页面的SwipeRefreshLayout
SwipeRefreshLayout nearbySwipeRefresh = findViewById(R.id.nearbySwipeRefresh);
if (nearbySwipeRefresh != null) {
nearbySwipeRefresh.setOnRefreshListener(this::loadNearbyUsers);
}
// 设置刷新按钮点击事件
View btnOpenLocal = findViewById(R.id.btnOpenLocal);
if (btnOpenLocal != null) {
btnOpenLocal.setOnClickListener(v -> loadNearbyUsers());
}
// 设置开启定位按钮点击事件
View btnEnableLocation = findViewById(R.id.btnEnableLocation);
if (btnEnableLocation != null) {
btnEnableLocation.setOnClickListener(v -> requestLocationPermission());
}
// 检查位置权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// 显示空状态提示,隐藏其他状态
View emptyNearbyContainer = findViewById(R.id.emptyNearbyContainer);
View nearbyLoadingContainer = findViewById(R.id.nearbyLoadingContainer);
RecyclerView nearbyList = findViewById(R.id.nearbyContentList);
if (nearbyLoadingContainer != null) {
nearbyLoadingContainer.setVisibility(View.GONE);
}
if (emptyNearbyContainer != null) {
emptyNearbyContainer.setVisibility(View.VISIBLE);
}
if (nearbyList != null) {
nearbyList.setVisibility(View.GONE);
}
// 请求位置权限
requestLocationPermission();
return;
}
// 有权限,加载附近用户
loadNearbyUsers();
}
/**
* 显示关注页面时从后端获取关注主播的直播间列表
* 注意关注功能需要用户登录在showFollowTab()中会检查登录状态
*/
private void fetchFollowRooms() {
// 检查登录状态
if (!AuthHelper.isLoggedIn(this)) {
followRooms.clear();
if (adapter != null) {
adapter.submitList(new ArrayList<>());
}
return;
}
// 显示加载状态
if (binding.loading != null) {
binding.loading.setVisibility(View.VISIBLE);
}
// 从关注列表获取关注的用户ID然后筛选出正在直播的房间
ApiClient.getService(getApplicationContext()).getFollowingList(1, 100)
.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) {
if (binding.loading != null) {
binding.loading.setVisibility(View.GONE);
}
ApiResponse<PageResponse<Map<String, Object>>> body = response.body();
if (response.isSuccessful() && body != null && body.isOk() && body.getData() != null) {
List<Map<String, Object>> followingList = body.getData().getList();
if (followingList != null && !followingList.isEmpty()) {
// 获取所有直播间,然后筛选出关注用户的直播间
fetchAndFilterFollowRooms(followingList);
} else {
followRooms.clear();
if ("关注".equals(currentTopTab) && adapter != null) {
adapter.submitList(new ArrayList<>());
}
}
}
}
@Override
public void onFailure(Call<ApiResponse<PageResponse<Map<String, Object>>>> call, Throwable t) {
if (binding.loading != null) {
binding.loading.setVisibility(View.GONE);
}
followRooms.clear();
if ("关注".equals(currentTopTab) && adapter != null) {
adapter.submitList(new ArrayList<>());
}
}
});
}
/**
* 获取所有直播间并筛选出关注用户的直播间
*/
private void fetchAndFilterFollowRooms(List<Map<String, Object>> followList) {
// 从关注列表中提取用户ID
if (followList == null || followList.isEmpty()) {
if (adapter != null) {
adapter.submitList(new ArrayList<>());
}
return;
}
// TODO: 实现根据关注列表筛选直播间
if (adapter != null) {
adapter.submitList(new ArrayList<>());
}
}
/**
* 构建发现页面的房间列表(推荐算法前端实现)
*/
private List<Room> buildDiscoverRooms() {
// TODO: 接入后端接口 - 获取推荐直播间列表
// 接口路径: GET /api/rooms/recommend 或 GET /api/rooms?type=recommend
// 请求参数:
// - userId: 当前用户ID从token中获取用于个性化推荐
// - page (可选): 页码
// - pageSize (可选): 每页数量
// 返回数据格式: ApiResponse<List<Room>>
// Room对象应包含: id, title, streamerName, type, isLive, coverUrl, viewerCount, recommendScore等字段
// 后端应根据用户观看历史、点赞记录、关注关系等进行个性化推荐
List<Room> list = new ArrayList<>();
// 不再使用模拟数据,只从后端接口获取真实推荐直播间数据
return list;
}
/**
* 构建附近页面的用户列表(使用模拟位置数据)
*/
private List<NearbyUser> buildNearbyUsers() {
// TODO: 接入后端接口 - 获取附近用户列表
// 接口路径: GET /api/users/nearby
// 请求参数:
// - latitude: 当前用户纬度(必填)
// - longitude: 当前用户经度(必填)
// - radius (可选): 搜索半径单位默认5000
// - page (可选): 页码
// - pageSize (可选): 每页数量
// 返回数据格式: ApiResponse<List<NearbyUser>>
// NearbyUser对象应包含: id, name, avatarUrl, distance (距离,单位:米), isLive, location等字段
// 需要先获取用户位置权限,然后调用此接口
List<NearbyUser> list = new ArrayList<>();
// 不再使用模拟数据,只从后端接口获取真实附近用户数据
return list;
}
/**
* 请求位置权限
*/
private void requestLocationPermission() {
// 检查是否应该显示权限说明
// shouldShowRequestPermissionRationale 返回 true 表示用户之前拒绝过,但还可以再次请求
// 返回 false 可能是第一次请求,也可能是用户选择了"不再询问"
boolean shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION);
if (shouldShowRationale) {
// 用户之前拒绝过,但还可以再次请求,显示说明后直接请求权限
new AlertDialog.Builder(this)
.setTitle("需要位置权限")
.setMessage("附近功能需要访问位置信息,以便为您推荐附近的用户和直播。")
.setPositiveButton("授权", (dialog, which) -> {
// 直接请求权限
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
},
REQUEST_LOCATION_PERMISSION);
})
.setNegativeButton("取消", null)
.setCancelable(true)
.show();
} else {
// 第一次请求权限,或者用户选择了"不再询问"
// 直接请求权限,如果用户选择了"不再询问",系统会静默失败
// 我们会在权限回调中处理这种情况
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
},
REQUEST_LOCATION_PERMISSION);
}
}
// 用于取消超时回调的Runnable
private Runnable followLoadingTimeoutRunnable;
private Runnable nearbyLoadingTimeoutRunnable;
/**
* 停止关注页面的下拉刷新
*/
private void stopFollowSwipeRefresh() {
SwipeRefreshLayout followSwipeRefresh = findViewById(R.id.followSwipeRefresh);
if (followSwipeRefresh != null && followSwipeRefresh.isRefreshing()) {
followSwipeRefresh.setRefreshing(false);
}
}
/**
* 停止附近页面的下拉刷新
*/
private void stopNearbySwipeRefresh() {
SwipeRefreshLayout nearbySwipeRefresh = findViewById(R.id.nearbySwipeRefresh);
if (nearbySwipeRefresh != null && nearbySwipeRefresh.isRefreshing()) {
nearbySwipeRefresh.setRefreshing(false);
}
}
/**
* 加载推荐用户列表(关注页面)
* 有关注显示关注用户没有关注则随机展示10名用户
*/
private void loadRecommendUsers() {
// 显示加载状态,隐藏其他状态
View followLoadingContainer = findViewById(R.id.followLoadingContainer);
View emptyRecommendContainer = findViewById(R.id.emptyRecommendContainer);
View emptyFollowContainer = findViewById(R.id.emptyFollowContainer);
View followedUsersContainer = findViewById(R.id.followedUsersContainer);
RecyclerView recommendUsersList = findViewById(R.id.recommendUsersList);
// 如果不是下拉刷新触发的,才显示加载容器
// 获取SwipeRefreshLayout引用判断是否是下拉刷新触发
SwipeRefreshLayout followSwipeRefresh = findViewById(R.id.followSwipeRefresh);
boolean isSwipeRefresh = followSwipeRefresh != null && followSwipeRefresh.isRefreshing();
// 如果不是下拉刷新触发的,才显示加载容器
if (!isSwipeRefresh) {
if (followLoadingContainer != null) {
followLoadingContainer.setVisibility(View.VISIBLE);
}
}
if (emptyRecommendContainer != null) {
emptyRecommendContainer.setVisibility(View.GONE);
}
if (recommendUsersList != null) {
recommendUsersList.setVisibility(View.GONE);
}
// 取消之前的超时回调
if (followLoadingTimeoutRunnable != null) {
handler.removeCallbacks(followLoadingTimeoutRunnable);
}
// 设置3秒超时确保加载圈不会一直显示
followLoadingTimeoutRunnable = () -> {
// 停止下拉刷新
stopFollowSwipeRefresh();
if (followLoadingContainer != null && followLoadingContainer.getVisibility() == View.VISIBLE) {
followLoadingContainer.setVisibility(View.GONE);
// 如果还在加载,显示空状态
if (recommendUsers.isEmpty()) {
if (recommendUsersList != null) {
recommendUsersList.setVisibility(View.GONE);
}
if (emptyRecommendContainer != null) {
emptyRecommendContainer.setVisibility(View.VISIBLE);
}
}
}
};
handler.postDelayed(followLoadingTimeoutRunnable, 3000);
// 先检查是否有关注的用户
if (AuthHelper.isLoggedIn(this)) {
checkFollowingAndLoadUsers(followLoadingContainer, emptyRecommendContainer, emptyFollowContainer,
followedUsersContainer, recommendUsersList);
} else {
// 未登录,直接加载随机推荐用户
if (emptyFollowContainer != null) {
emptyFollowContainer.setVisibility(View.VISIBLE);
}
if (followedUsersContainer != null) {
followedUsersContainer.setVisibility(View.GONE);
}
loadRandomRecommendUsers(followLoadingContainer, emptyRecommendContainer, recommendUsersList);
}
}
/**
* 检查关注列表并加载用户
*/
private void checkFollowingAndLoadUsers(View followLoadingContainer, View emptyRecommendContainer,
View emptyFollowContainer, View followedUsersContainer, RecyclerView recommendUsersList) {
ApiClient.getService(getApplicationContext()).getFollowingList(1, 10)
.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) {
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
PageResponse<Map<String, Object>> data = response.body().getData();
if (data != null && data.getList() != null && !data.getList().isEmpty()) {
// 有关注的用户,显示关注区域
if (emptyFollowContainer != null) {
emptyFollowContainer.setVisibility(View.GONE);
}
if (followedUsersContainer != null) {
followedUsersContainer.setVisibility(View.VISIBLE);
}
// TODO: 加载关注用户的动态
} else {
// 没有关注的用户
if (emptyFollowContainer != null) {
emptyFollowContainer.setVisibility(View.VISIBLE);
}
if (followedUsersContainer != null) {
followedUsersContainer.setVisibility(View.GONE);
}
}
} else {
if (emptyFollowContainer != null) {
emptyFollowContainer.setVisibility(View.VISIBLE);
}
}
// 加载随机推荐用户
loadRandomRecommendUsers(followLoadingContainer, emptyRecommendContainer, recommendUsersList);
}
@Override
public void onFailure(Call<ApiResponse<PageResponse<Map<String, Object>>>> call, Throwable t) {
if (emptyFollowContainer != null) {
emptyFollowContainer.setVisibility(View.VISIBLE);
}
loadRandomRecommendUsers(followLoadingContainer, emptyRecommendContainer, recommendUsersList);
}
});
}
/**
* 加载随机推荐用户最多10名
* 优先使用推荐接口,失败则使用附近用户接口
*/
private void loadRandomRecommendUsers(View followLoadingContainer, View emptyRecommendContainer,
RecyclerView recommendUsersList) {
// 从后端获取推荐用户
ApiClient.getService(getApplicationContext()).getRecommendUsers(30)
.enqueue(new Callback<ApiResponse<List<CommunityResponse.MatchUser>>>() {
@Override
public void onResponse(Call<ApiResponse<List<CommunityResponse.MatchUser>>> call,
Response<ApiResponse<List<CommunityResponse.MatchUser>>> response) {
// 取消超时回调
if (followLoadingTimeoutRunnable != null) {
handler.removeCallbacks(followLoadingTimeoutRunnable);
}
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
List<CommunityResponse.MatchUser> users = response.body().getData();
if (users != null && !users.isEmpty()) {
displayRecommendUsers(users, followLoadingContainer, emptyRecommendContainer, recommendUsersList);
} else {
// 推荐用户为空,尝试使用附近用户接口
loadUsersFromNearby(followLoadingContainer, emptyRecommendContainer, recommendUsersList);
}
} else {
// 请求失败,尝试使用附近用户接口
loadUsersFromNearby(followLoadingContainer, emptyRecommendContainer, recommendUsersList);
}
}
@Override
public void onFailure(Call<ApiResponse<List<CommunityResponse.MatchUser>>> call, Throwable t) {
Log.e(TAG, "加载推荐用户失败: " + t.getMessage());
// 取消超时回调
if (followLoadingTimeoutRunnable != null) {
handler.removeCallbacks(followLoadingTimeoutRunnable);
}
// 尝试使用附近用户接口
loadUsersFromNearby(followLoadingContainer, emptyRecommendContainer, recommendUsersList);
}
});
}
/**
* 从附近用户API加载用户备选方案
*/
private void loadUsersFromNearby(View followLoadingContainer, View emptyRecommendContainer,
RecyclerView recommendUsersList) {
// 使用附近用户接口获取用户列表传入0,0坐标后端会返回所有用户
ApiClient.getService(getApplicationContext()).getNearbyUsers(0, 0, 50000, 30)
.enqueue(new Callback<ApiResponse<CommunityResponse.NearbyUserList>>() {
@Override
public void onResponse(Call<ApiResponse<CommunityResponse.NearbyUserList>> call,
Response<ApiResponse<CommunityResponse.NearbyUserList>> response) {
// 停止下拉刷新
stopFollowSwipeRefresh();
// 隐藏加载状态
if (followLoadingContainer != null) {
followLoadingContainer.setVisibility(View.GONE);
}
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
CommunityResponse.NearbyUserList userList = response.body().getData();
if (userList != null && userList.getUsers() != null && !userList.getUsers().isEmpty()) {
List<CommunityResponse.NearbyUser> users = userList.getUsers();
recommendUsers.clear();
String baseUrl = ApiClient.getCurrentBaseUrl(getApplicationContext());
// 随机打乱用户列表
List<CommunityResponse.NearbyUser> shuffledUsers = new ArrayList<>(users);
Collections.shuffle(shuffledUsers);
// 最多取10名用户
int count = Math.min(10, shuffledUsers.size());
for (int i = 0; i < count; i++) {
CommunityResponse.NearbyUser user = shuffledUsers.get(i);
String avatarUrl = "";
if (user.avatar != null && !user.avatar.isEmpty()) {
if (user.avatar.startsWith("http://") || user.avatar.startsWith("https://")) {
avatarUrl = user.avatar;
} else if (user.avatar.startsWith("crmebimage/")) {
avatarUrl = baseUrl.replace("/api/", "/") + user.avatar;
} else {
avatarUrl = user.avatar;
}
}
// 只显示个性签名,没有签名则显示默认文本
String desc = (user.bio != null && !user.bio.isEmpty()) ? user.bio : "这个人很懒,什么都没写";
recommendUsers.add(new RecommendUserAdapter.RecommendUser(
user.id, user.nickname, avatarUrl, desc, false));
}
if (recommendUserAdapter != null) {
recommendUserAdapter.submitList(new ArrayList<>(recommendUsers));
}
// 显示列表
if (recommendUsersList != null) {
recommendUsersList.setVisibility(View.VISIBLE);
}
if (emptyRecommendContainer != null) {
emptyRecommendContainer.setVisibility(View.GONE);
}
} else {
showEmptyRecommendState(followLoadingContainer, emptyRecommendContainer, recommendUsersList);
}
} else {
showEmptyRecommendState(followLoadingContainer, emptyRecommendContainer, recommendUsersList);
}
}
@Override
public void onFailure(Call<ApiResponse<CommunityResponse.NearbyUserList>> call, Throwable t) {
Log.e(TAG, "从附近用户加载失败: " + t.getMessage());
showEmptyRecommendState(followLoadingContainer, emptyRecommendContainer, recommendUsersList);
}
});
}
/**
* 显示推荐用户列表
*/
private void displayRecommendUsers(List<CommunityResponse.MatchUser> users,
View followLoadingContainer, View emptyRecommendContainer, RecyclerView recommendUsersList) {
// 停止下拉刷新
stopFollowSwipeRefresh();
// 隐藏加载状态
if (followLoadingContainer != null) {
followLoadingContainer.setVisibility(View.GONE);
}
recommendUsers.clear();
String baseUrl = ApiClient.getCurrentBaseUrl(getApplicationContext());
// 随机打乱用户列表
List<CommunityResponse.MatchUser> shuffledUsers = new ArrayList<>(users);
Collections.shuffle(shuffledUsers);
// 最多取10名用户
int count = Math.min(10, shuffledUsers.size());
for (int i = 0; i < count; i++) {
CommunityResponse.MatchUser user = shuffledUsers.get(i);
String avatarUrl = "";
if (user.avatar != null && !user.avatar.isEmpty()) {
if (user.avatar.startsWith("http://") || user.avatar.startsWith("https://")) {
avatarUrl = user.avatar;
} else if (user.avatar.startsWith("crmebimage/")) {
avatarUrl = baseUrl.replace("/api/", "/") + user.avatar;
} else {
avatarUrl = user.avatar;
}
}
// 只显示个性签名,没有签名则显示默认文本
String desc = (user.bio != null && !user.bio.isEmpty()) ? user.bio : "这个人很懒,什么都没写";
recommendUsers.add(new RecommendUserAdapter.RecommendUser(user.id, user.nickname, avatarUrl, desc, false));
}
if (recommendUserAdapter != null) {
recommendUserAdapter.submitList(new ArrayList<>(recommendUsers));
}
// 显示列表
if (recommendUsersList != null) {
recommendUsersList.setVisibility(View.VISIBLE);
}
if (emptyRecommendContainer != null) {
emptyRecommendContainer.setVisibility(View.GONE);
}
}
/**
* 显示推荐用户空状态
*/
private void showEmptyRecommendState(View followLoadingContainer, View emptyRecommendContainer,
RecyclerView recommendUsersList) {
// 停止下拉刷新
stopFollowSwipeRefresh();
if (followLoadingContainer != null) {
followLoadingContainer.setVisibility(View.GONE);
}
if (recommendUsersList != null) {
recommendUsersList.setVisibility(View.GONE);
}
if (emptyRecommendContainer != null) {
emptyRecommendContainer.setVisibility(View.VISIBLE);
}
}
/**
* 关注用户
*/
private void followUser(RecommendUserAdapter.RecommendUser user, int position) {
if (!AuthHelper.isLoggedIn(this)) {
AuthHelper.requireLogin(this, "关注用户需要登录");
return;
}
Map<String, Object> body = new java.util.HashMap<>();
body.put("userId", user.getId());
ApiClient.getService(getApplicationContext()).followUser(body)
.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 && response.body().isOk()) {
user.setFollowed(true);
if (recommendUserAdapter != null) {
recommendUserAdapter.notifyItemChanged(position);
}
Toast.makeText(MainActivity.this, "关注成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "关注失败", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
Toast.makeText(MainActivity.this, "网络错误", Toast.LENGTH_SHORT).show();
}
});
}
/**
* 向附近用户发送好友请求
*/
private void sendFriendRequestToNearbyUser(NearbyUser user) {
if (!AuthHelper.isLoggedIn(this)) {
AuthHelper.requireLogin(this, "添加好友需要登录");
return;
}
try {
int userId = Integer.parseInt(user.getId());
Map<String, Object> body = new java.util.HashMap<>();
body.put("targetUserId", userId);
body.put("message", "我想加你为好友");
ApiClient.getService(getApplicationContext()).sendFriendRequest(body)
.enqueue(new Callback<ApiResponse<Boolean>>() {
@Override
public void onResponse(Call<ApiResponse<Boolean>> call, Response<ApiResponse<Boolean>> response) {
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
Toast.makeText(MainActivity.this, "好友请求已发送", Toast.LENGTH_SHORT).show();
} else {
String msg = response.body() != null && response.body().getMessage() != null
? response.body().getMessage() : "发送失败";
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<Boolean>> call, Throwable t) {
Toast.makeText(MainActivity.this, "网络错误", Toast.LENGTH_SHORT).show();
}
});
} catch (NumberFormatException e) {
Toast.makeText(this, "用户ID格式错误", Toast.LENGTH_SHORT).show();
}
}
/**
* 加载频道数据(发现页面)
* 只保留5个固定标签推荐、游戏、才艺、直播、音乐
*/
private void loadChannelData() {
// 初始化默认频道5个固定标签
if (myChannels.isEmpty()) {
myChannels.add(new ChannelTagAdapter.ChannelTag(1, "推荐", true));
myChannels.add(new ChannelTagAdapter.ChannelTag(2, "游戏", true));
myChannels.add(new ChannelTagAdapter.ChannelTag(3, "才艺", true));
myChannels.add(new ChannelTagAdapter.ChannelTag(4, "直播", true));
myChannels.add(new ChannelTagAdapter.ChannelTag(5, "音乐", true));
}
if (myChannelAdapter != null) {
myChannelAdapter.submitList(new ArrayList<>(myChannels));
myChannelAdapter.setSelectedPosition(0); // 默认选中推荐
}
}
/**
* 加载附近用户(附近页面)
* 展示同城用户最多10名刷新时随机展示同省用户
*/
private void loadNearbyUsers() {
View emptyNearbyContainer = findViewById(R.id.emptyNearbyContainer);
RecyclerView nearbyList = findViewById(R.id.nearbyContentList);
View nearbyLoadingContainer = findViewById(R.id.nearbyLoadingContainer);
TextView nearbyCountText = findViewById(R.id.nearbyCountText);
TextView locationText = findViewById(R.id.locationText);
// 获取SwipeRefreshLayout引用判断是否是下拉刷新触发
SwipeRefreshLayout nearbySwipeRefresh = findViewById(R.id.nearbySwipeRefresh);
boolean isSwipeRefresh = nearbySwipeRefresh != null && nearbySwipeRefresh.isRefreshing();
// 如果不是下拉刷新触发的,才显示加载容器
if (!isSwipeRefresh) {
if (nearbyLoadingContainer != null) {
nearbyLoadingContainer.setVisibility(View.VISIBLE);
}
}
if (emptyNearbyContainer != null) {
emptyNearbyContainer.setVisibility(View.GONE);
}
if (nearbyList != null) {
nearbyList.setVisibility(View.GONE);
}
if (locationText != null) {
locationText.setText("正在搜索附近的人...");
}
// 取消之前的超时回调
if (nearbyLoadingTimeoutRunnable != null) {
handler.removeCallbacks(nearbyLoadingTimeoutRunnable);
}
// 设置3秒超时确保加载圈不会一直显示
nearbyLoadingTimeoutRunnable = () -> {
// 停止下拉刷新
stopNearbySwipeRefresh();
if (nearbyLoadingContainer != null && nearbyLoadingContainer.getVisibility() == View.VISIBLE) {
nearbyLoadingContainer.setVisibility(View.GONE);
// 如果还在加载,显示空状态
if (nearbyUsers.isEmpty()) {
if (nearbyList != null) {
nearbyList.setVisibility(View.GONE);
}
if (emptyNearbyContainer != null) {
emptyNearbyContainer.setVisibility(View.VISIBLE);
}
if (locationText != null) {
locationText.setText("加载超时,请重试");
}
}
}
};
handler.postDelayed(nearbyLoadingTimeoutRunnable, 3000);
// 从后端获取附近用户(获取更多用户以便随机筛选)
ApiClient.getService(getApplicationContext()).getNearbyUsers(0, 0, 50000, 50)
.enqueue(new Callback<ApiResponse<CommunityResponse.NearbyUserList>>() {
@Override
public void onResponse(Call<ApiResponse<CommunityResponse.NearbyUserList>> call,
Response<ApiResponse<CommunityResponse.NearbyUserList>> response) {
// 取消超时回调
if (nearbyLoadingTimeoutRunnable != null) {
handler.removeCallbacks(nearbyLoadingTimeoutRunnable);
}
// 停止下拉刷新
stopNearbySwipeRefresh();
// 隐藏加载状态
if (nearbyLoadingContainer != null) {
nearbyLoadingContainer.setVisibility(View.GONE);
}
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
CommunityResponse.NearbyUserList userList = response.body().getData();
if (userList != null && userList.getUsers() != null && !userList.getUsers().isEmpty()) {
nearbyUsers.clear();
// 随机打乱用户列表
List<CommunityResponse.NearbyUser> shuffledUsers = new ArrayList<>(userList.getUsers());
Collections.shuffle(shuffledUsers);
// 最多取10名用户
int count = Math.min(10, shuffledUsers.size());
for (int i = 0; i < count; i++) {
CommunityResponse.NearbyUser user = shuffledUsers.get(i);
String location = user.location != null ? user.location : "";
nearbyUsers.add(new NearbyUser(String.valueOf(user.id), user.nickname, location, user.isOnline));
}
if (nearbyUsersAdapter != null) {
nearbyUsersAdapter.submitList(new ArrayList<>(nearbyUsers));
}
// 更新附近人数
if (nearbyCountText != null) {
nearbyCountText.setText("" + nearbyUsers.size() + "");
}
if (locationText != null) {
locationText.setText("已找到附近的人");
}
// 显示列表,隐藏空状态
if (emptyNearbyContainer != null) {
emptyNearbyContainer.setVisibility(View.GONE);
}
if (nearbyList != null) {
nearbyList.setVisibility(View.VISIBLE);
}
} else {
// 没有数据,显示空状态
if (nearbyCountText != null) {
nearbyCountText.setText("");
}
if (locationText != null) {
locationText.setText("附近暂无用户");
}
if (emptyNearbyContainer != null) {
emptyNearbyContainer.setVisibility(View.VISIBLE);
}
if (nearbyList != null) {
nearbyList.setVisibility(View.GONE);
}
}
} else {
// 请求失败
if (locationText != null) {
locationText.setText("获取附近用户失败");
}
if (emptyNearbyContainer != null) {
emptyNearbyContainer.setVisibility(View.VISIBLE);
}
if (nearbyList != null) {
nearbyList.setVisibility(View.GONE);
}
}
}
@Override
public void onFailure(Call<ApiResponse<CommunityResponse.NearbyUserList>> call, Throwable t) {
Log.e(TAG, "加载附近用户失败: " + t.getMessage());
// 取消超时回调
if (nearbyLoadingTimeoutRunnable != null) {
handler.removeCallbacks(nearbyLoadingTimeoutRunnable);
}
// 停止下拉刷新
stopNearbySwipeRefresh();
// 隐藏加载状态,显示空状态
if (nearbyLoadingContainer != null) {
nearbyLoadingContainer.setVisibility(View.GONE);
}
if (locationText != null) {
locationText.setText("网络错误,请重试");
}
if (emptyNearbyContainer != null) {
emptyNearbyContainer.setVisibility(View.VISIBLE);
}
if (nearbyList != null) {
nearbyList.setVisibility(View.GONE);
}
}
});
}
/**
* 显示频道管理底部弹窗
*/
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;
}
}
}
}