Compare commits

..
47 Commits
Author SHA1 Message Date
Li ZongYing fe92fa5853 optimize style 2024-04-25 22:36:37 +08:00
Li ZongYing 3d8ab65955 fix ysp 2024-04-23 19:41:30 +08:00
Li ZongYing 5485a686bc fix crash on 4.2 2024-04-19 20:13:43 +08:00
Li ZongYing 716efb5a83 change style 2024-04-19 19:26:06 +08:00
Li ZongYing ca82d5409f add time 2024-04-18 10:21:54 +08:00
Li ZongYing 3936629af8 add epg 2024-04-02 22:57:39 +08:00
Li ZongYing bb88e754a5 fix 368 2024-03-29 17:04:11 +08:00
Li ZongYing 49e96d18f4 fix version code 2024-03-17 09:38:46 +08:00
Li ZongYing 43343f8faa fix version code 2024-03-17 09:35:34 +08:00
Li ZongYing 0335f58a12 fix version name 2024-03-16 16:17:10 +08:00
Li ZongYing 9450de2c91 menu style 2024-03-16 13:14:14 +08:00
Li ZongYing d03de91c1e request optimize 2024-03-16 10:44:55 +08:00
Li ZongYing 7057e4a8b4 request optimize 2024-03-15 22:32:43 +08:00
Li ZongYing 188431a29f fix 4.0 crash 2024-03-14 22:08:28 +08:00
Li ZongYing 5c0152e36c optimize fetch token 2024-03-13 12:11:51 +08:00
Li ZongYing a63463559f phoenix retry 2024-03-13 11:31:37 +08:00
Li ZongYing d37ada1268 phoenix epg 2024-03-12 16:16:07 +08:00
Li ZongYing 00e6c8d66e cetv1 2024-03-07 19:37:03 +08:00
Li ZongYing 0ebc166acc bootStartup->false 2024-03-06 01:20:44 +08:00
Li ZongYing 2975a61e14 test RecyclerView alpha 2024-02-22 21:26:55 +08:00
Li ZongYing 86b9f5159a readme 2024-02-22 11:16:12 +08:00
Li ZongYing 10f261be8b fix save state 2024-02-21 21:57:05 +08:00
Li ZongYing 52d356817a fix some keys invalid 2024-02-21 19:26:11 +08:00
Li ZongYing 6ede7f76ef auto auth 2024-02-21 16:36:37 +08:00
Li ZongYing a4da01adf4 new memu style 2024-02-21 15:14:06 +08:00
Li ZongYing 8b33c7c94c optimize memu 2024-02-20 20:55:35 +08:00
Li ZongYing 84a6c0f78e menu delay disappears 2024-02-20 11:33:30 +08:00
Li ZongYing 1417bbf57b auto-startup 2024-02-19 15:57:39 +08:00
Li ZongYing ce678b8741 test release api 2024-02-11 10:14:23 +08:00
Li ZongYing 353dc0570f fix ysp 2024-02-09 22:36:48 +08:00
Li ZongYing d7a60531ab fix cctv6 2024-02-08 00:22:24 +08:00
Li ZongYing 764f2a1549 fix time error 2024-02-07 19:21:17 +08:00
Li ZongYing aa4d52681f fix ysp 2024-02-07 03:05:11 +08:00
Li ZongYing 8137515605 fix ysp err 2024-02-05 06:25:31 +08:00
Li ZongYing 6c16408b90 upgrade player 2024-02-04 22:23:28 +08:00
Li ZongYing 44ab3a6669 fix channel number did not disappear 2024-02-03 23:30:37 +08:00
Li ZongYing 917b245bbe readme 2024-02-02 23:34:39 +08:00
Li ZongYing dd4550cf4f replay on resume 2024-02-01 18:10:58 +08:00
Li ZongYing 4bdc625718 add channel 2024-01-26 18:30:31 +08:00
Li ZongYing cda19995b2 optimize channel switch 2024-01-25 15:12:47 +08:00
Li ZongYing 1eb2f257b5 change channel order 2024-01-25 11:04:06 +08:00
Li ZongYing 9feb0dc4bc channel num setting 2024-01-19 12:18:17 +08:00
Li ZongYing 46311036d6 number selection 2024-01-17 16:27:25 +08:00
Li ZongYing 1aade3a48d 17ok 2024-01-14 18:58:37 +08:00
Li ZongYing 2528cf33db test 17 2024-01-14 10:09:59 +08:00
Li ZongYing 4364f66ec5 17 2024-01-14 02:53:09 +08:00
Li ZongYing 49cd7b3af2 help page 2024-01-11 16:32:08 +08:00
49 changed files with 1072 additions and 513 deletions
+18 -1
View File
@@ -1,5 +1,23 @@
## 更新日志
### v1.8.4(通用)
* 解決部分情況下打開後黑屏问题
* 延长频道信息显示时间,增加信息显示长度
* 调整频道列表间距
* 其他样式优化
### v1.8.2(通用)
* 修复无法播放问题
### v1.8.0(通用)
* 修复返回键无法退出问题
* 设置中增加退出按钮
* 修改非电视上的样式
* 修复低版本闪退问题
### v1.7.8(通用)
* 修复播放过程中的卡顿问题
@@ -20,7 +38,6 @@
* 增加时间显示
* 频道号优化
* 设置中增加退出按钮
*
### v1.7.4(通用)
+14 -8
View File
@@ -32,16 +32,22 @@ adb install my-tv.apk
* 地方频道
* 收藏夹
* 海外
* 1.5.0 无法安装,1.5.1 可以安装
* 节目增加预告
* 频道列表优化
* 自动更新
* 显示时间
* 软解
* 不同分辨率
* “比如:我输了3跳转到3号频道后数字没有消失时,这时我发现按错了频道,然后我想马上换到6频道输到6就变成36了”
* epg更新后菜单更新
* 隐藏频道
* 亮度调节
* 音量调节
* 错误页
* 频道列表页只点击一次即可
無法自啟的設備:
斐讯N1盒子,[Phicomm] Phicomm p230 (Android 7.1.2)
## 版权说明
本项目仅供学习研究,禁止用于商业用途,请于下载二十四小时内删除。
本项目可能随时终止,请大家谨慎使用,建议使用官方渠道进行观看。
本项目使用的部分代码、图片、文字等资源来源于网络,如有侵权,请联系删除。
## 赞赏
+11 -8
View File
@@ -13,7 +13,7 @@ android {
defaultConfig {
applicationId "com.lizongying.mytv"
minSdk 21
minSdk 17
targetSdk 33
versionCode VersionCode()
versionName VersionName()
@@ -95,7 +95,7 @@ static def VersionName() {
}
dependencies {
def media3_version = "1.3.1"
def media3_version = "1.2.1"
implementation "androidx.media3:media3-ui:$media3_version"
@@ -105,20 +105,21 @@ dependencies {
// For HLS playback support with ExoPlayer
implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
// 21:2.11.0 17:2.6.4
def retrofit2_version = "2.11.0"
// 21:2.9.0 17:2.6.4
def retrofit2_version = "2.6.4"
implementation 'com.google.protobuf:protobuf-kotlin:3.25.1'
implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
implementation "com.squareup.retrofit2:converter-protobuf:$retrofit2_version"
implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
implementation 'androidx.leanback:leanback:1.2.0-alpha04'
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation 'androidx.core:core-ktx:1.13.0'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
implementation 'androidx.core:core-ktx:1.11.0-beta02'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.leanback:leanback:1.2.0-alpha02'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC")
@@ -126,4 +127,6 @@ dependencies {
implementation 'com.google.android.exoplayer:exoplayer-ui:2.13.3'
implementation 'com.google.android.exoplayer:exoplayer-core:2.13.3'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.13.3'
implementation 'javax.annotation:javax.annotation-api:1.3.2'
}
+1 -1
View File
@@ -14,7 +14,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:name=".MyApplication"
android:name=".MyTvApplication"
android:allowBackup="true"
android:banner="@drawable/banner"
android:icon="@drawable/logo"
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,157 @@
package com.lizongying.mytv
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.ScaleAnimation
import androidx.core.view.marginTop
import androidx.core.view.setPadding
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.lizongying.mytv.databinding.CardBinding
import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel
class CardAdapter(
private val recyclerView: RecyclerView,
private val mainFragment: MainFragment,
private var tvListViewModel: TVListViewModel,
) :
RecyclerView.Adapter<CardAdapter.ViewHolder>() {
private var listener: ItemListener? = null
private var focused: View? = null
var focusable = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = CardBinding.inflate(inflater, parent, false)
val application = mainFragment.requireActivity().applicationContext as MyTvApplication
binding.card.layoutParams.width = application.px2Px(binding.card.layoutParams.width)
binding.card.layoutParams.height = application.px2Px(binding.card.layoutParams.height)
binding.icon.layoutParams.height = application.px2Px(binding.icon.layoutParams.height)
binding.icon.setPadding(application.px2Px(binding.icon.paddingTop))
binding.main.layoutParams.height = application.px2Px(binding.main.layoutParams.height)
binding.main.setPadding(application.px2Px(binding.main.paddingTop))
binding.title.textSize = application.px2PxFont(binding.title.textSize)
binding.desc.textSize = application.px2PxFont(binding.desc.textSize)
val layoutParams = binding.desc.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = application.px2Px(binding.desc.marginTop)
binding.desc.layoutParams = layoutParams
val cardView = binding.root
cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
return ViewHolder(cardView, binding)
}
fun clear() {
focused?.clearFocus()
recyclerView.invalidate()
}
private fun startScaleAnimation(view: View, fromScale: Float, toScale: Float, duration: Long) {
val scaleAnimation = ScaleAnimation(
fromScale, toScale,
fromScale, toScale,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f
)
scaleAnimation.duration = duration
scaleAnimation.fillAfter = false
view.startAnimation(scaleAnimation)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val item = tvListViewModel.getTVViewModel(position)
val tvViewModel = item as TVViewModel
val cardView = viewHolder.view
cardView.tag = tvViewModel
val onFocusChangeListener = View.OnFocusChangeListener { view, hasFocus ->
if (hasFocus) {
listener?.onItemHasFocus(item)
focused = cardView
if (focusable) {
startScaleAnimation(cardView, 0.9f, 1.0f, 200)
viewHolder.focus(true)
}
} else {
viewHolder.focus(false)
}
}
cardView.onFocusChangeListener = onFocusChangeListener
cardView.setOnClickListener { _ ->
listener?.onItemClicked(item)
}
cardView.setOnKeyListener { _, keyCode, event: KeyEvent? ->
if (event?.action == KeyEvent.ACTION_DOWN) {
return@setOnKeyListener listener?.onKey(keyCode) ?: false
}
false
}
viewHolder.binding.title.text = tvViewModel.getTV().title
viewHolder.binding.icon.let {
Glide.with(viewHolder.view.context)
.load(tvViewModel.getTV().logo)
.centerInside()
.into(it)
}
val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() }
if (!epg.isNullOrEmpty()) {
viewHolder.binding.desc.text = epg.last().title
} else {
viewHolder.binding.desc.text = ""
}
}
override fun getItemCount() = tvListViewModel.size()
class ViewHolder(itemView: View, var binding: CardBinding) : RecyclerView.ViewHolder(itemView) {
val view = itemView
fun focus(hasFocus: Boolean) {
if (hasFocus) {
binding.main.setBackgroundResource(R.drawable.rounded_light_bottom)
} else {
binding.main.setBackgroundResource(R.drawable.rounded_dark_bottom)
}
}
}
fun toPosition(position: Int) {
recyclerView.post {
recyclerView.scrollToPosition(position)
recyclerView.getChildAt(position)?.isSelected
recyclerView.getChildAt(position)?.requestFocus()
}
}
interface ItemListener {
fun onItemHasFocus(tvViewModel: TVViewModel)
fun onItemClicked(tvViewModel: TVViewModel)
fun onKey(keyCode: Int): Boolean
}
fun setItemListener(listener: ItemListener) {
this.listener = listener
}
companion object {
private const val TAG = "CardAdapter"
}
}
@@ -1,76 +0,0 @@
package com.lizongying.mytv
import android.content.Context
import android.graphics.Color
import android.view.ContextThemeWrapper
import android.view.ViewGroup
import android.widget.ImageView
import androidx.leanback.widget.ImageCardView
import androidx.leanback.widget.Presenter
import com.bumptech.glide.Glide
import com.lizongying.mytv.models.TVViewModel
class CardPresenter(
private val context: Context,
) : Presenter() {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val cardView = object :
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
return ViewHolder(cardView)
}
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any?) {
val tvViewModel = item as TVViewModel
val cardView = viewHolder.view as ImageCardView
cardView.titleText = tvViewModel.getTV().title
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)
cardView.tag = tvViewModel.videoUrl.value
cardView.mainImageView?.let {
Glide.with(viewHolder.view.context)
.load(tvViewModel.getTV().logo)
.centerInside()
.into(it)
}
cardView.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
// cardView.setOnFocusChangeListener { v, hasFocus ->
// run {
// if (hasFocus) {
// if (v != null) {
// (v as ImageCardView).setInfoAreaBackgroundColor(context.resources.getColor(R.color.focus))
// }
// } else {
// if (v != null) {
// (v as ImageCardView).setInfoAreaBackgroundColor(context.resources.getColor(R.color.ic_launcher_background))
// }
// }
// }
// }
val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() }
if (!epg.isNullOrEmpty()) {
cardView.contentText = epg.last().title
} else {
cardView.contentText = ""
}
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) {
val cardView = viewHolder.view as ImageCardView
cardView.mainImage = null
}
companion object {
private const val TAG = "CardPresenter"
private const val CARD_WIDTH = 300
private const val CARD_HEIGHT = 101
}
}
@@ -1,11 +1,13 @@
package com.lizongying.mytv
import android.content.res.Resources
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.marginRight
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment
import com.lizongying.mytv.databinding.ChannelBinding
import com.lizongying.mytv.models.TVViewModel
@@ -15,7 +17,7 @@ class ChannelFragment : Fragment() {
private val binding get() = _binding!!
private val handler = Handler()
private val delay: Long = 3000
private val delay: Long = 5000
private var channel = 0
private var channelCount = 0
@@ -26,37 +28,39 @@ class ChannelFragment : Fragment() {
_binding = ChannelBinding.inflate(inflater, container, false)
_binding!!.root.visibility = View.GONE
val activity = requireActivity()
val application = activity.applicationContext as MyApplication
val displayMetrics = application.getDisplayMetrics()
val application = requireActivity().applicationContext as MyTvApplication
displayMetrics.density
var screenWidth = displayMetrics.widthPixels
var screenHeight = displayMetrics.heightPixels
if (screenHeight > screenWidth) {
screenWidth = displayMetrics.heightPixels
screenHeight = displayMetrics.widthPixels
}
val width = application.getWidth()
val height = application.getHeight()
val ratio = 16f / 9f
if (screenWidth / screenHeight > ratio) {
val x = ((screenWidth - screenHeight * ratio) / 2).toInt()
if (width.toFloat() / height > ratio) {
val x =
((Resources.getSystem().displayMetrics.widthPixels - height * ratio) / 2).toInt()
val originalLayoutParams =
binding.channelFragment.layoutParams as ViewGroup.MarginLayoutParams
binding.channel.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.rightMargin += x
binding.channelFragment.layoutParams = originalLayoutParams
binding.channel.layoutParams = originalLayoutParams
}
if (screenWidth / screenHeight < ratio) {
val y = ((screenHeight - screenWidth / ratio) / 2).toInt()
if (width.toFloat() / height < ratio) {
val y =
((height - Resources.getSystem().displayMetrics.widthPixels / ratio) / 2).toInt()
val originalLayoutParams =
binding.channelFragment.layoutParams as ViewGroup.MarginLayoutParams
binding.channel.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.topMargin += y
binding.channelFragment.layoutParams = originalLayoutParams
binding.channel.layoutParams = originalLayoutParams
}
binding.channel.layoutParams.width = application.px2Px(binding.channel.layoutParams.width)
binding.channel.layoutParams.height = application.px2Px(binding.channel.layoutParams.height)
val layoutParams = binding.channel.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = application.px2Px(binding.channel.marginTop)
layoutParams.rightMargin = application.px2Px(binding.channel.marginRight)
binding.channel.layoutParams = layoutParams
binding.content.textSize = application.px2PxFont(binding.content.textSize)
(activity as MainActivity).fragmentReady("ChannelFragment")
return binding.root
}
@@ -64,7 +68,7 @@ class ChannelFragment : Fragment() {
fun show(tvViewModel: TVViewModel) {
handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(playRunnable)
binding.channelContent.text = (tvViewModel.getTV().id.plus(1)).toString()
binding.content.text = (tvViewModel.getTV().id.plus(1)).toString()
view?.visibility = View.VISIBLE
handler.postDelayed(hideRunnable, delay)
}
@@ -74,13 +78,11 @@ class ChannelFragment : Fragment() {
return
}
channelCount++
Log.i(TAG, "channelCount ${channelCount}")
this.channel = "${this.channel}$channel".toInt()
Log.i(TAG, "this.channel ${this.channel}")
handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(playRunnable)
if (channelCount < 2) {
binding.channelContent.text = "${this.channel}"
binding.content.text = "${this.channel}"
view?.visibility = View.VISIBLE
handler.postDelayed(playRunnable, delay)
} else {
@@ -102,20 +104,18 @@ class ChannelFragment : Fragment() {
}
private val hideRunnable = Runnable {
binding.channelContent.text = ""
binding.content.text = ""
view?.visibility = View.GONE
channel = 0
channelCount = 0
Log.i(TAG, "hideRunnable")
}
private val playRunnable = Runnable {
(activity as MainActivity).play(channel - 1)
binding.channelContent.text = ""
binding.content.text = ""
view?.visibility = View.GONE
channel = 0
channelCount = 0
Log.i(TAG, "playRunnable")
}
override fun onDestroyView() {
@@ -1,28 +0,0 @@
package com.lizongying.mytv
import android.view.View
import androidx.core.content.ContextCompat
import androidx.leanback.app.ErrorSupportFragment
class ErrorFragment : ErrorSupportFragment() {
internal fun setErrorContent(message: String) {
imageDrawable =
ContextCompat.getDrawable(context!!, androidx.leanback.R.drawable.lb_ic_sad_cloud)
this.message = message
setDefaultBackground(TRANSLUCENT)
backgroundDrawable = ContextCompat.getDrawable(
context!!,
R.color.black
)
buttonText = resources.getString(R.string.dismiss_error)
buttonClickListener = View.OnClickListener {
fragmentManager!!.beginTransaction().remove(this@ErrorFragment).commit()
}
}
companion object {
private const val TRANSLUCENT = false
}
}
@@ -1,11 +1,16 @@
package com.lizongying.mytv
import android.content.res.Resources
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.marginBottom
import androidx.core.view.marginLeft
import androidx.core.view.marginTop
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.lizongying.mytv.databinding.InfoBinding
@@ -16,31 +21,66 @@ class InfoFragment : Fragment() {
private val binding get() = _binding!!
private val handler = Handler()
private val delay: Long = 3000
private val delay: Long = 5000
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = InfoBinding.inflate(inflater, container, false)
val application = requireActivity().applicationContext as MyTvApplication
val width = application.getWidth()
val height = application.getHeight()
val ratio = 16f / 9f
if (width.toFloat() / height < ratio) {
val y =
((height - Resources.getSystem().displayMetrics.widthPixels / ratio) / 2).toInt()
val originalLayoutParams = binding.info.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.bottomMargin += y
binding.info.layoutParams = originalLayoutParams
}
binding.info.layoutParams.width = application.px2Px(binding.info.layoutParams.width)
binding.info.layoutParams.height = application.px2Px(binding.info.layoutParams.height)
val layoutParams = binding.info.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.bottomMargin = application.px2Px(binding.info.marginBottom)
binding.info.layoutParams = layoutParams
binding.logo.layoutParams.width = application.px2Px(binding.logo.layoutParams.width)
binding.logo.setPadding(application.px2Px(binding.logo.paddingTop))
binding.main.layoutParams.width = application.px2Px(binding.main.layoutParams.width)
binding.main.setPadding(application.px2Px(binding.main.paddingTop))
val layoutParamsMain = binding.main.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsMain.leftMargin = application.px2Px(binding.main.marginLeft)
binding.main.layoutParams = layoutParamsMain
val layoutParamsDesc = binding.desc.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsDesc.topMargin = application.px2Px(binding.desc.marginTop)
binding.desc.layoutParams = layoutParamsDesc
binding.title.textSize = application.px2PxFont(binding.title.textSize)
binding.desc.textSize = application.px2PxFont(binding.desc.textSize)
_binding!!.root.visibility = View.GONE
(activity as MainActivity).fragmentReady("InfoFragment")
return binding.root
}
fun show(tvViewModel: TVViewModel) {
binding.textView.text = tvViewModel.getTV().title
binding.title.text = tvViewModel.getTV().title
Glide.with(this)
.load(tvViewModel.getTV().logo)
.into(binding.infoLogo)
.into(binding.logo)
Log.i(TAG, "${tvViewModel.getTV().title} ${tvViewModel.epg.value}")
val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() }
if (!epg.isNullOrEmpty()) {
binding.infoDesc.text = epg.last().title
binding.desc.text = epg.last().title
} else {
binding.infoDesc.text = ""
binding.desc.text = ""
}
handler.removeCallbacks(removeRunnable)
@@ -0,0 +1,29 @@
package com.lizongying.mytv
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class ItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
private var application: MyTvApplication
init {
application = context.applicationContext as MyTvApplication
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
val space = application.dp2Px(5)
outRect.top = space
outRect.bottom = space
outRect.left = space
outRect.right = space
}
}
@@ -11,7 +11,6 @@ import android.util.Log
import android.view.GestureDetector
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
import android.view.WindowManager
import android.widget.Toast
@@ -33,7 +32,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private val channelFragment = ChannelFragment()
private var timeFragment = TimeFragment()
private val settingFragment = SettingFragment()
private val errorFragment = ErrorFragment()
private var doubleBackToExitPressedOnce = false
@@ -44,12 +42,13 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private val delayHideSetting: Long = 10000
init {
Utils.setRequestListener(this)
}
private fun syncTime() {
lifecycleScope.launch(Dispatchers.IO) {
val utilsJob = async(start = CoroutineStart.LAZY) { Utils.init() }
utilsJob.start()
// utilsJob.await()
}
}
@@ -77,12 +76,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
}
gestureDetector = GestureDetector(this, GestureListener())
errorFragment.buttonClickListener = View.OnClickListener {
supportFragmentManager.beginTransaction()
.remove(errorFragment)
.commit()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val connectivityManager =
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
@@ -101,7 +94,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
Log.i(TAG, "net ${Build.VERSION.SDK_INT}")
ready++
}
}
fun showInfoFragment(tvViewModel: TVViewModel) {
@@ -127,7 +119,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
fun play(tvViewModel: TVViewModel) {
playerFragment.play(tvViewModel)
mainFragment.view?.requestFocus()
}
fun play(itemPosition: Int) {
@@ -142,18 +133,11 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
mainFragment.next()
}
private fun prevSource() {
// mainFragment.prevSource()
}
private fun nextSource() {
// mainFragment.nextSource()
}
fun switchMainFragment() {
val transaction = supportFragmentManager.beginTransaction()
if (mainFragment.isHidden) {
mainFragment.setPosition()
transaction.show(mainFragment)
mainActive()
} else {
@@ -174,11 +158,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
showTime()
}
fun settingHideNow() {
handler.removeCallbacks(hideSetting)
handler.postDelayed(hideSetting, 0)
}
fun settingNeverHide() {
handler.removeCallbacks(hideSetting)
}
@@ -204,7 +183,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
fun fragmentReady(tag: String) {
ready++
Log.i(TAG, "ready $tag $ready ")
if (ready == 6) {
if (ready == 7) {
mainFragment.fragmentReady()
showTime()
}
@@ -217,14 +196,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} else {
timeFragment.hide()
}
}
fun isPlaying() {
if (errorFragment.isVisible) {
supportFragmentManager.beginTransaction()
.remove(errorFragment)
.commit()
}
mainFragment.changeStyle()
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
@@ -237,6 +209,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
Log.i(TAG, "onSingleTapConfirmed")
switchMainFragment()
return true
}
@@ -297,9 +270,14 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private val hideSetting = Runnable {
if (settingFragment.isVisible) {
settingFragment.dismiss()
showTime()
}
}
private fun hideSettingFragment() {
handler.postDelayed(hideSetting, 0)
}
private fun channelUp() {
if (mainFragment.isHidden) {
if (SP.channelReversal) {
@@ -339,6 +317,11 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
return
}
if (settingFragment.isVisible) {
hideSettingFragment()
return
}
if (doubleBackToExitPressedOnce) {
super.onBackPressed()
return
@@ -352,8 +335,9 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
}, 2000)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
Log.i(TAG, "keyCode $keyCode, event $event")
fun onKey(keyCode: Int): Boolean {
Log.i(TAG, "keyCode $keyCode")
when (keyCode) {
KeyEvent.KEYCODE_0 -> {
showChannel("0")
@@ -465,19 +449,26 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
}
KeyEvent.KEYCODE_DPAD_LEFT -> {
if (!mainFragment.isVisible && !settingFragment.isVisible) {
if (mainFragment.isHidden && !settingFragment.isVisible) {
switchMainFragment()
return true
}
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
if (!mainFragment.isVisible && !settingFragment.isVisible) {
if (mainFragment.isHidden && !settingFragment.isVisible) {
showSetting()
return true
}
}
}
return false
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (onKey(keyCode)) {
return true
}
return super.onKeyDown(keyCode, event)
}
@@ -485,7 +476,8 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private fun getAppSignature() = this.appSignature
override fun onStart() {
Log.i(TAG, "onStart")
Log.i(TAG, "onStart MainActivity")
syncTime()
super.onStart()
}
@@ -509,13 +501,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
}
override fun onRequestFinished(message: String?) {
if (message != null && !errorFragment.isVisible) {
supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, errorFragment)
.commitNow()
errorFragment.setErrorContent(message)
}
fragmentReady("Request")
fragmentReady("request")
}
private companion object {
@@ -1,72 +1,95 @@
package com.lizongying.mytv
import android.content.res.Resources
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.ArrayObjectAdapter
import androidx.leanback.widget.HeaderItem
import androidx.leanback.widget.ListRow
import androidx.leanback.widget.ListRowPresenter
import androidx.leanback.widget.ListRowPresenter.SelectItemViewHolderTask
import androidx.leanback.widget.OnItemViewClickedListener
import androidx.leanback.widget.OnItemViewSelectedListener
import androidx.leanback.widget.Presenter
import androidx.leanback.widget.Row
import androidx.leanback.widget.RowPresenter
import androidx.core.view.marginBottom
import androidx.core.view.marginLeft
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.databinding.MenuBinding
import com.lizongying.mytv.databinding.RowBinding
import com.lizongying.mytv.models.ProgramType
import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainFragment : BrowseSupportFragment() {
class MainFragment : Fragment(), CardAdapter.ItemListener {
private var itemPosition = 0
private var rowsAdapter: ArrayObjectAdapter? = null
private var rowList: MutableList<View> = mutableListOf()
private var _binding: MenuBinding? = null
private val binding get() = _binding!!
var tvListViewModel = TVListViewModel()
private var lastVideoUrl = ""
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(TAG, "onCreate")
super.onCreate(savedInstanceState)
headersState = HEADERS_DISABLED
private lateinit var application: MyTvApplication
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = MenuBinding.inflate(inflater, container, false)
application = requireActivity().applicationContext as MyTvApplication
val width = application.getWidth()
val height = application.getHeight()
val ratio = 16f / 9f
if (width.toFloat() / height > ratio) {
val x =
((Resources.getSystem().displayMetrics.widthPixels - height * ratio) / 2).toInt()
val originalLayoutParams =
binding.scroll.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.leftMargin += x
originalLayoutParams.rightMargin += x
binding.scroll.layoutParams = originalLayoutParams
Log.i(
TAG,
"binding.scroll ${Resources.getSystem().displayMetrics.widthPixels} ${height * ratio}"
)
}
if (width.toFloat() / height < ratio) {
val y =
((height - Resources.getSystem().displayMetrics.widthPixels / ratio) / 2).toInt()
val originalLayoutParams =
binding.scroll.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.topMargin += y
originalLayoutParams.bottomMargin += y
binding.scroll.layoutParams = originalLayoutParams
}
binding.menu.setOnClickListener {
hideSelf()
}
return binding.root
}
// override fun onCreateView(
// inflater: LayoutInflater,
// container: ViewGroup?,
// savedInstanceState: Bundle?
// ): View? {
// val rootView = super.onCreateView(inflater, container, savedInstanceState)
// rootView?.setOnClickListener {
// Log.i(TAG, "main on click")
// fragmentManager!!.beginTransaction().hide(this).commit()
// }
// mainFragment.view?.setOnClickListener {
// Log.i(TAG, "mainFragment on click")
// fragmentManager!!.beginTransaction().hide(this).commit()
// }
// getRowsSupportFragment().view?.setOnClickListener {
// Log.i(TAG, "getRowsSupportFragment on click")
// fragmentManager!!.beginTransaction().hide(this).commit()
// }
//
//
// return rootView
// }
override fun onStart() {
Log.i(TAG, "onStart")
super.onStart()
private fun hideSelf() {
requireActivity().supportFragmentManager.beginTransaction()
.hide(this)
.commit()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
@@ -74,168 +97,227 @@ class MainFragment : BrowseSupportFragment() {
activity?.let { YSP.init(it) }
loadRows()
setupEventListeners()
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.errInfo.value != null
&& tvViewModel.getTV().id == itemPosition
) {
Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT).show()
}
}
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
// not first time && channel not change
if (tvViewModel.ready.value != null
&& tvViewModel.getTV().id == itemPosition
&& check(tvViewModel)
) {
Log.i(TAG, "ready ${tvViewModel.getTV().title}")
(activity as? MainActivity)?.play(tvViewModel)
}
}
tvViewModel.change.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.change.value != null) {
val title = tvViewModel.getTV().title
Log.i(TAG, "switch $title")
if (tvViewModel.getTV().pid != "") {
Log.i(TAG, "request $title")
lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { Request.fetchData(it) }
}
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setSelectedPosition(
tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
)
} else {
if (check(tvViewModel)) {
(activity as? MainActivity)?.play(tvViewModel)
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setSelectedPosition(
tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
)
}
}
}
}
}
(activity as MainActivity).fragmentReady("MainFragment")
}
fun toLastPosition() {
setSelectedPosition(
selectedPosition, false,
SelectItemViewHolderTask(tvListViewModel.maxNum[selectedPosition] - 1)
)
}
fun toFirstPosition() {
setSelectedPosition(
selectedPosition, false,
SelectItemViewHolderTask(0)
)
}
override fun startHeadersTransition(withHeaders: Boolean) {
}
private fun loadRows() {
rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
val cardPresenter = CardPresenter(context!!)
var idx: Long = 0
for ((k, v) in TVList.list) {
val listRowAdapter = ArrayObjectAdapter(cardPresenter)
for ((idx2, v1) in v.withIndex()) {
val tvViewModel = TVViewModel(v1)
tvViewModel.setRowPosition(idx.toInt())
tvViewModel.setItemPosition(idx2)
tvListViewModel.addTVViewModel(tvViewModel)
listRowAdapter.add(tvViewModel)
}
tvListViewModel.maxNum.add(v.size)
val header = HeaderItem(idx, k)
rowsAdapter!!.add(ListRow(header, listRowAdapter))
idx++
}
adapter = rowsAdapter
itemPosition = SP.itemPosition
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
}
fun prevSource() {
view?.post {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
if (tvViewModel != null) {
if (tvViewModel.videoUrl.value!!.size > 1) {
val videoIndex = tvViewModel.videoIndex.value?.minus(1)
if (videoIndex == -1) {
tvViewModel.setVideoIndex(tvViewModel.videoUrl.value!!.size - 1)
val content = binding.content
var idx: Long = 0
for ((k, v) in TVList.list) {
val itemBinding: RowBinding =
RowBinding.inflate(layoutInflater, content, false)
val tvListViewModelCurrent = TVListViewModel()
for ((idx2, v1) in v.withIndex()) {
val tvViewModel = TVViewModel(v1)
tvViewModel.setRowPosition(idx.toInt())
tvViewModel.setItemPosition(idx2)
tvListViewModelCurrent.addTVViewModel(tvViewModel)
tvListViewModel.addTVViewModel(tvViewModel)
}
tvListViewModel.maxNum.add(v.size)
val adapter =
CardAdapter(
itemBinding.items,
this,
tvListViewModelCurrent,
)
rowList.add(itemBinding.items)
adapter.setItemListener(this)
itemBinding.header.text = k
itemBinding.items.tag = idx.toInt()
itemBinding.items.adapter = adapter
itemBinding.items.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
(activity as MainActivity).mainActive()
}
tvViewModel.changed()
}
}
}
}
})
fun nextSource() {
view?.post {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
if (tvViewModel != null) {
if (tvViewModel.videoUrl.value!!.size > 1) {
val videoIndex = tvViewModel.videoIndex.value?.plus(1)
if (videoIndex == tvViewModel.videoUrl.value!!.size) {
tvViewModel.setVideoIndex(0)
val itemDecoration = context?.let { ItemDecoration(it) }
if (itemDecoration != null) {
itemBinding.items.addItemDecoration(itemDecoration)
}
if (SP.grid) {
itemBinding.items.layoutManager =
GridLayoutManager(context, 6)
itemBinding.items.layoutParams.height =
application.dp2Px(110 * ((tvListViewModelCurrent.size() + 6 - 1) / 6) + 5)
} else {
itemBinding.items.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
}
val layoutParams = itemBinding.row.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = application.dp2Px(11)
itemBinding.row.layoutParams = layoutParams
itemBinding.row.setOnClickListener {
hideSelf()
}
val layoutParamsHeader =
itemBinding.header.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsHeader.topMargin = application.px2Px(itemBinding.header.marginTop)
layoutParamsHeader.bottomMargin = application.px2Px(itemBinding.header.marginBottom)
layoutParamsHeader.leftMargin = application.px2Px(itemBinding.header.marginLeft)
itemBinding.header.layoutParams = layoutParamsHeader
itemBinding.header.textSize = application.px2PxFont(itemBinding.header.textSize)
content.addView(itemBinding.row)
idx++
}
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.errInfo.value != null
&& tvViewModel.getTV().id == itemPosition
) {
Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT)
.show()
}
tvViewModel.changed()
}
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
// not first time && channel not change
if (tvViewModel.ready.value != null
&& tvViewModel.getTV().id == itemPosition
&& check(tvViewModel)
) {
Log.i(TAG, "ready ${tvViewModel.getTV().title}")
(activity as? MainActivity)?.play(tvViewModel)
}
}
tvViewModel.change.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.change.value != null) {
val title = tvViewModel.getTV().title
Log.i(TAG, "switch $title")
if (tvViewModel.getTV().pid != "") {
Log.i(TAG, "request $title")
lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { Request.fetchData(it) }
}
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setPosition(
tvViewModel.getRowPosition(), tvViewModel.getItemPosition()
)
} else {
if (check(tvViewModel)) {
// TODO lastVideoUrl
(activity as? MainActivity)?.play(tvViewModel)
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setPosition(
tvViewModel.getRowPosition(), tvViewModel.getItemPosition()
)
}
}
}
}
}
(activity as MainActivity).fragmentReady("MainFragment")
}
}
fun changeStyle() {
if (SP.grid) {
for (i in rowList) {
if (i is RecyclerView) {
i.layoutManager = GridLayoutManager(context, 6)
i.layoutParams.height =
application.dp2Px(110 * (((i.adapter as CardAdapter).getItemCount() + 6 - 1) / 6) + 5)
}
}
} else {
for (i in rowList) {
if (i is RecyclerView) {
i.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
i.layoutParams.height = application.dp2Px(110 + 5)
}
}
}
}
private fun setupEventListeners() {
onItemViewClickedListener = ItemViewClickedListener()
onItemViewSelectedListener = ItemViewSelectedListener()
}
override fun onKey(keyCode: Int): Boolean {
if (this.isHidden) {
when (keyCode) {
KeyEvent.KEYCODE_DPAD_UP -> {
(activity as MainActivity).onKey(keyCode)
return true
}
private inner class ItemViewClickedListener : OnItemViewClickedListener {
override fun onItemClicked(
itemViewHolder: Presenter.ViewHolder,
item: Any,
rowViewHolder: RowPresenter.ViewHolder,
row: Row
) {
if (item is TVViewModel) {
if (itemPosition != item.getTV().id) {
itemPosition = item.getTV().id
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
KeyEvent.KEYCODE_DPAD_DOWN -> {
(activity as MainActivity).onKey(keyCode)
return true
}
(activity as? MainActivity)?.switchMainFragment()
}
}
return false
}
private inner class ItemViewSelectedListener : OnItemViewSelectedListener {
override fun onItemSelected(
itemViewHolder: Presenter.ViewHolder?, item: Any?,
rowViewHolder: RowPresenter.ViewHolder, row: Row
) {
if (item is TVViewModel) {
tvListViewModel.setItemPositionCurrent(item.getTV().id)
(activity as MainActivity).mainActive()
override fun onItemHasFocus(tvViewModel: TVViewModel) {
tvListViewModel.setItemPositionCurrent(tvViewModel.getTV().id)
val row = tvViewModel.getRowPosition()
for (i in rowList) {
if (i.tag as Int != row) {
((i as RecyclerView).adapter as CardAdapter).focusable = false
((i as RecyclerView).adapter as CardAdapter).clear()
} else {
((i as RecyclerView).adapter as CardAdapter).focusable = true
}
}
(activity as MainActivity).mainActive()
}
override fun onItemClicked(tvViewModel: TVViewModel) {
Log.i(TAG, "onItemClicked")
if (this.isHidden) {
(activity as? MainActivity)?.switchMainFragment()
return
}
if (itemPosition != tvViewModel.getTV().id) {
itemPosition = tvViewModel.getTV().id
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed("menu")
}
(activity as? MainActivity)?.switchMainFragment()
}
fun setPosition() {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) ?: return
val rowPosition = tvViewModel.getRowPosition()
val itemPosition = tvViewModel.getItemPosition()
setPosition(rowPosition, itemPosition)
}
fun setPosition(rowPosition: Int, itemPosition: Int) {
rowList[rowPosition].post {
when (val layoutManager = (rowList[rowPosition] as RecyclerView).layoutManager) {
is GridLayoutManager -> {
layoutManager.findViewByPosition(
itemPosition
)?.requestFocus()
}
is LinearLayoutManager -> {
layoutManager.findViewByPosition(
itemPosition
)?.requestFocus()
}
}
}
}
@@ -257,7 +339,7 @@ class MainFragment : BrowseSupportFragment() {
}
fun fragmentReady() {
tvListViewModel.getTVViewModel(itemPosition)?.changed()
tvListViewModel.getTVViewModel(itemPosition)?.changed("init")
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
updateEPG(tvViewModel)
@@ -269,7 +351,7 @@ class MainFragment : BrowseSupportFragment() {
if (itemPosition > -1 && itemPosition < tvListViewModel.size()) {
this.itemPosition = itemPosition
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
tvListViewModel.getTVViewModel(itemPosition)?.changed("num")
} else {
Toast.makeText(context, "频道不存在", Toast.LENGTH_SHORT).show()
}
@@ -283,7 +365,7 @@ class MainFragment : BrowseSupportFragment() {
itemPosition = tvListViewModel.size() - 1
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
tvListViewModel.getTVViewModel(itemPosition)?.changed("prev")
}
}
@@ -294,7 +376,7 @@ class MainFragment : BrowseSupportFragment() {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
tvListViewModel.getTVViewModel(itemPosition)?.changed("next")
}
}
@@ -314,6 +396,34 @@ class MainFragment : BrowseSupportFragment() {
}
}
fun shouldHasFocus(tvModel: TVViewModel): Boolean {
return tvModel == tvListViewModel.getTVViewModel(itemPosition)
}
override fun onHiddenChanged(hidden: Boolean) {
Log.i(TAG, "onHiddenChanged $hidden")
super.onHiddenChanged(hidden)
if (!hidden) {
val tvModel = tvListViewModel.getTVViewModel(itemPosition)
val rowPosition = tvModel?.getRowPosition()
val itemPosition = tvModel?.getItemPosition()
Log.i(TAG, "toPosition $rowPosition $itemPosition")
for (i in rowList) {
if (i.tag as Int == rowPosition) {
((i as RecyclerView).adapter as CardAdapter).focusable = true
((i as RecyclerView).adapter as CardAdapter).toPosition(itemPosition!!)
break
}
}
} else {
view?.post {
for (i in rowList) {
((i as RecyclerView).adapter as CardAdapter).focusable = false
}
}
}
}
override fun onResume() {
Log.i(TAG, "onResume")
super.onResume()
@@ -331,6 +441,12 @@ class MainFragment : BrowseSupportFragment() {
super.onDestroy()
}
override fun onDestroyView() {
Log.i(TAG, "onDestroyView")
super.onDestroyView()
_binding = null
}
companion object {
private const val TAG = "MainFragment"
private const val POSITION = "position"
@@ -1,23 +0,0 @@
package com.lizongying.mytv
import android.app.Application
import android.content.Context
import android.util.DisplayMetrics
import android.view.WindowManager
class MyApplication : Application() {
private lateinit var displayMetrics: DisplayMetrics
override fun onCreate() {
super.onCreate()
displayMetrics = DisplayMetrics()
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
// windowManager.defaultDisplay.getRealMetrics(displayMetrics)
windowManager.defaultDisplay.getMetrics(displayMetrics)
}
fun getDisplayMetrics(): DisplayMetrics {
return displayMetrics
}
}
@@ -0,0 +1,95 @@
package com.lizongying.mytv
import android.app.Application
import android.content.Context
import android.content.res.Resources
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.multidex.MultiDex
class MyTvApplication : Application() {
private lateinit var displayMetrics: DisplayMetrics
private lateinit var realDisplayMetrics: DisplayMetrics
private var width = 0
private var height = 0
private var deviation = 0
private var ratio = 1.0
private var density = 2.0f
private var scale = 1.0f
override fun onCreate() {
super.onCreate()
displayMetrics = DisplayMetrics()
realDisplayMetrics = DisplayMetrics()
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
windowManager.defaultDisplay.getMetrics(displayMetrics)
windowManager.defaultDisplay.getRealMetrics(realDisplayMetrics)
if (realDisplayMetrics.heightPixels > realDisplayMetrics.widthPixels) {
width = realDisplayMetrics.heightPixels
height = realDisplayMetrics.widthPixels
} else {
width = realDisplayMetrics.widthPixels
height = realDisplayMetrics.heightPixels
}
density = displayMetrics.density
ratio = if ((width.toDouble() / height) < (16.0 / 9)) {
width * 2 / 1920.0 / density
} else {
height * 2 / 1080.0 / density
}
deviation = width - Resources.getSystem().displayMetrics.widthPixels
scale = Resources.getSystem().displayMetrics.scaledDensity
}
fun getDisplayMetrics(): DisplayMetrics {
return displayMetrics
}
fun getRealDisplayMetrics(): DisplayMetrics {
return realDisplayMetrics
}
fun getWidth(): Int {
return width
}
fun getDeviation(): Int {
return deviation
}
fun getHeight(): Int {
return height
}
fun getRatio(): Double {
return ratio
}
fun dp2Px(dp: Int): Int {
return (dp * ratio * density + 0.5f).toInt()
}
fun px2Px(px: Int): Int {
return (px * ratio + 0.5f).toInt()
}
fun px2PxFont(px: Float): Float {
return (px * ratio / scale).toFloat()
}
fun sp2Px(sp: Float): Float {
return (sp * ratio * scale).toFloat()
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(base)
}
}
@@ -80,14 +80,7 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
super.onPlayerError(error)
Log.e(TAG, "PlaybackException $error")
tvViewModel?.changed()
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
if (isPlaying) {
(activity as MainActivity).isPlaying()
}
tvViewModel?.changed("retry")
}
})
}
@@ -54,7 +54,7 @@ object Request {
private var token = ""
private var tokenFH = ""
private var needAuth = false
private var needAuth = true
private var needToken = false
private val handler = Handler(Looper.getMainLooper())
@@ -590,7 +590,7 @@ object Request {
val randomNumber = Random.nextDouble()
val url = tvModel.getTV().videoUrl.first()
var r = KvcollectRequest(
val r = KvcollectRequest(
guid = guid,
prog = sid,
viewid = sid,
@@ -605,27 +605,10 @@ object Request {
_dc = randomNumber,
)
var e =
val e =
"BossId=${r.BossId}&Pwd=${r.Pwd}&_dc=${r._dc}&cdn=${r.cdn}&cmd=${r.cmd}&defn=${r.defn}&downspeed=${r.downspeed}&durl=${r.durl}&errcode=${r.errcode}&fact1=${r.fact1}&firstreport=${r.firstreport}&fplayerver=${r.fplayerver}&ftime=${r.ftime}&geturltime=6&guid=${r.guid}&hc_openid=${r.hc_openid}&hh_ua=${r.hh_ua}&live_type=${r.live_type}&livepid=${r.livepid}&login_type=${r.login_type}&open_id=&openid=${r.openid}&platform=${r.platform}&playno=${r.playno}&prd=${r.prd}&prog=${r.prog}&rand_str=${r.rand_str}&sRef=${r.sRef}&sUrl=${r.sUrl}&sdtfrom=${r.sdtfrom}&seq=${r.seq}&url=${r.url}&viewid=${r.viewid}"
println(e)
r.signature = YSP.getAuthSignature(e)
println(r.signature)
// val ttt = """{"BossId":2727,"Pwd":1424084450,"prog":"2022576801","playno":"BEoZw3cpZh","guid":"luccvoyr_58va8i3cqw6","hh_ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36","cdn":"waibao","sdtfrom":"ysp_pc_01","prd":60000,"platform":"5910204","errcode":"","durl":"https://outlivecloud-cdn.ysp.cctv.cn/906028A94D533483FE8BAC5CF4DB6CC70753E25A93EF3EC50988538DEDBF10161D1EA8F67C916EE41BA9D891D7893C69500B3F1A37CA814D2EE19341197360196CD9CCF327949104097C904643D493EB5A9B588EC28A2FAC1C8E30F8EE330DB2364B0DEC0124F6990A83773FF283489A/2022576803.m3u8?from=player&svrtime=1713365896&pid=600001859&cdn=5401&revoi=6E400EC7436C79D49BA06E61333D31FB8915F3484D9426FE4CCC4DE01CB8FD1DF473A8F416EF1107B669B0A9447F913272789A827EDD3DC0FE88534D9B59A35669678946589AA4D246F8F248F7C33AB08CE72FA0A73FE7F1F9FD2091D30176895CB8BD44A5B85FAD9579C700CB2AFD8D0939574B2A3EFF4C1B237DFF3E0CD3E194662F9FDEE48D1ACD7BDFAB01868B220743021C40E0EA5514ECCF396B509DA1&app_id=519748109&guid=luccvoyr_58va8i3cqw6&ysign=bb79232986c5429600d76cfedb1d5978&ytime=1713365896&ytype=1","firstreport":1,"sUrl":"https://www.yangshipin.cn/#/tv/home?pid=600001859","sRef":"","fplayerver":"100","livepid":"600001859","viewid":"2022576801","seq":0,"cmd":263,"login_type":"","geturltime":6,"downspeed":1.75,"hc_openid":"","open_id":"","defn":"fhd","fact1":"ysp_pc_live_b","openid":"","_dc":0.7140416315131102,"live_type":"","ftime":"2024-04-17 22:58:31","url":"https://outlivecloud-cdn.ysp.cctv.cn/906028A94D533483FE8BAC5CF4DB6CC70753E25A93EF3EC50988538DEDBF10161D1EA8F67C916EE41BA9D891D7893C69500B3F1A37CA814D2EE19341197360196CD9CCF327949104097C904643D493EB5A9B588EC28A2FAC1C8E30F8EE330DB2364B0DEC0124F6990A83773FF283489A/2022576803.m3u8?from=player&svrtime=1713365896&pid=600001859&cdn=5401&revoi=6E400EC7436C79D49BA06E61333D31FB8915F3484D9426FE4CCC4DE01CB8FD1DF473A8F416EF1107B669B0A9447F913272789A827EDD3DC0FE88534D9B59A35669678946589AA4D246F8F248F7C33AB08CE72FA0A73FE7F1F9FD2091D30176895CB8BD44A5B85FAD9579C700CB2AFD8D0939574B2A3EFF4C1B237DFF3E0CD3E194662F9FDEE48D1ACD7BDFAB01868B220743021C40E0EA5514ECCF396B509DA1&app_id=519748109&guid=luccvoyr_58va8i3cqw6&ysign=bb79232986c5429600d76cfedb1d5978&ytime=1713365896&ytype=1","rand_str":"oKkkZs8lON"}"""
// val gson = Gson()
// r = gson.fromJson(ttt, KvcollectRequest::class.java)
//
// e =
// "BossId=${r.BossId}&Pwd=${r.Pwd}&_dc=${r._dc}&cdn=${r.cdn}&cmd=${r.cmd}&defn=${r.defn}&downspeed=${r.downspeed}&durl=${r.durl}&errcode=${r.errcode}&fact1=${r.fact1}&firstreport=${r.firstreport}&fplayerver=${r.fplayerver}&ftime=${r.ftime}&geturltime=6&guid=${r.guid}&hc_openid=${r.hc_openid}&hh_ua=${r.hh_ua}&live_type=${r.live_type}&livepid=${r.livepid}&login_type=${r.login_type}&open_id=&openid=${r.openid}&platform=${r.platform}&playno=${r.playno}&prd=${r.prd}&prog=${r.prog}&rand_str=${r.rand_str}&sRef=${r.sRef}&sUrl=${r.sUrl}&sdtfrom=${r.sdtfrom}&seq=${r.seq}&url=${r.url}&viewid=${r.viewid}"
// println(e)
// e = "BossId=2727&Pwd=1424084450&_dc=0.7140416315131102&cdn=waibao&cmd=263&defn=fhd&downspeed=1.75&durl=https://outlivecloud-cdn.ysp.cctv.cn/906028A94D533483FE8BAC5CF4DB6CC70753E25A93EF3EC50988538DEDBF10161D1EA8F67C916EE41BA9D891D7893C69500B3F1A37CA814D2EE19341197360196CD9CCF327949104097C904643D493EB5A9B588EC28A2FAC1C8E30F8EE330DB2364B0DEC0124F6990A83773FF283489A/2022576803.m3u8?from=player&svrtime=1713365896&pid=600001859&cdn=5401&revoi=6E400EC7436C79D49BA06E61333D31FB8915F3484D9426FE4CCC4DE01CB8FD1DF473A8F416EF1107B669B0A9447F913272789A827EDD3DC0FE88534D9B59A35669678946589AA4D246F8F248F7C33AB08CE72FA0A73FE7F1F9FD2091D30176895CB8BD44A5B85FAD9579C700CB2AFD8D0939574B2A3EFF4C1B237DFF3E0CD3E194662F9FDEE48D1ACD7BDFAB01868B220743021C40E0EA5514ECCF396B509DA1&app_id=519748109&guid=luccvoyr_58va8i3cqw6&ysign=bb79232986c5429600d76cfedb1d5978&ytime=1713365896&ytype=1&errcode=&fact1=ysp_pc_live_b&firstreport=1&fplayerver=100&ftime=2024-04-17 22:58:31&geturltime=6&guid=luccvoyr_58va8i3cqw6&hc_openid=&hh_ua=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36&live_type=&livepid=600001859&login_type=&open_id=&openid=&platform=5910204&playno=BEoZw3cpZh&prd=60000&prog=2022576801&rand_str=oKkkZs8lON&sRef=&sUrl=https://www.yangshipin.cn/#/tv/home?pid=600001859&sdtfrom=ysp_pc_01&seq=0&url=https://outlivecloud-cdn.ysp.cctv.cn/906028A94D533483FE8BAC5CF4DB6CC70753E25A93EF3EC50988538DEDBF10161D1EA8F67C916EE41BA9D891D7893C69500B3F1A37CA814D2EE19341197360196CD9CCF327949104097C904643D493EB5A9B588EC28A2FAC1C8E30F8EE330DB2364B0DEC0124F6990A83773FF283489A/2022576803.m3u8?from=player&svrtime=1713365896&pid=600001859&cdn=5401&revoi=6E400EC7436C79D49BA06E61333D31FB8915F3484D9426FE4CCC4DE01CB8FD1DF473A8F416EF1107B669B0A9447F913272789A827EDD3DC0FE88534D9B59A35669678946589AA4D246F8F248F7C33AB08CE72FA0A73FE7F1F9FD2091D30176895CB8BD44A5B85FAD9579C700CB2AFD8D0939574B2A3EFF4C1B237DFF3E0CD3E194662F9FDEE48D1ACD7BDFAB01868B220743021C40E0EA5514ECCF396B509DA1&app_id=519748109&guid=luccvoyr_58va8i3cqw6&ysign=bb79232986c5429600d76cfedb1d5978&ytime=1713365896&ytype=1&viewid=2022576801"
// println(e)
// r.signature = YSP.getAuthSignature(e)
// println(r.signature)
callBtracePage = yspBtraceService2.kvcollect2(
r
)
callBtracePage = yspBtraceService2.kvcollect2(r)
callBtracePage?.enqueue(object : Callback<Void> {
override fun onResponse(call: Call<Void>, response: Response<Void>) {
if (response.isSuccessful) {
@@ -5,6 +5,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.core.view.marginEnd
import androidx.core.view.marginTop
import androidx.fragment.app.DialogFragment
import com.lizongying.mytv.databinding.SettingBinding
@@ -71,6 +73,14 @@ class SettingFragment : DialogFragment() {
}
}
binding.switchGrid.run {
isChecked = SP.grid
setOnCheckedChangeListener { _, isChecked ->
SP.grid = isChecked
(activity as MainActivity).settingDelayHide()
}
}
updateManager = UpdateManager(context, this, context.appVersionCode)
binding.checkVersion.setOnClickListener(
OnClickListenerCheckVersion(
@@ -79,7 +89,67 @@ class SettingFragment : DialogFragment() {
)
)
binding.exit.setOnClickListener{
val application = requireActivity().applicationContext as MyTvApplication
binding.content.layoutParams.width =
application.px2Px(binding.content.layoutParams.width)
binding.content.setPadding(
application.px2Px(binding.content.paddingLeft),
application.px2Px(binding.content.paddingTop),
application.px2Px(binding.content.paddingRight),
application.px2Px(binding.content.paddingBottom)
)
binding.name.textSize = application.px2PxFont(binding.name.textSize)
binding.version.textSize = application.px2PxFont(binding.version.textSize)
val layoutParamsDesc = binding.version.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsDesc.topMargin = application.px2Px(binding.version.marginTop)
binding.version.layoutParams = layoutParamsDesc
binding.checkVersion.textSize = application.px2PxFont(binding.checkVersion.textSize)
val layoutParamsCheckVersion =
binding.checkVersion.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsCheckVersion.marginEnd = application.px2Px(binding.checkVersion.marginEnd)
binding.checkVersion.layoutParams = layoutParamsCheckVersion
binding.versionName.textSize = application.px2PxFont(binding.versionName.textSize)
binding.exit.textSize = application.px2PxFont(binding.exit.textSize)
binding.switchChannelReversal.textSize =
application.px2PxFont(binding.switchChannelReversal.textSize)
val layoutParamsChannelReversal =
binding.switchChannelReversal.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsChannelReversal.topMargin =
application.px2Px(binding.switchChannelReversal.marginTop)
binding.switchChannelReversal.layoutParams = layoutParamsChannelReversal
binding.switchChannelNum.textSize = application.px2PxFont(binding.switchChannelNum.textSize)
val layoutParamsChannelNum =
binding.switchChannelNum.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsChannelNum.topMargin = application.px2Px(binding.switchChannelNum.marginTop)
binding.switchChannelNum.layoutParams = layoutParamsChannelNum
binding.switchTime.textSize = application.px2PxFont(binding.switchTime.textSize)
val layoutParamsTime = binding.switchTime.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsTime.topMargin = application.px2Px(binding.switchTime.marginTop)
binding.switchTime.layoutParams = layoutParamsTime
binding.switchBootStartup.textSize =
application.px2PxFont(binding.switchBootStartup.textSize)
val layoutParamsBootStartup =
binding.switchBootStartup.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsBootStartup.topMargin = application.px2Px(binding.switchBootStartup.marginTop)
binding.switchBootStartup.layoutParams = layoutParamsBootStartup
binding.switchGrid.textSize = application.px2PxFont(binding.switchGrid.textSize)
val layoutParamsGrid = binding.switchGrid.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsGrid.topMargin = application.px2Px(binding.switchGrid.marginTop)
binding.switchGrid.layoutParams = layoutParamsGrid
binding.appreciate.layoutParams.width =
application.px2Px(binding.appreciate.layoutParams.width)
binding.exit.setOnClickListener {
requireActivity().finishAffinity()
}
+3 -2
View File
@@ -15,14 +15,15 @@ data class TV(
var programType: ProgramType,
var needToken: Boolean = false,
var mustToken: Boolean = false,
) : Serializable {
) : Serializable {
override fun toString(): String {
return "TV{" +
"id=" + id +
", title='" + title + '\'' +
", alias='" + alias + '\'' +
", videoUrl='" + videoUrl + '\'' +
", channel='" + channel + '\'' +
", logo='" + logo + '\'' +
", pid='" + pid + '\'' +
", sid='" + sid + '\'' +
@@ -1,11 +1,13 @@
package com.lizongying.mytv
import android.content.res.Resources
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.marginRight
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment
import com.lizongying.mytv.Utils.getDateFormat
import com.lizongying.mytv.databinding.TimeBinding
@@ -21,38 +23,39 @@ class TimeFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
Log.i(TAG, "onCreateView")
_binding = TimeBinding.inflate(inflater, container, false)
val activity = requireActivity()
val application = activity.applicationContext as MyApplication
val displayMetrics = application.getDisplayMetrics()
val application = requireActivity().applicationContext as MyTvApplication
displayMetrics.density
var screenWidth = displayMetrics.widthPixels
var screenHeight = displayMetrics.heightPixels
if (screenHeight > screenWidth) {
screenWidth = displayMetrics.heightPixels
screenHeight = displayMetrics.widthPixels
}
val width = application.getWidth()
val height = application.getHeight()
val ratio = 16f / 9f
if (screenWidth / screenHeight > ratio) {
val x = ((screenWidth - screenHeight * ratio) / 2).toInt()
if (width.toFloat() / height > ratio) {
val x =
((Resources.getSystem().displayMetrics.widthPixels - height * ratio) / 2).toInt()
val originalLayoutParams = binding.time.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.rightMargin += x
binding.time.layoutParams = originalLayoutParams
}
if (screenWidth / screenHeight < ratio) {
val y = ((screenHeight - screenWidth / ratio) / 2).toInt()
if (width.toFloat() / height < ratio) {
val y =
((height - Resources.getSystem().displayMetrics.widthPixels / ratio) / 2).toInt()
val originalLayoutParams = binding.time.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.topMargin += y
binding.time.layoutParams = originalLayoutParams
}
binding.time.layoutParams.width = application.px2Px(binding.time.layoutParams.width)
binding.time.layoutParams.height = application.px2Px(binding.time.layoutParams.height)
val layoutParams = binding.time.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = application.px2Px(binding.time.marginTop)
layoutParams.rightMargin = application.px2Px(binding.time.marginRight)
binding.time.layoutParams = layoutParams
binding.content.textSize = application.px2PxFont(binding.content.textSize)
(activity as MainActivity).fragmentReady("TimeFragment")
return binding.root
}
@@ -72,6 +75,9 @@ class TimeFragment : Fragment() {
private val showRunnable: Runnable = Runnable {
run {
if (_binding == null) {
return@Runnable
}
binding.content.text = getDateFormat("HH:mm")
handler.postDelayed(showRunnable, delay)
}
@@ -89,6 +95,7 @@ class TimeFragment : Fragment() {
}
override fun onDestroyView() {
handler.removeCallbacks(showRunnable)
super.onDestroyView()
_binding = null
}
@@ -62,7 +62,7 @@ class UpdateManager(
private fun updateUI(text: String) {
val dialog = ConfirmationFragment(this@UpdateManager, text)
dialog.show(settingFragment.fragmentManager, TAG)
settingFragment.fragmentManager?.let { dialog.show(it, TAG) }
}
private fun haveStoragePermission(): Boolean {
@@ -15,6 +15,8 @@ import java.util.Locale
object Utils {
private var between: Long = 0
private var listener: Request.RequestListener? = null
fun getDateFormat(format: String): String {
return SimpleDateFormat(
format,
@@ -38,6 +40,10 @@ object Utils {
println("Failed to retrieve timestamp from server: ${e.message}")
}
between = System.currentTimeMillis() - currentTimeMillis
withContext(Dispatchers.Main) {
listener?.onRequestFinished(null)
}
}
/**
@@ -72,6 +78,8 @@ object Utils {
}
fun dpToPx(dp: Int): Int {
// val density = Resources.getSystem().displayMetrics.density
// return (dp * density + 0.5f).toInt()
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), Resources.getSystem().displayMetrics
).toInt()
@@ -88,4 +96,8 @@ object Utils {
}
fun isTmallDevice() = Build.MANUFACTURER.equals("Tmall", ignoreCase = true)
fun setRequestListener(listener: Request.RequestListener) {
this.listener = listener
}
}
@@ -7,6 +7,6 @@ import retrofit2.http.Path
interface ReleaseService {
@GET("my-tv/v2/release/{name}")
fun getRelease(
@Path("name") date: String = "1",
@Path("name") date: String = "2",
): Call<ReleaseV2>
}
@@ -41,8 +41,8 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val videoIndex: LiveData<Int>
get() = _videoIndex
private val _change = MutableLiveData<Boolean>()
val change: LiveData<Boolean>
private val _change = MutableLiveData<String>()
val change: LiveData<String>
get() = _change
private val _ready = MutableLiveData<Boolean>()
@@ -74,8 +74,8 @@ class TVViewModel(private var tv: TV) : ViewModel() {
}
}
fun changed() {
_change.value = true
fun changed(from: String) {
_change.value = from
}
fun allReady() {
@@ -1,4 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FF263238" />
<corners android:radius="4dp" /> <!-- Adjust the radius as needed -->
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/blur" />
<corners
android:bottomLeftRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/blur" />
<corners
android:topLeftRadius="4dp"
android:bottomLeftRadius="4dp" />
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/blur" />
<corners
android:topRightRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/focus" />
<corners
android:bottomLeftRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners
android:topLeftRadius="4dp"
android:bottomLeftRadius="4dp" />
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners
android:topRightRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -1,4 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners android:radius="4dp" /> <!-- Adjust the radius as needed -->
<corners
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
</shape>
+60
View File
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/card"
android:layout_width="150dp"
android:layout_height="100dp"
android:orientation="vertical"
android:layout_gravity="center_vertical"
>
<ImageView
android:id="@+id/icon"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/rounded_white_top"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:padding="5dp" />
<LinearLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="60dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/icon"
android:background="@drawable/rounded_dark_bottom"
android:orientation="vertical"
android:padding="7dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:gravity="start"
android:text="Title"
android:maxLines="1"
android:ellipsize="end"
android:textColor="#FFEEEEEE"
android:textSize="14sp" />
<TextView
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
android:layout_marginTop="8dp"
android:gravity="start"
android:maxLines="1"
android:ellipsize="end"
android:text="Description"
android:textColor="#B3EEEEEE"
android:textSize="12sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
+2 -3
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/channel_fragment"
android:id="@+id/channel"
android:layout_width="90dp"
android:layout_height="60dp"
android:layout_gravity="end|top"
@@ -8,11 +8,10 @@
android:layout_marginEnd="170dp">
<TextView
android:id="@+id/channel_content"
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="0dp"
android:textColor="#FFEEEEEE"
android:textStyle="bold"
android:textSize="40sp" />
+13 -20
View File
@@ -1,40 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/info_fragment"
android:layout_width="320dp"
android:id="@+id/info"
android:layout_width="400dp"
android:layout_height="80dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="20dp"
android:background="@drawable/rounded_background">
android:layout_marginBottom="20dp">
<ImageView
android:id="@+id/info_logo"
android:id="@+id/logo"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_height="match_parent"
android:background="@drawable/rounded_white_left"
android:padding="10dp" />
<LinearLayout
android:layout_width="216dp"
android:id="@+id/main"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_marginStart="100dp"
android:background="#FF263238"
android:background="@drawable/rounded_dark_right"
android:gravity="start|center_vertical"
android:orientation="vertical"
android:padding="7dp">
<TextView
android:id="@+id/textView"
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="0dp"
android:gravity="start"
android:textColor="#FFEEEEEE"
android:textSize="22sp" />
android:textSize="20sp" />
<TextView
android:id="@+id/info_desc"
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
@@ -42,12 +41,6 @@
android:gravity="start"
android:singleLine="true"
android:textColor="#B3EEEEEE"
android:textSize="16sp" />
android:textSize="14sp" />
</LinearLayout>
<View
android:layout_width="8dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="@drawable/rounded_background2" />
</FrameLayout>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:clickable="true"
android:focusable="false"
>
<ScrollView
android:id="@+id/scroll"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</FrameLayout>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/player_fragment"
android:id="@+id/player"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/transparent"
android:clickable="true"
android:focusable="false"
>
<TextView
android:id="@+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="0dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="5dp"
android:gravity="start"
android:textColor="#FFEEEEEE"
android:textSize="22sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/items"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
+22 -5
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialog"
android:id="@+id/setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
@@ -8,15 +8,17 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/content"
android:layout_width="300dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="20dp"
android:paddingLeft="7dp"
android:paddingRight="7dp"
android:background="#FF263238"
android:background="@drawable/rounded_dark_left"
>
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
@@ -27,9 +29,10 @@
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
android:textSize="14sp"
/>
<LinearLayout
android:layout_width="300dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
@@ -38,6 +41,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="14sp"
android:text="@string/check_version"
/>
<ProgressBar
@@ -48,6 +52,7 @@
<TextView
android:id="@+id/version_name"
android:text=""
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
@@ -55,38 +60,50 @@
android:id="@+id/exit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="14sp"
android:text="@string/exit"
/>
<Switch
android:id="@+id/switch_channel_reversal"
android:text="@string/title_channel_reversal"
android:textSize="14sp"
android:layout_marginTop="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:id="@+id/switch_channel_num"
android:text="@string/title_channel_num"
android:textSize="14sp"
android:layout_marginTop="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:id="@+id/switch_time"
android:text="@string/title_time"
android:textSize="14sp"
android:layout_marginTop="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:id="@+id/switch_boot_startup"
android:text="@string/title_boot_startup"
android:textSize="14sp"
android:layout_marginTop="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:id="@+id/switch_grid"
android:text="@string/title_grid"
android:textSize="14sp"
android:layout_marginTop="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView
android:id="@+id/appreciate"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:src="@drawable/appreciate"
android:background="@color/white" />
android:background="@drawable/rounded_white_right" />
</LinearLayout>
</FrameLayout>
-1
View File
@@ -12,7 +12,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="0dp"
android:textColor="#FFEEEEEE"
android:textStyle="bold"
android:textSize="32sp" />
+2 -3
View File
@@ -1,7 +1,6 @@
<resources>
<color name="focus">#0096a6</color>
<color name="fastlane_background">#30000000</color>
<color name="black">#000</color>
<color name="white">#FFF</color>
<color name="ic_launcher_background">#223239</color>
<color name="focus">#0096a6</color>
<color name="blur">#FF263238</color>
</resources>
@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#223239</color>
</resources>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="server_url">http://localhost:8080</string>
</resources>
+1 -1
View File
@@ -4,7 +4,7 @@
<string name="title_channel_num">换台时显示频道号</string>
<string name="check_version">检查更新</string>
<string name="title_boot_startup">开机自启</string>
<string name="dismiss_error">好的</string>
<string name="title_grid">频道列表采用网格样式</string>
<string name="title_time">显示时间</string>
<string name="exit">退出</string>
</resources>
+16 -1
View File
@@ -1,10 +1,25 @@
<resources>
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<item name="lbImageCardViewType">Title|Content</item>
<item name="cardType">infoUnder</item>
<item name="infoVisibility">always</item>
<!-- In order to keep backward compatibility we have to create an icon on right. -->
<item name="lbImageCardViewType">Title|Content|IconOnRight</item>
<!-- Deprecated. Use 'Widget.Leanback.ImageCardView.InfoAreaStyle' instead. -->
<item name="infoAreaBackground">@null</item>
</style>
<style name="CustomImageCardViewImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
<item name="android:layout_width">150dp</item>
<item name="android:layout_height">40dp</item>
<item name="android:adjustViewBounds">true</item>
<item name="android:contentDescription">@null</item>
<item name="android:scaleType">centerInside</item>
<item name="layout_viewType">main</item>
</style>
<style name="CustomImageCardTheme" parent="Theme.Leanback">
<item name="imageCardViewStyle">@style/CustomImageCardViewStyle</item>
<item name="imageCardViewImageStyle">@style/CustomImageCardViewImageStyle</item>
</style>
</resources>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 0 B