feat: 新增tvcat.cc视频来源, 设置页面支持多来源切换

This commit is contained in:
xiaji
2026-06-08 21:01:00 +08:00
parent 1f363e74fa
commit cb238a6f36
6 changed files with 146 additions and 4 deletions

View File

@@ -1,16 +1,22 @@
package com.videoapp.tv package com.videoapp.tv
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.Spinner
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.videoapp.tv.data.ConfigRepository import com.videoapp.tv.data.ConfigRepository
import com.videoapp.tv.data.SiteConfig import com.videoapp.tv.data.SiteConfig
import com.videoapp.tv.data.SitePreset
class SettingsActivity : AppCompatActivity() { class SettingsActivity : AppCompatActivity() {
private lateinit var configRepo: ConfigRepository private lateinit var configRepo: ConfigRepository
private lateinit var spinnerPreset: Spinner
private lateinit var editBaseUrl: EditText private lateinit var editBaseUrl: EditText
private lateinit var editSearchPath: EditText private lateinit var editSearchPath: EditText
private lateinit var editSearchMethod: EditText private lateinit var editSearchMethod: EditText
@@ -24,6 +30,7 @@ class SettingsActivity : AppCompatActivity() {
private lateinit var editSourceEpisodeGroupSelector: EditText private lateinit var editSourceEpisodeGroupSelector: EditText
private lateinit var btnSave: Button private lateinit var btnSave: Button
private lateinit var btnRestore: Button private lateinit var btnRestore: Button
private var ignoringSpinner = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -31,6 +38,7 @@ class SettingsActivity : AppCompatActivity() {
configRepo = ConfigRepository(this) configRepo = ConfigRepository(this)
spinnerPreset = findViewById(R.id.spinner_preset)
editBaseUrl = findViewById(R.id.edit_base_url) editBaseUrl = findViewById(R.id.edit_base_url)
editSearchPath = findViewById(R.id.edit_search_path) editSearchPath = findViewById(R.id.edit_search_path)
editSearchMethod = findViewById(R.id.edit_search_method) editSearchMethod = findViewById(R.id.edit_search_method)
@@ -45,18 +53,49 @@ class SettingsActivity : AppCompatActivity() {
btnSave = findViewById(R.id.btn_save) btnSave = findViewById(R.id.btn_save)
btnRestore = findViewById(R.id.btn_restore) btnRestore = findViewById(R.id.btn_restore)
setupPresetSpinner()
loadConfig() loadConfig()
btnSave.setOnClickListener { saveConfig() } btnSave.setOnClickListener { saveConfig() }
btnRestore.setOnClickListener { btnRestore.setOnClickListener {
configRepo.restoreDefault() configRepo.restoreDefault()
ignoringSpinner = true
spinnerPreset.setSelection(0)
ignoringSpinner = false
loadConfig() loadConfig()
Toast.makeText(this, R.string.config_restored, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.config_restored, Toast.LENGTH_SHORT).show()
} }
} }
private fun loadConfig() { private fun setupPresetSpinner() {
val config = configRepo.getConfig() val presets = configRepo.getPresets()
val presetNames = presets.map { it.name }
val adapter = ArrayAdapter(
this,
android.R.layout.simple_spinner_item,
presetNames
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinnerPreset.adapter = adapter
spinnerPreset.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (ignoringSpinner) return
val preset = presets.getOrNull(position) ?: return
configRepo.applyPreset(preset)
loadConfigFromPreset(preset.config)
Toast.makeText(
this@SettingsActivity,
"已切换到 ${preset.name}",
Toast.LENGTH_SHORT
).show()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
}
private fun loadConfigFromPreset(config: SiteConfig) {
editBaseUrl.setText(config.baseUrl) editBaseUrl.setText(config.baseUrl)
editSearchPath.setText(config.searchPath) editSearchPath.setText(config.searchPath)
editSearchMethod.setText(config.searchMethod) editSearchMethod.setText(config.searchMethod)
@@ -72,6 +111,21 @@ class SettingsActivity : AppCompatActivity() {
editSourceEpisodeGroupSelector.setText(config.sourceEpisodeGroupSelector) editSourceEpisodeGroupSelector.setText(config.sourceEpisodeGroupSelector)
} }
private fun loadConfig() {
val config = configRepo.getConfig()
loadConfigFromPreset(config)
val presets = configRepo.getPresets()
val matchedIndex = presets.indexOfFirst {
it.config.baseUrl == config.baseUrl
}
if (matchedIndex >= 0) {
ignoringSpinner = true
spinnerPreset.setSelection(matchedIndex)
ignoringSpinner = false
}
}
private fun saveConfig() { private fun saveConfig() {
val extraParams = parseExtraParams(editExtraParams.text.toString()) val extraParams = parseExtraParams(editExtraParams.text.toString())

View File

@@ -30,6 +30,12 @@ class ConfigRepository(context: Context) {
saveConfig(SiteConfig.default()) saveConfig(SiteConfig.default())
} }
fun getPresets(): List<SitePreset> = SitePreset.PRESETS
fun applyPreset(preset: SitePreset) {
saveConfig(preset.config)
}
companion object { companion object {
private const val KEY_CONFIG = "site_config_json" private const val KEY_CONFIG = "site_config_json"
} }

View File

@@ -0,0 +1,61 @@
package com.videoapp.tv.data
data class SitePreset(
val name: String,
val config: SiteConfig
) {
companion object {
val PRESETS = listOf(
SitePreset(
name = "xb6v (星辰影视)",
config = SiteConfig(
baseUrl = "https://www.xb6v.com",
searchPath = "/e/search/11index.php",
searchMethod = "POST",
keywordParam = "keyboard",
extraParams = mapOf(
"show" to "title",
"tempid" to "1",
"tbname" to "article",
"mid" to "1",
"dopost" to "search"
),
resultSelector = "li.post",
titleSelector = "h2 a",
coverSelector = ".thumbnail img",
linkSelector = ".thumbnail a",
categorySelector = ".info_category a",
dateSelector = ".info_date",
episodeSelector = "a.lBtn",
sourceSelector = ".playfrom a, .play_source a, .source-list a",
sourceEpisodeGroupSelector = ".playlist > ul, .play_list > ul, .episode-list",
iframeSelector = ".video iframe",
videoSelector = "video source, video[src]"
)
),
SitePreset(
name = "tvcat (电视猫)",
config = SiteConfig(
baseUrl = "https://tvcat.cc",
searchPath = "/search",
searchMethod = "GET",
keywordParam = "q",
resultSelector = "li.col-md-2.col-sm-3.col-4",
titleSelector = "a[title]",
coverSelector = "img",
linkSelector = "a",
categorySelector = ".text-muted",
dateSelector = "",
episodeSelector = "li.list-inline-item a",
sourceSelector = "h2",
sourceEpisodeGroupSelector = "ul.list-unstyled",
iframeSelector = "iframe",
videoSelector = "video source, video[src]"
)
)
)
fun getPresetByName(name: String): SitePreset? =
PRESETS.find { it.name == name }
}
}

View File

@@ -185,7 +185,11 @@ class SearchFragment : Fragment() {
val path = config.searchPath.trimStart('/') val path = config.searchPath.trimStart('/')
val params = "${config.keywordParam}=${java.net.URLEncoder.encode(keyword, "UTF-8")}" val params = "${config.keywordParam}=${java.net.URLEncoder.encode(keyword, "UTF-8")}"
val extraParams = config.extraParams.entries.joinToString("&") { "${it.key}=${it.value}" } val extraParams = config.extraParams.entries.joinToString("&") { "${it.key}=${it.value}" }
val url = "$base/$path?$params&$extraParams" val url = if (extraParams.isNotEmpty()) {
"$base/$path?$params&$extraParams"
} else {
"$base/$path?$params"
}
fallbackWebView.visibility = View.VISIBLE fallbackWebView.visibility = View.VISIBLE
resultsGrid.visibility = View.GONE resultsGrid.visibility = View.GONE

View File

@@ -12,7 +12,23 @@
android:text="@string/site_config" android:text="@string/site_config"
android:textColor="@color/primary" android:textColor="@color/primary"
android:textSize="24sp" android:textSize="24sp"
android:layout_marginBottom="24dp" /> android:layout_marginBottom="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/source_preset"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<Spinner
android:id="@+id/spinner_preset"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/search_bg"
android:padding="12dp"
android:layout_marginBottom="24dp"
android:focusable="true" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -24,5 +24,6 @@
<string name="back">返回</string> <string name="back">返回</string>
<string name="config_saved">配置已保存</string> <string name="config_saved">配置已保存</string>
<string name="config_restored">配置已恢复默认</string> <string name="config_restored">配置已恢复默认</string>
<string name="source_preset">视频来源</string>
<string name="load_webpage">在网页中打开</string> <string name="load_webpage">在网页中打开</string>
</resources> </resources>