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) putExtra("history_episode", playHistory.episodeName) } 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 container = android.widget.LinearLayout(requireContext()).apply { orientation = android.widget.LinearLayout.VERTICAL setPadding(12, 12, 12, 12) layoutParams = android.widget.LinearLayout.LayoutParams( 200, // 固定宽度 200dp android.widget.LinearLayout.LayoutParams.WRAP_CONTENT ).apply { marginEnd = 16 } background = android.graphics.drawable.GradientDrawable().apply { setColor(android.graphics.Color.parseColor("#2D2D2D")) cornerRadius = 12f setStroke(2, android.graphics.Color.parseColor("#3D3D3D")) } } // 创建封面图片区域 val coverView = android.widget.ImageView(requireContext()).apply { layoutParams = android.widget.LinearLayout.LayoutParams( android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 100 // 固定高度 100dp ).apply { bottomMargin = 8 } scaleType = android.widget.ImageView.ScaleType.CENTER_CROP setBackgroundColor(android.graphics.Color.parseColor("#1E1E1E")) // 加载封面图 if (!item.coverUrl.isNullOrEmpty()) { com.bumptech.glide.Glide.with(this) .load(item.coverUrl) .placeholder(android.R.color.darker_gray) .error(android.R.color.darker_gray) .into(this) } } // 创建文字显示区域 val titleText = TextView(requireContext()).apply { text = item.title setTextColor(ContextCompat.getColor(requireContext(), R.color.text_primary)) textSize = 13f setPadding(4, 4, 4, 2) maxLines = 1 ellipsize = android.text.TextUtils.TruncateAt.END } // 创建剧集文字显示 val episodeText = TextView(requireContext()).apply { text = item.episodeName ?: "" setTextColor(ContextCompat.getColor(requireContext(), R.color.text_secondary)) textSize = 11f setPadding(4, 2, 4, 4) maxLines = 1 ellipsize = android.text.TextUtils.TruncateAt.END visibility = if (item.episodeName.isNullOrEmpty()) View.GONE else View.VISIBLE } // 添加到容器 container.addView(coverView) container.addView(titleText) container.addView(episodeText) // 设置点击事件 container.setOnClickListener { openPlayerFromHistory(item) } // 设置焦点效果 container.isFocusable = true container.isFocusableInTouchMode = true container.setOnFocusChangeListener { v, hasFocus -> if (hasFocus) { v.background = android.graphics.drawable.GradientDrawable().apply { setColor(android.graphics.Color.parseColor("#3D3D3D")) cornerRadius = 12f setStroke(3, android.graphics.Color.parseColor("#1A73E8")) } v.animate().scaleX(1.05f).scaleY(1.05f).setDuration(150).start() } else { v.background = android.graphics.drawable.GradientDrawable().apply { setColor(android.graphics.Color.parseColor("#2D2D2D")) cornerRadius = 12f setStroke(2, android.graphics.Color.parseColor("#3D3D3D")) } v.animate().scaleX(1.0f).scaleY(1.0f).setDuration(150).start() } } playHistoryList.addView(container) } } override fun onDestroyView() { super.onDestroyView() fallbackWebView.destroy() } }