package com.videoapp.tv.ui import android.content.Intent import android.os.Bundle import android.view.KeyEvent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Button import android.widget.EditText import android.widget.TextView import android.widget.Toast import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.videoapp.tv.R import com.videoapp.tv.PlayerActivity import com.videoapp.tv.SettingsActivity import com.videoapp.tv.data.AppDatabase import com.videoapp.tv.data.PlayHistory import com.videoapp.tv.data.SearchHistory import com.videoapp.tv.data.SearchResult import com.videoapp.tv.engine.SearchCoordinator import kotlinx.coroutines.launch class SearchFragment : Fragment() { private lateinit var searchInput: EditText private lateinit var btnSearch: Button private lateinit var btnSettings: Button private lateinit var historyContainer: ViewGroup private lateinit var historyList: ViewGroup private lateinit var btnClearHistory: Button private lateinit var playHistoryContainer: ViewGroup private lateinit var playHistoryList: ViewGroup private lateinit var btnClearPlayHistory: Button private lateinit var emptyPlayHistory: TextView private lateinit var resultsGrid: RecyclerView private lateinit var loadingProgress: View private lateinit var statusText: TextView private lateinit var fallbackWebView: WebView private val searchCoordinator by lazy { SearchCoordinator(requireContext()) } private val historyDao by lazy { AppDatabase.getInstance(requireContext()).searchHistoryDao() } private val playHistoryDao by lazy { AppDatabase.getInstance(requireContext()).playHistoryDao() } private val adapter by lazy { SearchResultAdapter( onItemClick = { result -> openPlayer(result) }, onItemFocus = { view -> view.animate().scaleX(1.05f).scaleY(1.05f).setDuration(150).start() } ) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_search, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) searchInput = view.findViewById(R.id.search_input) btnSearch = view.findViewById(R.id.btn_search) btnSettings = view.findViewById(R.id.btn_settings) historyContainer = view.findViewById(R.id.history_container) historyList = view.findViewById(R.id.history_list) btnClearHistory = view.findViewById(R.id.btn_clear_history) playHistoryContainer = view.findViewById(R.id.play_history_container) playHistoryList = view.findViewById(R.id.play_history_list) btnClearPlayHistory = view.findViewById(R.id.btn_clear_play_history) emptyPlayHistory = view.findViewById(R.id.empty_play_history) resultsGrid = view.findViewById(R.id.results_grid) loadingProgress = view.findViewById(R.id.loading_progress) statusText = view.findViewById(R.id.status_text) fallbackWebView = view.findViewById(R.id.fallback_webview) setupResultsGrid() setupListeners() loadHistory() loadPlayHistory() } override fun onResume() { super.onResume() // 每次返回首页时刷新播放历史 loadPlayHistory() } private fun setupResultsGrid() { val spanCount = if (resources.configuration.screenWidthDp >= 600) 5 else 3 resultsGrid.layoutManager = GridLayoutManager(context, spanCount) resultsGrid.adapter = adapter } private fun setupListeners() { searchInput.setOnEditorActionListener { _, actionId, event -> if (actionId == EditorInfo.IME_ACTION_SEARCH || (event?.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN) ) { performSearch(searchInput.text.toString().trim()) true } else { false } } btnSearch.setOnClickListener { performSearch(searchInput.text.toString().trim()) } btnSettings.setOnClickListener { startActivity(Intent(requireContext(), SettingsActivity::class.java)) } btnClearHistory.setOnClickListener { lifecycleScope.launch { historyDao.clearAll() showHistory(emptyList()) } } btnClearPlayHistory.setOnClickListener { lifecycleScope.launch { playHistoryDao.clearAll() showPlayHistory(emptyList()) } } } private fun performSearch(keyword: String) { if (keyword.isEmpty()) return hideFallbackWebView() showLoading(true) statusText.visibility = View.GONE historyContainer.visibility = View.GONE playHistoryContainer.visibility = View.GONE lifecycleScope.launch { // Save to history val existing = historyDao.findByKeyword(keyword) if (existing != null) { historyDao.updateTime(existing.id, System.currentTimeMillis()) } else { historyDao.insert(SearchHistory(keyword = keyword)) } } lifecycleScope.launch { searchCoordinator.search( keyword = keyword, onResult = { results -> showLoading(false) adapter.setItems(results) if (results.isEmpty()) { showStatus("未找到结果") } }, onError = { error -> showLoading(false) if (error == "FALLBACK_WEBVIEW") { showFallbackWebView(keyword) } else { showStatus("搜索失败,请重试") Toast.makeText(requireContext(), error, Toast.LENGTH_LONG).show() } }, onFallbackToWebView = { showFallbackWebView(keyword) } ) } } private fun showFallbackWebView(keyword: String) { val config = searchCoordinator.getConfig() val base = config.baseUrl.trimEnd('/') val path = config.searchPath.trimStart('/') val params = "${config.keywordParam}=${java.net.URLEncoder.encode(keyword, "UTF-8")}" val extraParams = config.extraParams.entries.joinToString("&") { "${it.key}=${it.value}" } val url = "$base/$path?$params&$extraParams" fallbackWebView.visibility = View.VISIBLE resultsGrid.visibility = View.GONE fallbackWebView.settings.javaScriptEnabled = true fallbackWebView.webViewClient = WebViewClient() fallbackWebView.loadUrl(url) } private fun hideFallbackWebView() { fallbackWebView.visibility = View.GONE fallbackWebView.stopLoading() resultsGrid.visibility = View.VISIBLE } private fun showLoading(show: Boolean) { loadingProgress.visibility = if (show) View.VISIBLE else View.GONE } private fun showStatus(msg: String) { statusText.text = msg statusText.visibility = View.VISIBLE } private fun openPlayer(result: SearchResult) { val intent = Intent(requireContext(), PlayerActivity::class.java).apply { putExtra("detail_url", result.detailUrl) putExtra("title", result.title) putExtra("category", result.category) putExtra("cover_url", result.coverUrl) } startActivity(intent) } private fun openPlayerFromHistory(playHistory: PlayHistory) { val intent = Intent(requireContext(), PlayerActivity::class.java).apply { putExtra("detail_url", playHistory.detailUrl) putExtra("title", playHistory.title) putExtra("category", playHistory.category) putExtra("cover_url", playHistory.coverUrl) } startActivity(intent) } private fun loadHistory() { lifecycleScope.launch { historyDao.getRecentHistory().collect { list -> showHistory(list) } } } private fun loadPlayHistory() { lifecycleScope.launch { playHistoryDao.getRecentPlayHistory().collect { list -> showPlayHistory(list) } } } private fun showHistory(list: List) { historyList.removeAllViews() if (list.isEmpty()) { historyContainer.visibility = View.GONE return } for (item in list) { val chip = Button(requireContext()).apply { text = item.keyword setTextColor(ContextCompat.getColor(requireContext(), R.color.text_primary)) setBackgroundResource(R.drawable.history_chip_selector) textSize = 14f setPadding(24, 8, 24, 8) isFocusable = true isFocusableInTouchMode = true setOnClickListener { searchInput.setText(item.keyword) performSearch(item.keyword) } } historyList.addView(chip) } historyContainer.visibility = View.VISIBLE } private fun showPlayHistory(list: List) { playHistoryList.removeAllViews() if (list.isEmpty()) { // 显示空状态提示 emptyPlayHistory.visibility = View.VISIBLE btnClearPlayHistory.visibility = View.GONE playHistoryList.visibility = View.GONE return } // 有播放历史,隐藏空状态提示 emptyPlayHistory.visibility = View.GONE btnClearPlayHistory.visibility = View.VISIBLE playHistoryList.visibility = View.VISIBLE for (item in list) { val chip = Button(requireContext()).apply { // 显示视频名称和剧集名称 text = if (item.episodeName != null) { "${item.title} - ${item.episodeName}" } else { item.title } setTextColor(ContextCompat.getColor(requireContext(), R.color.text_primary)) setBackgroundResource(R.drawable.history_chip_selector) textSize = 14f setPadding(24, 12, 24, 12) isFocusable = true isFocusableInTouchMode = true setOnClickListener { openPlayerFromHistory(item) } } playHistoryList.addView(chip) } } override fun onDestroyView() { super.onDestroyView() fallbackWebView.destroy() } }