refactor: convert from Android TV to phone/tablet mode

- Replace Theme.Leanback with Theme.AppCompat.DayNight.NoActionBar
- Remove leanback dependencies (leanback, leanback-preference)
- Remove LEANBACK_LAUNCHER, leanback feature, banner from manifest
- PlayerActivity: replace D-pad with touch controls (click to toggle episodes, close button)
- SearchFragment: adaptive grid (3 cols phone / 5 cols tablet), remove focus-based history toggle
- Fix deprecated adapterPosition -> bindingAdapterPosition
This commit is contained in:
xiaji
2026-05-24 21:19:34 +08:00
parent 7dee3977de
commit 153b555d52
7 changed files with 34 additions and 54 deletions

View File

@@ -41,10 +41,6 @@ android {
}
dependencies {
// Leanback (TV UI)
implementation("androidx.leanback:leanback:1.0.0")
implementation("androidx.leanback:leanback-preference:1.0.0")
// AndroidX
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")

View File

@@ -7,9 +7,6 @@
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<application
android:allowBackup="true"
@@ -20,12 +17,10 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:banner="@drawable/tv_banner">
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
@@ -33,7 +28,6 @@
android:name=".PlayerActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:theme="@style/Theme.VideoSearchTV.Player"
android:screenOrientation="landscape"
android:exported="false" />
<activity

View File

@@ -1,11 +1,11 @@
package com.videoapp.tv
import android.os.Bundle
import android.view.KeyEvent
import android.view.View
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Button
import android.widget.ImageButton
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
@@ -15,10 +15,7 @@ import androidx.media3.ui.PlayerView
import com.videoapp.tv.data.ConfigRepository
import com.videoapp.tv.engine.Episode
import com.videoapp.tv.engine.VideoExtractor
import com.videoapp.tv.engine.WebViewPlayer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class PlayerActivity : AppCompatActivity() {
@@ -28,15 +25,14 @@ class PlayerActivity : AppCompatActivity() {
private lateinit var episodeList: android.widget.LinearLayout
private lateinit var loadingIndicator: View
private lateinit var errorText: android.widget.TextView
private lateinit var btnClose: ImageButton
private var exoPlayer: ExoPlayer? = null
private val videoExtractor = VideoExtractor()
private val configRepo by lazy { ConfigRepository(this) }
private val webViewPlayer by lazy { WebViewPlayer(this) }
private var episodes: List<Episode> = emptyList()
private var currentEpisodeIndex = 0
private var isPlayerReady = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -48,13 +44,16 @@ class PlayerActivity : AppCompatActivity() {
episodeList = findViewById(R.id.episode_list)
loadingIndicator = findViewById(R.id.loading_indicator)
errorText = findViewById(R.id.error_text)
btnClose = findViewById(R.id.btn_close)
val detailUrl = intent.getStringExtra("detail_url") ?: ""
val title = intent.getStringExtra("title") ?: ""
btnClose.setOnClickListener { finish() }
initExoPlayer()
loadVideos(detailUrl, title)
setupFullscreenListener()
setupTouchListeners()
}
private fun initExoPlayer() {
@@ -63,7 +62,7 @@ class PlayerActivity : AppCompatActivity() {
}
}
private fun loadVideos(detailUrl: String, title: String) {
private fun loadVideos(detailUrl: String, @Suppress("UNUSED_PARAMETER") title: String) {
showLoading(true)
val config = configRepo.getConfig()
@@ -118,13 +117,10 @@ class PlayerActivity : AppCompatActivity() {
val (directUrl, iframeUrl) = videoExtractor.extractFromPlayPage(ep.playUrl, config)
if (directUrl != null) {
// Play with ExoPlayer
playWithExoPlayer(directUrl)
} else if (iframeUrl != null) {
// Play iframe in WebView
playWithWebView(iframeUrl)
} else {
// Fallback: load play page in WebView
playWithWebView(ep.playUrl)
}
}
@@ -189,28 +185,15 @@ class PlayerActivity : AppCompatActivity() {
showLoading(false)
}
private fun setupFullscreenListener() {
// Toggle episode panel visibility on dpad up/down
private fun setupTouchListeners() {
var panelVisible = true
playerView.setOnClickListener {
panelVisible = !panelVisible
episodePanel.visibility = if (panelVisible) View.VISIBLE else View.GONE
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return when (keyCode) {
KeyEvent.KEYCODE_DPAD_UP -> {
episodePanel.visibility = View.VISIBLE
true
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
if (episodePanel.visibility == View.VISIBLE) {
episodePanel.visibility = View.GONE
}
true
}
else -> super.onKeyDown(keyCode, event)
playerWebView.setOnClickListener {
panelVisible = !panelVisible
episodePanel.visibility = if (panelVisible) View.VISIBLE else View.GONE
}
}

View File

@@ -77,7 +77,8 @@ class SearchFragment : Fragment() {
}
private fun setupResultsGrid() {
resultsGrid.layoutManager = GridLayoutManager(context, 4)
val spanCount = if (resources.configuration.screenWidthDp >= 600) 5 else 3
resultsGrid.layoutManager = GridLayoutManager(context, spanCount)
resultsGrid.adapter = adapter
}
@@ -107,12 +108,6 @@ class SearchFragment : Fragment() {
showHistory(emptyList())
}
}
searchInput.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
historyContainer.visibility = View.VISIBLE
}
}
}
private fun performSearch(keyword: String) {

View File

@@ -41,7 +41,7 @@ class SearchResultAdapter(
init {
itemView.setOnClickListener {
val pos = adapterPosition
val pos = bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
onItemClick(items[pos])
}

View File

@@ -18,7 +18,18 @@
android:layout_height="match_parent"
android:visibility="gone" />
<RelativeLayout
<ImageButton
android:id="@+id/btn_close"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="start|top"
android:layout_margin="16dp"
android:background="@drawable/button_bg"
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:scaleType="centerInside"
android:contentDescription="@string/back" />
<FrameLayout
android:id="@+id/episode_panel"
android:layout_width="match_parent"
android:layout_height="60dp"
@@ -42,7 +53,7 @@
android:paddingStart="16dp"
android:paddingEnd="16dp" />
</HorizontalScrollView>
</RelativeLayout>
</FrameLayout>
<ProgressBar
android:id="@+id/loading_indicator"

View File

@@ -1,15 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.VideoSearchTV" parent="Theme.Leanback">
<style name="Theme.VideoSearchTV" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:windowBackground">@color/background</item>
<item name="android:textColor">@color/text_primary</item>
<item name="android:colorPrimary">@color/primary</item>
<item name="android:colorAccent">@color/accent</item>
<item name="colorPrimary">@color/primary</item>
<item name="colorAccent">@color/accent</item>
<item name="android:statusBarColor">@color/background</item>
<item name="android:navigationBarColor">@color/background</item>
</style>
<style name="Theme.VideoSearchTV.Player" parent="Theme.Leanback">
<style name="Theme.VideoSearchTV.Player" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:windowFullscreen">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/black</item>
<item name="android:statusBarColor">@android:color/black</item>
<item name="android:navigationBarColor">@android:color/black</item>