364 lines
14 KiB
Java
364 lines
14 KiB
Java
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);
|
||
}
|
||
}
|
||
}
|
||
}
|