zhibo/android-app/app/src/main/java/com/example/livestreaming/SearchActivity.java
2025-12-30 09:31:15 +08:00

364 lines
14 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.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.example.livestreaming.databinding.ActivitySearchBinding;
import com.example.livestreaming.net.ApiResponse;
import com.example.livestreaming.net.ApiService;
import com.example.livestreaming.net.HotSearchResponse;
import com.example.livestreaming.net.PageResponse;
import com.example.livestreaming.net.ApiClient;
import com.example.livestreaming.net.Room;
import com.example.livestreaming.net.SearchHistoryResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class SearchActivity extends AppCompatActivity {
private static final String TAG = "SearchActivity";
private ActivitySearchBinding binding;
private RoomsAdapter adapter;
private final List<Room> all = new ArrayList<>();
private boolean isSearching = false;
private String lastSearchKeyword = "";
private static final String EXTRA_SEARCH_QUERY = "search_query";
public static void start(Context context) {
start(context, null);
}
public static void start(Context context, String searchQuery) {
Intent intent = new Intent(context, SearchActivity.class);
if (searchQuery != null && !searchQuery.trim().isEmpty()) {
intent.putExtra(EXTRA_SEARCH_QUERY, searchQuery);
}
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySearchBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setupList();
setupInput();
binding.backButton.setOnClickListener(v -> finish());
binding.cancelBtn.setOnClickListener(v -> finish());
// 如果从Intent中获取到搜索关键词自动填充到搜索框并执行搜索
String searchQuery = getIntent().getStringExtra(EXTRA_SEARCH_QUERY);
if (searchQuery != null && !searchQuery.trim().isEmpty()) {
binding.searchInput.setText(searchQuery);
binding.searchInput.setSelection(searchQuery.length());
performSearch(searchQuery);
} else {
// 加载热门搜索
loadHotSearch();
}
binding.searchInput.requestFocus();
}
private void setupList() {
adapter = new RoomsAdapter(room -> {
if (room == null) return;
Intent intent = new Intent(SearchActivity.this, RoomDetailActivity.class);
intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
startActivity(intent);
});
StaggeredGridLayoutManager glm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
glm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
binding.resultsRecyclerView.setLayoutManager(glm);
binding.resultsRecyclerView.setAdapter(adapter);
}
private void setupInput() {
binding.searchInput.setImeOptions(EditorInfo.IME_ACTION_SEARCH);
// 监听搜索按钮点击
binding.searchInput.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
String keyword = binding.searchInput.getText().toString().trim();
if (!keyword.isEmpty()) {
performSearch(keyword);
}
return true;
}
return false;
});
// 实时搜索建议(可选,暂时注释掉以避免频繁请求)
/*
binding.searchInput.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 keyword = s != null ? s.toString().trim() : "";
if (!keyword.isEmpty() && keyword.length() >= 2) {
loadSearchSuggestions(keyword);
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
*/
}
/**
* 执行搜索
*/
private void performSearch(String keyword) {
if (keyword == null || keyword.trim().isEmpty()) {
return;
}
keyword = keyword.trim();
// 避免重复搜索
if (keyword.equals(lastSearchKeyword) && isSearching) {
return;
}
lastSearchKeyword = keyword;
isSearching = true;
Log.d(TAG, "执行搜索: " + keyword);
ApiService apiService = ApiClient.getService(this);
Call<ApiResponse<PageResponse<Map<String, Object>>>> call =
apiService.searchLiveRooms(keyword, null, null, 1, 20);
call.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) {
isSearching = false;
if (response.isSuccessful() && response.body() != null) {
ApiResponse<PageResponse<Map<String, Object>>> apiResponse = response.body();
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
PageResponse<Map<String, Object>> pageResponse = apiResponse.getData();
List<Map<String, Object>> rooms = pageResponse.getList();
if (rooms != null && !rooms.isEmpty()) {
List<Room> roomList = new ArrayList<>();
for (Map<String, Object> roomData : rooms) {
Room room = parseRoomFromMap(roomData);
if (room != null) {
roomList.add(room);
}
}
all.clear();
all.addAll(roomList);
adapter.submitList(new ArrayList<>(all));
updateEmptyState(all);
Log.d(TAG, "搜索成功,找到 " + roomList.size() + " 个直播间");
} else {
all.clear();
adapter.submitList(new ArrayList<>());
updateEmptyState(all);
Log.d(TAG, "搜索结果为空");
}
} else {
Toast.makeText(SearchActivity.this,
"搜索失败: " + apiResponse.getMessage(),
Toast.LENGTH_SHORT).show();
Log.e(TAG, "搜索失败: " + apiResponse.getMessage());
}
} else {
Toast.makeText(SearchActivity.this,
"搜索失败,请稍后重试",
Toast.LENGTH_SHORT).show();
Log.e(TAG, "搜索请求失败: " + response.code());
}
}
@Override
public void onFailure(Call<ApiResponse<PageResponse<Map<String, Object>>>> call, Throwable t) {
isSearching = false;
Toast.makeText(SearchActivity.this,
"网络错误: " + t.getMessage(),
Toast.LENGTH_SHORT).show();
Log.e(TAG, "搜索网络错误", t);
}
});
}
/**
* 从Map解析Room对象
*/
private Room parseRoomFromMap(Map<String, Object> data) {
try {
Object idObj = data.get("id");
String id = idObj != null ? String.valueOf(idObj) : null;
String title = (String) data.get("title");
String streamerName = (String) data.get("streamerName");
Object isLiveObj = data.get("isLive");
boolean isLive = false;
if (isLiveObj instanceof Boolean) {
isLive = (Boolean) isLiveObj;
} else if (isLiveObj instanceof Number) {
isLive = ((Number) isLiveObj).intValue() == 1;
}
String coverUrl = (String) data.get("coverUrl");
Room room = new Room(id, title, streamerName, isLive);
if (coverUrl != null) {
room.setCoverUrl(coverUrl);
}
return room;
} catch (Exception e) {
Log.e(TAG, "解析Room数据失败", e);
return null;
}
}
/**
* 加载热门搜索
*/
private void loadHotSearch() {
Log.d(TAG, "加载热门搜索");
ApiService apiService = ApiClient.getService(this);
Call<ApiResponse<List<HotSearchResponse>>> call = apiService.getHotSearch(2, 10); // 2-直播间
call.enqueue(new Callback<ApiResponse<List<HotSearchResponse>>>() {
@Override
public void onResponse(Call<ApiResponse<List<HotSearchResponse>>> call,
Response<ApiResponse<List<HotSearchResponse>>> response) {
if (response.isSuccessful() && response.body() != null) {
ApiResponse<List<HotSearchResponse>> apiResponse = response.body();
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
List<HotSearchResponse> hotSearchList = apiResponse.getData();
Log.d(TAG, "热门搜索加载成功: " + hotSearchList.size() + "");
// TODO: 可以在UI上显示热门搜索标签
}
}
}
@Override
public void onFailure(Call<ApiResponse<List<HotSearchResponse>>> call, Throwable t) {
Log.e(TAG, "加载热门搜索失败", t);
}
});
}
/**
* 加载搜索建议(可选功能)
*/
private void loadSearchSuggestions(String keyword) {
ApiService apiService = ApiClient.getService(this);
Call<ApiResponse<List<String>>> call = apiService.getSearchSuggestions(keyword, 2, 10); // 2-直播间
call.enqueue(new Callback<ApiResponse<List<String>>>() {
@Override
public void onResponse(Call<ApiResponse<List<String>>> call,
Response<ApiResponse<List<String>>> response) {
if (response.isSuccessful() && response.body() != null) {
ApiResponse<List<String>> apiResponse = response.body();
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
List<String> suggestions = apiResponse.getData();
Log.d(TAG, "搜索建议: " + suggestions.size() + "");
// TODO: 可以在UI上显示搜索建议下拉列表
}
}
}
@Override
public void onFailure(Call<ApiResponse<List<String>>> call, Throwable t) {
Log.e(TAG, "加载搜索建议失败", t);
}
});
}
private void applyFilter(String q) {
// TODO: 接入后端接口 - 实时搜索建议(当用户输入时)
// 接口路径: GET /api/search/suggestions
// 请求参数:
// - keyword: 搜索关键词(必填)
// - limit (可选): 返回数量限制默认10
// 返回数据格式: ApiResponse<List<SearchSuggestion>>
// SearchSuggestion对象应包含: type (room/user), id, title, subtitle, avatarUrl等字段
// 用于在用户输入时显示搜索建议,提升搜索体验
// TODO: 接入后端接口 - 搜索功能(当用户提交搜索时)
// 接口路径: GET /api/search
// 请求参数:
// - keyword: 搜索关键词(必填)
// - type (可选): 搜索类型room/user/all默认all
// - page (可选): 页码
// - pageSize (可选): 每页数量
// 返回数据格式: ApiResponse<{rooms: Room[], users: User[], total: number}>
// 搜索逻辑应迁移到后端,前端只负责展示结果
String query = q != null ? q.trim() : "";
if (query.isEmpty()) {
adapter.submitList(new ArrayList<>(all));
updateEmptyState(all);
return;
}
List<Room> filtered = new ArrayList<>();
for (Room r : all) {
if (r == null) continue;
String title = r.getTitle() != null ? r.getTitle() : "";
String streamer = r.getStreamerName() != null ? r.getStreamerName() : "";
if (title.contains(query) || streamer.contains(query)) {
filtered.add(r);
}
}
adapter.submitList(filtered);
updateEmptyState(filtered);
}
/**
* 更新空状态显示
*/
private void updateEmptyState(List<Room> rooms) {
if (rooms == null || rooms.isEmpty()) {
if (binding.emptyStateView != null) {
binding.emptyStateView.setNoSearchResultsState();
binding.emptyStateView.setVisibility(View.VISIBLE);
}
} else {
if (binding.emptyStateView != null) {
binding.emptyStateView.setVisibility(View.GONE);
}
}
}
}