Compare commits

...
5 Commits
Author SHA1 Message Date
Li ZongYing c16a4bcb9c optimize time 2024-05-09 22:26:37 +08:00
Li ZongYing 0286d118ed fix update 2024-05-07 17:55:08 +08:00
Li ZongYing fc83defe31 optimize version check 2024-05-07 12:56:34 +08:00
Li ZongYing 6fc73efd47 optimize version check 2024-05-07 12:55:11 +08:00
Li ZongYing b157f1eb28 fix time 2024-05-07 11:38:10 +08:00
22 changed files with 370 additions and 160 deletions
+22 -2
View File
@@ -1,5 +1,25 @@
## 更新日志 ## 更新日志
### v1.9.8(通用)
* 优化在线升级
* 优化可能状态错误的问题
### v1.9.6(通用)
* 优化在线升级
* 优化可能状态错误的问题
### v1.9.4(通用)
* 修复默认时间不显示问题
* 设置页居中
* 频道号和时间对齐
### v1.9.2(通用)
* 修复一个重试的错误
### v1.9.0(通用) ### v1.9.0(通用)
* 减少视频播放失败情况 * 减少视频播放失败情况
@@ -26,6 +46,8 @@
* 修复无法播放问题 * 修复无法播放问题
### 以下已不可用
### v1.8.0(通用) ### v1.8.0(通用)
* 修复返回键无法退出问题 * 修复返回键无法退出问题
@@ -161,8 +183,6 @@
* 修复播放失败的问题 * 修复播放失败的问题
### 以下已不可用
### v1.5.3(安卓5及以上专用) ### v1.5.3(安卓5及以上专用)
* 修复部分情况下APP切换后无法继续播放的问题 * 修复部分情况下APP切换后无法继续播放的问题
+9 -1
View File
@@ -1,6 +1,6 @@
.PHONY: all .PHONY: all
all: info all: info gen-version
branch := $(shell git rev-parse --abbrev-ref HEAD) branch := $(shell git rev-parse --abbrev-ref HEAD)
commit := $(shell git rev-parse --short HEAD) commit := $(shell git rev-parse --short HEAD)
@@ -9,3 +9,11 @@ info:
@echo 'SHELL='$(SHELL) @echo 'SHELL='$(SHELL)
@echo 'branch='$(branch) @echo 'branch='$(branch)
@echo 'commit='$(commit) @echo 'commit='$(commit)
gen-version:
git describe --tags --always
git describe --tags --always | sed 's/v/ /g' | sed 's/\./ /g' | sed 's/-/ /g' | awk '{print ($$1*16777216)+($$2*65536)+($$3*256)+$$4}'
#make gen v=v1.1.3
gen:
echo $(v) | sed 's/v/ /g' | sed 's/\./ /g' | sed 's/-/ /g' | awk '{print "{\"version_code\": " ($$1*16777216)+($$2*65536)+($$3*256)+$$4 ", \"version_name\": \"" "$(v)" "\"}"}' > version.json
+10
View File
@@ -12,6 +12,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application <application
android:name=".MyTvApplication" android:name=".MyTvApplication"
@@ -63,5 +64,14 @@
android:exported="false" /> android:exported="false" />
<meta-data android:name="android.max_aspect" android:value="2.5" /> <meta-data android:name="android.max_aspect" android:value="2.5" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
</manifest> </manifest>
@@ -39,6 +39,7 @@ class ChannelFragment : Fragment() {
binding.channel.layoutParams = layoutParams binding.channel.layoutParams = layoutParams
binding.content.textSize = application.px2PxFont(binding.content.textSize) binding.content.textSize = application.px2PxFont(binding.content.textSize)
binding.time.textSize = application.px2PxFont(binding.time.textSize)
binding.main.layoutParams.width = application.shouldWidthPx() binding.main.layoutParams.width = application.shouldWidthPx()
binding.main.layoutParams.height = application.shouldHeightPx() binding.main.layoutParams.height = application.shouldHeightPx()
@@ -7,24 +7,33 @@ import androidx.fragment.app.DialogFragment
class ConfirmationFragment( class ConfirmationFragment(
private val listener: ConfirmationListener, private val listener: ConfirmationListener,
private val message: String private val message: String,
private val update: Boolean
) : DialogFragment() { ) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
val builder = AlertDialog.Builder(it) val builder = AlertDialog.Builder(it)
builder.setTitle("确定更新吗?") builder.setTitle(message)
.setMessage(message) if (update) {
.setPositiveButton( builder.setMessage("确定更新吗?")
"确定" .setPositiveButton(
) { _, _ -> "确定"
listener.onConfirm() ) { _, _ ->
} listener.onConfirm()
.setNegativeButton( }
"取消" .setNegativeButton(
) { _, _ -> "取消"
listener.onCancel() ) { _, _ ->
} listener.onCancel()
}
} else {
builder.setMessage("")
.setNegativeButton(
"好的"
) { _, _ ->
}
}
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
@@ -1,8 +1,5 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
@@ -34,7 +31,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private val playerFragment = PlayerFragment() private val playerFragment = PlayerFragment()
private val errorFragment = ErrorFragment() private val errorFragment = ErrorFragment()
// private val loadingFragment = LoadingFragment() private val loadingFragment = LoadingFragment()
private val mainFragment = MainFragment() private val mainFragment = MainFragment()
private val infoFragment = InfoFragment() private val infoFragment = InfoFragment()
private val channelFragment = ChannelFragment() private val channelFragment = ChannelFragment()
@@ -86,7 +83,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
// } // }
if (savedInstanceState == null) { if (savedInstanceState == null) {
supportFragmentManager.beginTransaction() val transaction = supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, playerFragment) .add(R.id.main_browse_fragment, playerFragment)
.add(R.id.main_browse_fragment, errorFragment) .add(R.id.main_browse_fragment, errorFragment)
// .add(R.id.main_browse_fragment, loadingFragment) // .add(R.id.main_browse_fragment, loadingFragment)
@@ -96,9 +93,14 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
.add(R.id.main_browse_fragment, mainFragment) .add(R.id.main_browse_fragment, mainFragment)
.hide(mainFragment) .hide(mainFragment)
.hide(errorFragment) .hide(errorFragment)
// .hide(loadingFragment)
.hide(timeFragment) if (!SP.time) {
.commit() transaction.hide(timeFragment)
} else {
transaction.show(timeFragment)
}
transaction.commitNow()
} }
gestureDetector = GestureDetector(this, GestureListener()) gestureDetector = GestureDetector(this, GestureListener())
@@ -120,9 +122,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
// Log.i(TAG, "net ${Build.VERSION.SDK_INT}") // Log.i(TAG, "net ${Build.VERSION.SDK_INT}")
// ready++ // ready++
// } // }
showTime()
mainFragment.changeMenu()
} }
fun showInfoFragment(tvViewModel: TVViewModel) { fun showInfoFragment(tvViewModel: TVViewModel) {
@@ -152,6 +151,14 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
.commit() .commit()
} }
fun showLoadingFragment() {
showFragment(loadingFragment)
}
fun hideLoadingFragment() {
hideFragment(loadingFragment)
}
fun showPlayerFragment() { fun showPlayerFragment() {
if (playerFragment.isVisible) { if (playerFragment.isVisible) {
return return
@@ -222,8 +229,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
fun settingDelayHide() { fun settingDelayHide() {
handler.removeCallbacks(hideSetting) handler.removeCallbacks(hideSetting)
handler.postDelayed(hideSetting, delayHideSetting) handler.postDelayed(hideSetting, delayHideSetting)
showTime()
mainFragment.changeMenu()
} }
fun settingNeverHide() { fun settingNeverHide() {
@@ -342,14 +347,12 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
return return
} }
Log.i(TAG, "settingFragment ${settingFragment.isVisible}") if (settingFragment.isVisible) {
if (!settingFragment.isVisible) { return
settingFragment.show(supportFragmentManager, "setting")
settingDelayHide()
} else {
handler.removeCallbacks(hideSetting)
settingFragment.dismiss()
} }
settingFragment.show(supportFragmentManager, TAG)
settingDelayHide()
} }
private val hideSetting = Runnable { private val hideSetting = Runnable {
@@ -421,6 +424,11 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
}, 2000) }, 2000)
} }
private fun active() {
if (settingFragment.isVisible) {
settingDelayHide()
}
}
fun onKey(keyCode: Int): Boolean { fun onKey(keyCode: Int): Boolean {
Log.i(TAG, "keyCode $keyCode") Log.i(TAG, "keyCode $keyCode")
@@ -511,6 +519,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_ENTER -> { KeyEvent.KEYCODE_ENTER -> {
active()
switchMainFragment() switchMainFragment()
} }
@@ -519,6 +528,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_DPAD_UP -> { KeyEvent.KEYCODE_DPAD_UP -> {
active()
channelUp() channelUp()
} }
@@ -527,6 +537,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_DPAD_DOWN -> { KeyEvent.KEYCODE_DPAD_DOWN -> {
active()
channelDown() channelDown()
} }
@@ -535,6 +546,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
active()
if (mainFragment.isHidden && !settingFragment.isVisible) { if (mainFragment.isHidden && !settingFragment.isVisible) {
switchMainFragment() switchMainFragment()
return true return true
@@ -542,6 +554,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> {
active()
if (mainFragment.isHidden && !settingFragment.isVisible) { if (mainFragment.isHidden && !settingFragment.isVisible) {
showSetting() showSetting()
return true return true
@@ -570,6 +583,12 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
override fun onResume() { override fun onResume() {
Log.i(TAG, "onResume") Log.i(TAG, "onResume")
super.onResume() super.onResume()
}
override fun onResumeFragments() {
super.onResumeFragments()
mainFragment.changeMenu()
if (!mainFragment.isHidden) { if (!mainFragment.isHidden) {
handler.postDelayed(hideMain, delayHideMain) handler.postDelayed(hideMain, delayHideMain)
} }
@@ -1,7 +1,5 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.res.Resources
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
@@ -162,8 +160,10 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
if (tvViewModel.errInfo.value == "") { if (tvViewModel.errInfo.value == "") {
(activity as? MainActivity)?.showPlayerFragment() (activity as? MainActivity)?.showPlayerFragment()
(activity as? MainActivity)?.hideErrorFragment() (activity as? MainActivity)?.hideErrorFragment()
(activity as? MainActivity)?.hideLoadingFragment()
} else { } else {
(activity as? MainActivity)?.hidePlayerFragment() (activity as? MainActivity)?.hidePlayerFragment()
(activity as? MainActivity)?.hideLoadingFragment()
(activity as? MainActivity)?.showErrorFragment(tvViewModel.errInfo.value.toString()) (activity as? MainActivity)?.showErrorFragment(tvViewModel.errInfo.value.toString())
} }
} }
@@ -1,10 +1,16 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.marginEnd import androidx.core.view.marginEnd
import androidx.core.view.marginTop import androidx.core.view.marginTop
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
@@ -23,6 +29,11 @@ class SettingFragment : DialogFragment() {
dialog?.window?.apply { dialog?.window?.apply {
addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
setAttributes(attributes)
}
} }
} }
@@ -81,13 +92,10 @@ class SettingFragment : DialogFragment() {
} }
} }
updateManager = UpdateManager(context, this, context.appVersionCode) binding.checkVersion.setOnClickListener {
binding.checkVersion.setOnClickListener( (activity as MainActivity).settingDelayHide()
OnClickListenerCheckVersion( requestInstallPermissions()
activity as MainActivity, }
updateManager
)
)
val application = requireActivity().applicationContext as MyTvApplication val application = requireActivity().applicationContext as MyTvApplication
@@ -156,20 +164,63 @@ class SettingFragment : DialogFragment() {
return binding.root return binding.root
} }
fun setVersionName(versionName: String) { private fun requestInstallPermissions() {
if (_binding != null) { val context = requireContext()
binding.versionName.text = versionName val permissionsList: MutableList<String> = ArrayList()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !context.packageManager.canRequestPackageInstalls()) {
permissionsList.add(Manifest.permission.REQUEST_INSTALL_PACKAGES)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
permissionsList.add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
permissionsList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if (permissionsList.isNotEmpty()) {
ActivityCompat.requestPermissions(
requireActivity(),
permissionsList.toTypedArray<String>(),
PERMISSIONS_REQUEST_CODE
)
} else {
UpdateManager(context, context.appVersionCode).checkAndUpdate()
} }
} }
internal class OnClickListenerCheckVersion( override fun onRequestPermissionsResult(
private val mainActivity: MainActivity, requestCode: Int,
private val updateManager: UpdateManager permissions: Array<out String>,
) : grantResults: IntArray
View.OnClickListener { ) {
override fun onClick(view: View?) { super.onRequestPermissionsResult(requestCode, permissions, grantResults)
mainActivity.settingDelayHide() if (requestCode == PERMISSIONS_REQUEST_CODE) {
updateManager.checkAndUpdate() var allPermissionsGranted = true
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allPermissionsGranted = false
break
}
}
if (allPermissionsGranted) {
val context = requireContext()
UpdateManager(context, context.appVersionCode).checkAndUpdate()
} else {
Toast.makeText(context, "权限授权失败", Toast.LENGTH_LONG).show()
}
} }
} }
@@ -180,6 +231,7 @@ class SettingFragment : DialogFragment() {
companion object { companion object {
const val TAG = "SettingFragment" const val TAG = "SettingFragment"
const val PERMISSIONS_REQUEST_CODE = 1
} }
} }
@@ -1,6 +1,5 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.res.Resources
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.view.LayoutInflater import android.view.LayoutInflater
@@ -36,10 +35,18 @@ class TimeFragment : Fragment() {
binding.time.layoutParams = layoutParams binding.time.layoutParams = layoutParams
binding.content.textSize = application.px2PxFont(binding.content.textSize) binding.content.textSize = application.px2PxFont(binding.content.textSize)
binding.channel.textSize = application.px2PxFont(binding.channel.textSize)
binding.main.layoutParams.width = application.shouldWidthPx() binding.main.layoutParams.width = application.shouldWidthPx()
binding.main.layoutParams.height = application.shouldHeightPx() binding.main.layoutParams.height = application.shouldHeightPx()
if (SP.time) {
handler.removeCallbacks(showRunnable)
handler.postDelayed(showRunnable, 0)
} else {
handler.removeCallbacks(showRunnable)
}
(activity as MainActivity).fragmentReady(TAG) (activity as MainActivity).fragmentReady(TAG)
return binding.root return binding.root
} }
@@ -1,6 +1,5 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.app.Activity
import android.app.DownloadManager import android.app.DownloadManager
import android.app.DownloadManager.Request import android.app.DownloadManager.Request
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
@@ -14,11 +13,10 @@ import android.os.Environment
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import androidx.core.app.ActivityCompat import androidx.fragment.app.FragmentActivity
import androidx.core.content.PermissionChecker import com.lizongying.mytv.api.ApiClient
import androidx.core.content.PermissionChecker.checkSelfPermission
import com.lizongying.mytv.api.ReleaseV2
import com.lizongying.mytv.requests.ReleaseRequest import com.lizongying.mytv.requests.ReleaseRequest
import com.lizongying.mytv.requests.ReleaseResponse
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -27,81 +25,62 @@ import java.io.File
class UpdateManager( class UpdateManager(
private var context: Context, private var context: Context,
private var settingFragment: SettingFragment,
private var versionCode: Long private var versionCode: Long
) : ) :
ConfirmationFragment.ConfirmationListener { ConfirmationFragment.ConfirmationListener {
private var myRequest = ReleaseRequest() private var releaseRequest = ReleaseRequest()
private var release: ReleaseV2? = null private var release: ReleaseResponse? = null
private var downloadReceiver: DownloadReceiver? = null private var downloadReceiver: DownloadReceiver? = null
fun checkAndUpdate() { fun checkAndUpdate() {
if (!haveStoragePermission()) {
return
}
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
var text = "版本获取失败" var text = "版本获取失败"
var update = false
try { try {
release = myRequest.getRelease() release = releaseRequest.getRelease()
Log.i(TAG, "versionCode $versionCode ${release?.c}") Log.i(TAG, "versionCode $versionCode ${release?.version_code}")
if (release?.c != null) { if (release?.version_code != null) {
text = if (release?.c!! > versionCode) { if (release?.version_code!! > versionCode) {
"最新版本:${release?.n}\n${release?.d ?: ""}" text = "最新版本:${release?.version_name}"
update = true
} else { } else {
"已是最新版本,不需要更新" text = "已是最新版本,不需要更新"
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error occurred: ${e.message}", e) Log.e(TAG, "Error occurred: ${e.message}", e)
} }
updateUI(text) updateUI(text, update)
} }
} }
private fun updateUI(text: String) { private fun updateUI(text: String, update: Boolean) {
val dialog = ConfirmationFragment(this@UpdateManager, text) val dialog = ConfirmationFragment(this@UpdateManager, text, update)
settingFragment.fragmentManager?.let { dialog.show(it, TAG) } dialog.show((context as FragmentActivity).supportFragmentManager, TAG)
} }
private fun haveStoragePermission(): Boolean { private fun startDownload(release: ReleaseResponse) {
if (Build.VERSION.SDK_INT >= 23) { val apkName = "my-tv"
if (checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) val apkFileName = "$apkName-${release.version_name}.apk"
=== PermissionChecker.PERMISSION_GRANTED
) {
Log.e("Permission error", "You have permission")
return true
} else {
Log.e("Permission error", "You have asked for permission")
ActivityCompat.requestPermissions(
context as Activity, arrayOf(
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
), 1
)
return false
}
} else { //you don't need to worry about these stuff below api level 23
Log.e("Permission error", "You already have the permission")
return true
}
}
private fun startDownload(release: ReleaseV2) {
val apkFileName = "my-tv-${release.n}.apk"
Log.i(TAG, "apkFileName $apkFileName") Log.i(TAG, "apkFileName $apkFileName")
val downloadManager = val downloadManager =
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val request = Request(Uri.parse(release.u)) val request =
Log.i(TAG, "url ${Uri.parse(release.u)}") Request(Uri.parse("${ApiClient.DOWNLOAD_HOST}${release.version_name}/$apkName-${release.version_name}.apk"))
Log.i(
TAG,
"url ${Uri.parse("${ApiClient.DOWNLOAD_HOST}${release.version_name}/$apkName-${release.version_name}.apk")}"
)
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.mkdirs() context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.mkdirs()
Log.i(TAG, "save dir ${Environment.DIRECTORY_DOWNLOADS}")
request.setDestinationInExternalFilesDir( request.setDestinationInExternalFilesDir(
context, context,
Environment.DIRECTORY_DOWNLOADS, Environment.DIRECTORY_DOWNLOADS,
apkFileName apkFileName
) )
request.setTitle("${settingFragment.resources.getString(R.string.app_name)} ${release.n}") request.setTitle("${context.resources.getString(R.string.app_name)} ${release.version_name}")
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setAllowedOverRoaming(false) request.setAllowedOverRoaming(false)
request.setMimeType("application/vnd.android.package-archive") request.setMimeType("application/vnd.android.package-archive")
@@ -179,27 +158,73 @@ class UpdateManager(
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
val reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) val reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
Log.i(TAG, "reference $reference") Log.i(TAG, "reference $reference")
val progress = intent.getIntExtra("progress", 0)
Log.i(TAG, "progress $progress")
// 检查是否是我们发起的下载
if (reference == downloadReference) { if (reference == downloadReference) {
// 下载完成,触发安装 val downloadManager =
installNewVersion() context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val query = DownloadManager.Query().setFilterById(downloadReference)
val cursor = downloadManager.query(query)
if (cursor != null && cursor.moveToFirst()) {
val statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
if (statusIndex < 0) {
Log.i(TAG, "Download failure")
return
}
val status = cursor.getInt(statusIndex)
val progressIndex =
cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
if (progressIndex < 0) {
Log.i(TAG, "Download failure")
return
}
val progress = cursor.getInt(progressIndex)
val totalSizeIndex =
cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
val totalSize = cursor.getInt(totalSizeIndex)
cursor.close()
when (status) {
DownloadManager.STATUS_SUCCESSFUL -> {
installNewVersion()
}
DownloadManager.STATUS_FAILED -> {
// Handle download failure
Log.i(TAG, "Download failure")
}
else -> {
// Update UI with download progress
val percentage = progress * 100 / totalSize
Log.i(TAG, "Download progress: $percentage%")
}
}
}
} }
} }
private fun installNewVersion() { private fun installNewVersion() {
val installIntent = Intent(Intent.ACTION_VIEW) val apkFile = File(
val apkUri = Uri.fromFile( context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
File( apkFileName
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
apkFileName
)
) )
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive") Log.i(TAG, "apkFile $apkFile")
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(installIntent) if (apkFile.exists()) {
val apkUri = Uri.parse("file://$apkFile")
Log.i(TAG, "apkUri $apkUri")
val installIntent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(apkUri, "application/vnd.android.package-archive")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(installIntent)
} else {
Log.e(TAG, "APK file does not exist!")
}
} }
} }
+22 -6
View File
@@ -8,6 +8,8 @@ import com.lizongying.mytv.api.TimeResponse
import com.lizongying.mytv.requests.Request import com.lizongying.mytv.requests.Request
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException import java.io.IOException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@@ -34,19 +36,34 @@ object Utils {
} }
suspend fun init() { suspend fun init() {
var currentTimeMillis: Long = 0
try { try {
currentTimeMillis = getTimestampFromServer() val currentTimeMillis = getTimestampFromServer()
if (currentTimeMillis > 0) {
between = System.currentTimeMillis() - currentTimeMillis
}
} catch (e: Exception) { } catch (e: Exception) {
println("Failed to retrieve timestamp from server: ${e.message}") println("Failed to retrieve timestamp from server: ${e.message}")
} }
between = System.currentTimeMillis() - currentTimeMillis
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
listener?.onRequestFinished(null) listener?.onRequestFinished(null)
} }
} }
class RetryInterceptor(private val maxRetry: Int) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var response = chain.proceed(request)
var tryCount = 0
while (!response.isSuccessful && tryCount < maxRetry) {
tryCount++
response = chain.proceed(request)
}
return response
}
}
/** /**
* 从服务器获取时间戳 * 从服务器获取时间戳
* @return Long 时间戳 * @return Long 时间戳
@@ -54,8 +71,8 @@ object Utils {
private suspend fun getTimestampFromServer(): Long { private suspend fun getTimestampFromServer(): Long {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val client = okhttp3.OkHttpClient.Builder() val client = okhttp3.OkHttpClient.Builder()
.connectTimeout(500, java.util.concurrent.TimeUnit.MILLISECONDS) .addInterceptor(RetryInterceptor(3))
.readTimeout(1, java.util.concurrent.TimeUnit.SECONDS).build() .build()
val request = okhttp3.Request.Builder() val request = okhttp3.Request.Builder()
.url("https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp") .url("https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp")
.build() .build()
@@ -66,7 +83,6 @@ object Utils {
Gson().fromJson(string, TimeResponse::class.java).data.t.toLong() Gson().fromJson(string, TimeResponse::class.java).data.t.toLong()
} }
} catch (e: IOException) { } catch (e: IOException) {
// Handle network errors
throw IOException("Error during network request", e) throw IOException("Error during network request", e)
} }
} }
@@ -4,6 +4,7 @@ package com.lizongying.mytv.api
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import com.lizongying.mytv.jce.JceConverterFactory import com.lizongying.mytv.jce.JceConverterFactory
import com.lizongying.mytv.requests.ReleaseService
import okhttp3.ConnectionSpec import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.TlsVersion import okhttp3.TlsVersion
@@ -47,7 +48,7 @@ class ApiClient {
val releaseService: ReleaseService by lazy { val releaseService: ReleaseService by lazy {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(myUrl) .baseUrl(HOST)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.build().create(ReleaseService::class.java) .build().create(ReleaseService::class.java)
@@ -170,4 +171,9 @@ class ApiClient {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
companion object {
const val HOST = "https://gitee.com/lizongying/my-tv/raw/"
const val DOWNLOAD_HOST = "https://gitee.com/lizongying/my-tv/releases/download/"
}
} }
@@ -1,12 +0,0 @@
package com.lizongying.mytv.api
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
interface ReleaseService {
@GET("my-tv/v2/release/{name}")
fun getRelease(
@Path("name") date: String = "2",
): Call<ReleaseV2>
}
@@ -13,17 +13,20 @@ import kotlin.coroutines.suspendCoroutine
class ReleaseRequest { class ReleaseRequest {
private var releaseService = ApiClient().releaseService private var releaseService = ApiClient().releaseService
suspend fun getRelease(): ReleaseV2? { suspend fun getRelease(): ReleaseResponse? {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
fetchRelease() fetchRelease()
} }
} }
private suspend fun fetchRelease(): ReleaseV2? { private suspend fun fetchRelease(): ReleaseResponse? {
return suspendCoroutine { continuation -> return suspendCoroutine { continuation ->
releaseService.getRelease() releaseService.getRelease()
.enqueue(object : Callback<ReleaseV2> { .enqueue(object : Callback<ReleaseResponse> {
override fun onResponse(call: Call<ReleaseV2>, response: Response<ReleaseV2>) { override fun onResponse(
call: Call<ReleaseResponse>,
response: Response<ReleaseResponse>
) {
if (response.isSuccessful) { if (response.isSuccessful) {
continuation.resume(response.body()) continuation.resume(response.body())
} else { } else {
@@ -31,7 +34,7 @@ class ReleaseRequest {
} }
} }
override fun onFailure(call: Call<ReleaseV2>, t: Throwable) { override fun onFailure(call: Call<ReleaseResponse>, t: Throwable) {
continuation.resume(null) continuation.resume(null)
} }
}) })
@@ -0,0 +1,7 @@
package com.lizongying.mytv.requests
data class ReleaseResponse(
val version_code: Int?,
val version_name: String?,
)
@@ -0,0 +1,10 @@
package com.lizongying.mytv.requests
import retrofit2.Call
import retrofit2.http.GET
interface ReleaseService {
@GET("JELLY_BEAN_MR1/version.json")
fun getRelease(
): Call<ReleaseResponse>
}
+19 -6
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
@@ -10,22 +11,34 @@
android:layout_height="match_parent" android:layout_height="match_parent"
> >
<FrameLayout <RelativeLayout
android:id="@+id/channel" android:id="@+id/channel"
android:layout_width="90dp" android:layout_width="90dp"
android:layout_height="60dp" android:layout_height="60dp"
android:layout_gravity="end|top" android:layout_gravity="end|top"
android:layout_marginTop="20dp" android:layout_marginTop="15dp"
android:layout_marginEnd="170dp"> android:layout_marginEnd="170dp"
android:gravity="end|bottom" >
<TextView <TextView
android:id="@+id/content" android:id="@+id/content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end|bottom"
android:textColor="@color/white" android:textColor="@color/white"
android:textStyle="bold" android:textStyle="bold"
android:textSize="40sp" /> android:textSize="40sp"
</FrameLayout> tools:text="99"
android:layout_alignBaseline="@+id/time"/>
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="32sp"
tools:text="12:34" />
</RelativeLayout>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
-1
View File
@@ -40,7 +40,6 @@
android:id="@+id/check_version" android:id="@+id/check_version"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="14sp" android:textSize="14sp"
android:text="@string/check_version" android:text="@string/check_version"
/> />
+21 -8
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
@@ -10,23 +11,35 @@
android:layout_height="match_parent" android:layout_height="match_parent"
> >
<FrameLayout <RelativeLayout
android:id="@+id/time" android:id="@+id/time"
android:layout_width="100dp" android:layout_width="120dp"
android:layout_height="40dp" android:layout_height="60dp"
android:layout_gravity="end|top" android:layout_gravity="end|top"
android:layout_marginTop="25dp" android:layout_marginTop="15dp"
android:layout_marginEnd="50dp"> android:layout_marginEnd="50dp"
android:gravity="end|bottom" >
<TextView
android:id="@+id/channel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="40sp"
tools:text="99"
android:layout_alignBaseline="@+id/content"/>
<TextView <TextView
android:id="@+id/content" android:id="@+id/content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end|bottom"
android:textColor="@color/white" android:textColor="@color/white"
android:textStyle="bold" android:textStyle="bold"
android:textSize="32sp" /> android:textSize="32sp"
</FrameLayout> tools:text="12:34" />
</RelativeLayout>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
+1 -1
View File
@@ -6,5 +6,5 @@
<string name="title_boot_startup">开机自启</string> <string name="title_boot_startup">开机自启</string>
<string name="title_grid">频道列表采用网格样式</string> <string name="title_grid">频道列表采用网格样式</string>
<string name="title_time">显示时间</string> <string name="title_time">显示时间</string>
<string name="exit">退出</string> <string name="exit">退出应用</string>
</resources> </resources>
+3
View File
@@ -0,0 +1,3 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="." />
</paths>
+1
View File
@@ -0,0 +1 @@
{"version_code": 17369088, "version_name": "v1.9.8"}