Compare commits

...
10 Commits
Author SHA1 Message Date
Li ZongYing 1fd0951f92 channel reversal 2024-01-16 19:04:43 +08:00
Li ZongYing 42b072f5ec support KEYCODE_CHANNEL_UP & KEYCODE_CHANNEL_DOWN 2024-01-14 12:09:12 +08:00
Li ZongYing a23b1e11ce fix the menu did not appear 2024-01-14 10:15:45 +08:00
Li ZongYing 1ac6d87c3c clean log 2024-01-12 10:13:29 +08:00
Li ZongYing b075a7db81 trust all certs 2024-01-11 19:33:31 +08:00
Li ZongYing 7821e8fa58 optimize 2024-01-11 15:19:57 +08:00
Li ZongYing 3109a61bcf action sign 2024-01-10 23:19:21 +08:00
Li ZongYing ce6e5b6546 action sign 2024-01-10 23:18:16 +08:00
Li ZongYing 230e4fea2b action sign 2024-01-10 23:14:49 +08:00
Li ZongYing 0d26912321 action sign 2024-01-10 23:12:35 +08:00
16 changed files with 326 additions and 146 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
keyPassword: ${{ secrets.ALIAS_PASSWORD }} keyPassword: ${{ secrets.ALIAS_PASSWORD }}
env: env:
# override default build-tools version (29.0.3) -- optional # override default build-tools version (29.0.3) -- optional
BUILD_TOOLS_VERSION: "30.0.2" BUILD_TOOLS_VERSION: "34.0.0"
- name: Create Release - name: Create Release
id: create_release id: create_release
+6 -3
View File
@@ -100,10 +100,13 @@ dependencies {
// For HLS playback support with ExoPlayer // For HLS playback support with ExoPlayer
implementation "androidx.media3:media3-exoplayer-hls:$media3_version" implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
// 21:2.9.0 17:2.6.4
def retrofit2_version = "2.9.0"
implementation 'com.google.protobuf:protobuf-kotlin:3.25.1' implementation 'com.google.protobuf:protobuf-kotlin:3.25.1'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
implementation 'com.squareup.retrofit2:converter-protobuf:2.9.0' implementation "com.squareup.retrofit2:converter-protobuf:$retrofit2_version"
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
implementation 'androidx.core:core-ktx:1.11.0-beta02' implementation 'androidx.core:core-ktx:1.11.0-beta02'
implementation 'androidx.leanback:leanback:1.2.0-alpha02' implementation 'androidx.leanback:leanback:1.2.0-alpha02'
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,7 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.app.AlertDialog import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.Signature import android.content.pm.Signature
@@ -15,11 +16,7 @@ import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
import android.view.WindowManager import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import java.security.MessageDigest import java.security.MessageDigest
@@ -36,7 +33,10 @@ class MainActivity : FragmentActivity() {
private lateinit var gestureDetector: GestureDetector private lateinit var gestureDetector: GestureDetector
private val handler = Handler() private val handler = Handler()
private val delay: Long = 3000 private val delay: Long = 4000
private lateinit var sharedPref: SharedPreferences
private var channelReversal = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -56,6 +56,9 @@ class MainActivity : FragmentActivity() {
mainFragment.view?.requestFocus() mainFragment.view?.requestFocus()
} }
gestureDetector = GestureDetector(this, GestureListener()) gestureDetector = GestureDetector(this, GestureListener())
sharedPref = getPreferences(Context.MODE_PRIVATE)
channelReversal = sharedPref.getBoolean(CHANNEL_REVERSAL, false)
} }
fun showInfoFragment(tvViewModel: TVViewModel) { fun showInfoFragment(tvViewModel: TVViewModel) {
@@ -63,7 +66,6 @@ class MainActivity : FragmentActivity() {
} }
fun play(tvViewModel: TVViewModel) { fun play(tvViewModel: TVViewModel) {
Log.i(TAG, "play: ${tvViewModel.getTV()}")
playerFragment.play(tvViewModel) playerFragment.play(tvViewModel)
mainFragment.view?.requestFocus() mainFragment.view?.requestFocus()
} }
@@ -170,88 +172,135 @@ class MainActivity : FragmentActivity() {
} }
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { fun saveChannelReversal(channelReversal: Boolean) {
when (keyCode) { with(sharedPref.edit()) {
KeyEvent.KEYCODE_BACK -> { putBoolean(CHANNEL_REVERSAL, channelReversal)
if (!mainFragmentIsHidden()) { apply()
hideMainFragment() }
return true this.channelReversal = channelReversal
} }
if (doubleBackToExitPressedOnce) { private fun showHelp() {
super.onBackPressed() if (!mainFragment.isHidden) {
return true return
} }
this.doubleBackToExitPressedOnce = true val versionName = getPackageInfo().versionName
Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show() val dialogFragment = MyDialogFragment(versionName, channelReversal)
dialogFragment.show(supportFragmentManager, "settings_dialog")
}
Handler(Looper.getMainLooper()).postDelayed({ private fun channelUp() {
doubleBackToExitPressedOnce = false if (mainFragment.isHidden) {
}, 2000) if (channelReversal) {
return true next()
return
} }
prev()
KeyEvent.KEYCODE_MENU -> { } else {
val versionName = getPackageInfo().versionName
val textView = TextView(this)
textView.text =
"当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
val imageView = ImageView(this)
val drawable = ContextCompat.getDrawable(this, R.drawable.appreciate)
imageView.setImageDrawable(drawable)
val linearLayout = LinearLayout(this)
linearLayout.orientation = LinearLayout.VERTICAL
linearLayout.addView(textView)
linearLayout.addView(imageView)
val layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
imageView.layoutParams = layoutParams
textView.layoutParams = layoutParams
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder
.setView(linearLayout)
val dialog: AlertDialog = builder.create()
dialog.show()
return true
}
KeyEvent.KEYCODE_DPAD_CENTER -> {
Log.i(TAG, "KEYCODE_DPAD_CENTER")
switchMainFragment()
}
KeyEvent.KEYCODE_DPAD_UP -> {
if (mainFragment.isHidden) {
prev()
} else {
// if (mainFragment.selectedPosition == 0) { // if (mainFragment.selectedPosition == 0) {
// mainFragment.setSelectedPosition( // mainFragment.setSelectedPosition(
// mainFragment.tvListViewModel.maxNum.size - 1, // mainFragment.tvListViewModel.maxNum.size - 1,
// false // false
// ) // )
// } // }
} }
} }
KeyEvent.KEYCODE_DPAD_DOWN -> { private fun channelDown() {
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
next() if (channelReversal) {
} else { prev()
return
}
next()
} else {
// if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) { // if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) {
//// mainFragment.setSelectedPosition(0, false) //// mainFragment.setSelectedPosition(0, false)
// hideMainFragment() // hideMainFragment()
// return false // return false
// } // }
} }
}
private fun back() {
if (!mainFragmentIsHidden()) {
hideMainFragment()
return
}
if (doubleBackToExitPressedOnce) {
super.onBackPressed()
return
}
doubleBackToExitPressedOnce = true
Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show()
Handler(Looper.getMainLooper()).postDelayed({
doubleBackToExitPressedOnce = false
}, 2000)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_ESCAPE -> {
back()
return true
}
KeyEvent.KEYCODE_BACK -> {
back()
return true
}
KeyEvent.KEYCODE_BOOKMARK -> {
showHelp()
return true
}
KeyEvent.KEYCODE_UNKNOWN -> {
showHelp()
return true
}
KeyEvent.KEYCODE_HELP -> {
showHelp()
return true
}
KeyEvent.KEYCODE_SETTINGS -> {
showHelp()
return true
}
KeyEvent.KEYCODE_MENU -> {
showHelp()
return true
}
KeyEvent.KEYCODE_ENTER -> {
switchMainFragment()
}
KeyEvent.KEYCODE_DPAD_CENTER -> {
switchMainFragment()
}
KeyEvent.KEYCODE_DPAD_UP -> {
channelUp()
}
KeyEvent.KEYCODE_CHANNEL_UP -> {
channelUp()
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
channelDown()
}
KeyEvent.KEYCODE_CHANNEL_DOWN -> {
channelDown()
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
@@ -336,5 +385,6 @@ class MainActivity : FragmentActivity() {
companion object { companion object {
private const val TAG = "MainActivity" private const val TAG = "MainActivity"
private const val CHANNEL_REVERSAL = "channel_reversal"
} }
} }
@@ -209,8 +209,8 @@ class MainFragment : BrowseSupportFragment() {
itemPosition = sharedPref?.getInt("position", 0)!! itemPosition = sharedPref?.getInt("position", 0)!!
if (itemPosition >= tvListViewModel.size()) { if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0 itemPosition = 0
tvListViewModel.setItemPosition(itemPosition)
} }
tvListViewModel.setItemPosition(itemPosition)
} }
fun fragmentReady() { fun fragmentReady() {
@@ -0,0 +1,44 @@
package com.lizongying.mytv
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.lizongying.mytv.databinding.DialogBinding
class MyDialogFragment(private val versionName: String, private val channelReversal: Boolean) :
DialogFragment() {
private var _binding: DialogBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogBinding.inflate(inflater, container, false)
_binding?.version?.text =
"当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
val switchView = _binding?.switchView
switchView?.isChecked = channelReversal
switchView?.setOnCheckedChangeListener { _, isChecked ->
(activity as MainActivity).saveChannelReversal(isChecked)
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
const val TAG = "MyDialogFragment"
}
}
@@ -24,6 +24,7 @@ class PlayerFragment : Fragment() {
private var _binding: PlayerBinding? = null private var _binding: PlayerBinding? = null
private var playerView: PlayerView? = null private var playerView: PlayerView? = null
private var tvViewModel: TVViewModel? = null private var tvViewModel: TVViewModel? = null
private val aspectRatio = 16f / 9f
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@@ -43,10 +44,9 @@ class PlayerFragment : Fragment() {
playerView!!.player?.playWhenReady = true playerView!!.player?.playWhenReady = true
playerView!!.player?.addListener(object : Player.Listener { playerView!!.player?.addListener(object : Player.Listener {
override fun onVideoSizeChanged(videoSize: VideoSize) { override fun onVideoSizeChanged(videoSize: VideoSize) {
val aspectRatio = 16f / 9f
val layoutParams = playerView?.layoutParams
val ratio = playerView?.measuredWidth?.div(playerView?.measuredHeight!!) val ratio = playerView?.measuredWidth?.div(playerView?.measuredHeight!!)
if (ratio != null) { if (ratio != null) {
val layoutParams = playerView?.layoutParams
if (ratio < aspectRatio) { if (ratio < aspectRatio) {
layoutParams?.height = layoutParams?.height =
(playerView?.measuredWidth?.div(aspectRatio))?.toInt() (playerView?.measuredWidth?.div(aspectRatio))?.toInt()
@@ -87,7 +87,10 @@ class Request {
ysp = YSP(context) ysp = YSP(context)
} }
var call: Call<LiveInfo>? = null
fun fetchVideo(tvModel: TVViewModel, cookie: String) { fun fetchVideo(tvModel: TVViewModel, cookie: String) {
call?.cancel()
if (::myRunnable.isInitialized) { if (::myRunnable.isInitialized) {
handler.removeCallbacks(myRunnable) handler.removeCallbacks(myRunnable)
} }
@@ -97,71 +100,71 @@ class Request {
tvModel.seq = 0 tvModel.seq = 0
val data = ysp?.switch(tvModel) val data = ysp?.switch(tvModel)
val request = data?.let { LiveInfoRequest(it) } val request = data?.let { LiveInfoRequest(it) }
call = request?.let { yspApiService.getLiveInfo("guid=${ysp?.getGuid()}; $cookie", it) }
request?.let { yspApiService.getLiveInfo(cookie, it) } call?.enqueue(object : Callback<LiveInfo> {
?.enqueue(object : Callback<LiveInfo> { override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) { if (response.isSuccessful) {
if (response.isSuccessful) { val liveInfo = response.body()
val liveInfo = response.body() if (liveInfo?.data?.playurl != null) {
if (liveInfo?.data?.playurl != null) { val chanll = liveInfo.data.chanll
val chanll = liveInfo.data.chanll val decodedBytes = Base64.decode(
val decodedBytes = Base64.decode( chanll.substring(9, chanll.length - 3),
chanll.substring(9, chanll.length - 3), Base64.DEFAULT
Base64.DEFAULT )
) val decodedString = String(decodedBytes)
val decodedString = String(decodedBytes) val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""")
val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""") val matchResult = regex.find(decodedString)
val matchResult = regex.find(decodedString) if (matchResult != null) {
if (matchResult != null) { val (key, iv) = matchResult.destructured
val (key, iv) = matchResult.destructured val keyBytes = Base64.decode(key, Base64.DEFAULT)
val keyBytes = Base64.decode(key, Base64.DEFAULT) val ivBytes = Base64.decode(iv, Base64.DEFAULT)
val ivBytes = Base64.decode(iv, Base64.DEFAULT) val url = liveInfo.data.playurl + "&revoi=" + encryptTripleDES(
val url = liveInfo.data.playurl + "&revoi=" + encryptTripleDES( keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0),
keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0), ivBytes
ivBytes ).uppercase()
).uppercase() Log.i(TAG, "$title url $url")
Log.i(TAG, "$title url $url") tvModel.addVideoUrl(url)
tvModel.addVideoUrl(url) tvModel.allReady()
tvModel.allReady() tvModel.retryTimes = 0
tvModel.retryTimes = 0 myRunnable = MyRunnable(tvModel)
myRunnable = MyRunnable(tvModel) handler.post(myRunnable)
handler.post(myRunnable)
} else {
Log.e(TAG, "$title key error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
}
}
} else { } else {
if (liveInfo?.data?.errinfo != null && liveInfo.data.errinfo == "应版权方要求,暂停提供直播信号,请点击观看其他精彩节目") { Log.e(TAG, "$title key error")
Log.e(TAG, "$title error ${liveInfo.data.errinfo}") if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.setErrInfo(liveInfo.data.errinfo) tvModel.retryTimes++
} else { fetchVideo(tvModel, cookie)
Log.e(TAG, "$title url error $request $liveInfo")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
}
} }
} }
} else { } else {
Log.e(TAG, "$title status error") if (liveInfo?.data?.errinfo != null && liveInfo.data.errinfo == "应版权方要求,暂停提供直播信号,请点击观看其他精彩节目") {
if (tvModel.retryTimes < tvModel.retryMaxTimes) { Log.e(TAG, "$title error ${liveInfo.data.errinfo}")
tvModel.retryTimes++ tvModel.setErrInfo(liveInfo.data.errinfo)
fetchVideo(tvModel, cookie) } else {
Log.e(TAG, "$title url error $request $liveInfo")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
}
} }
} }
} } else {
Log.e(TAG, "$title status error")
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "$title request error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) { if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++ tvModel.retryTimes++
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} }
} }
}) }
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "$title request error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
fetchVideo(tvModel, cookie)
}
}
})
} }
fun fetchVideo(tvModel: TVViewModel) { fun fetchVideo(tvModel: TVViewModel) {
@@ -169,11 +172,10 @@ class Request {
.enqueue(object : Callback<Info> { .enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) { override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) { if (response.isSuccessful) {
val info = response.body() val token = response.body()?.data?.token
val token = info?.data?.token
Log.i(TAG, "info success $token") Log.i(TAG, "info success $token")
val cookie = val cookie =
"guid=1; vplatform=109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" "vplatform=109; yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token"
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} else { } else {
Log.e(TAG, "info status error") Log.e(TAG, "info status error")
@@ -198,7 +200,7 @@ class Request {
if (tvModel.needToken) { if (tvModel.needToken) {
fetchVideo(tvModel) fetchVideo(tvModel)
} else { } else {
val cookie = "guid=1; vplatform=109" val cookie = "vplatform=109"
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} }
} }
@@ -5,6 +5,9 @@ import okhttp3.OkHttpClient
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.protobuf.ProtoConverterFactory import retrofit2.converter.protobuf.ProtoConverterFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
class ApiClient { class ApiClient {
@@ -13,9 +16,7 @@ class ApiClient {
private val protoUrl = "https://capi.yangshipin.cn/" private val protoUrl = "https://capi.yangshipin.cn/"
private val traceUrl = "https://btrace.yangshipin.cn/" private val traceUrl = "https://btrace.yangshipin.cn/"
private var okHttpClient = OkHttpClient.Builder() private var okHttpClient = getUnsafeOkHttpClient()
.dns(DnsCache())
.build()
val yspApiService: YSPApiService by lazy { val yspApiService: YSPApiService by lazy {
Retrofit.Builder() Retrofit.Builder()
@@ -48,4 +49,40 @@ class ApiClient {
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.build().create(YSPBtraceService::class.java) .build().create(YSPBtraceService::class.java)
} }
private fun getUnsafeOkHttpClient(): OkHttpClient {
try {
val trustAllCerts: Array<TrustManager> = arrayOf(
object : X509TrustManager {
override fun checkClientTrusted(
chain: Array<out java.security.cert.X509Certificate>?,
authType: String?
) {
}
override fun checkServerTrusted(
chain: Array<out java.security.cert.X509Certificate>?,
authType: String?
) {
}
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> {
return emptyArray()
}
}
)
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
return OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }
.dns(DnsCache())
.build()
} catch (e: Exception) {
throw RuntimeException(e)
}
}
} }
@@ -71,7 +71,10 @@ class YSP(var context: Context) {
randStr = getRand() randStr = getRand()
guid = newGuid() if (tvModel.retryTimes > 0) {
guid = newGuid()
}
timeStr = getTimeStr() timeStr = getTimeStr()
// guid = "lq3oqitm_1e15dnzgjnb" // guid = "lq3oqitm_1e15dnzgjnb"
@@ -107,7 +110,7 @@ class YSP(var context: Context) {
return guid return guid
} }
fun newGuid(): String { private fun newGuid(): String {
guid = generateGuid() guid = generateGuid()
with(sharedPref!!.edit()) { with(sharedPref!!.edit()) {
putString("guid", guid) putString("guid", guid)
@@ -1,6 +1,5 @@
package com.lizongying.mytv.models package com.lizongying.mytv.models
import android.content.SharedPreferences
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
+44
View File
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="300dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="20dp"
android:paddingLeft="7dp"
android:paddingRight="7dp"
android:background="#FF263238"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textSize="16sp" />
<TextView
android:id="@+id/version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
></TextView>
<Switch
android:id="@+id/switch_view"
android:text="@string/title_channel_reversal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView
android:layout_width="300dp"
android:layout_height="wrap_content"
android:src="@drawable/appreciate"
android:background="@color/white" />
</LinearLayout>
</FrameLayout>
-1
View File
@@ -12,7 +12,6 @@
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:contentDescription="@string/logo"
android:padding="10dp" /> android:padding="10dp" />
<LinearLayout <LinearLayout
+1 -1
View File
@@ -1,4 +1,4 @@
<resources> <resources>
<string name="app_name">我的电视</string> <string name="app_name">我的电视</string>
<string name="logo">logo</string> <string name="title_channel_reversal">频道反转</string>
</resources> </resources>
-1
View File
@@ -1,6 +1,5 @@
<resources> <resources>
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle"> <style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<item name="lbImageCardViewType">Title|Content</item> <item name="lbImageCardViewType">Title|Content</item>
</style> </style>