Skip to content

Commit

Permalink
✨ 自动更新
Browse files Browse the repository at this point in the history
  • Loading branch information
yaoxieyoulei committed Apr 16, 2024
1 parent 187504b commit ec67a64
Show file tree
Hide file tree
Showing 13 changed files with 396 additions and 8 deletions.
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:authorities="${applicationId}.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package top.yogiczy.mytv.data.entities

data class GithubRelease(
val tagName: String = "v0.0.0",
val downloadUrl: String = "",
val description: String = "",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package top.yogiczy.mytv.data.repositories

import top.yogiczy.mytv.data.entities.GithubRelease

interface GithubRepository {
suspend fun latestRelease(): GithubRelease
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package top.yogiczy.mytv.data.repositories

import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONArray
import org.json.JSONObject
import top.yogiczy.mytv.data.entities.GithubRelease
import top.yogiczy.mytv.data.utils.Constants

class GithubRepositoryImpl : GithubRepository {
override suspend fun latestRelease(): GithubRelease = withContext(Dispatchers.IO) {
Log.d(TAG, "获取最新release: ${Constants.GITHUB_RELEASE_LATEST_URL}")

val client = OkHttpClient()
val request = Request.Builder().url(Constants.GITHUB_RELEASE_LATEST_URL).build()

try {
return@withContext with(client.newCall(request).execute()) {
if (!isSuccessful) {
throw Exception("获取最新release失败: $code")
}

val json = JSONObject(body!!.string())

val release = GithubRelease(
tagName = json["tag_name"] as String,
downloadUrl = (json["assets"] as JSONArray).getJSONObject(0)["browser_download_url"] as String,
description = json["body"] as String
)
Log.d(TAG, "获取最新release: $release")

return@with release
}
} catch (e: Exception) {
Log.e(TAG, "获取最新release失败", e)
throw Exception("获取最新release失败,请检查网络连接", e.cause)
}
}

companion object {
const val TAG = "GithubRepositoryImpl"
}
}
7 changes: 6 additions & 1 deletion app/src/main/java/top/yogiczy/mytv/data/utils/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,10 @@ object Constants {
* GitHub最新版本信息
*/
const val GITHUB_RELEASE_LATEST_URL =
"https://api.github.com/repos/yaoxieyoulei/my_tv/releases/latest"
"https://api.github.com/repos/yaoxieyoulei/mytv-android/releases/latest"

/**
* GitHub加速代理地址
*/
const val GITHUB_PROXY = "https://mirror.ghproxy.com/"
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import top.yogiczy.mytv.data.entities.IptvGroupList.Companion.iptvIdx
import top.yogiczy.mytv.ui.screens.monitor.MonitorScreen
import top.yogiczy.mytv.ui.screens.panel.PanelScreen
import top.yogiczy.mytv.ui.screens.settings.SettingsScreen
import top.yogiczy.mytv.ui.screens.settings.components.rememberUpdateState
import top.yogiczy.mytv.ui.screens.video.VideoScreen
import top.yogiczy.mytv.ui.screens.video.rememberExoPlayerState
import top.yogiczy.mytv.ui.utils.SP
Expand All @@ -76,6 +77,7 @@ fun HomeContent(
val lifecycleOwner = LocalLifecycleOwner.current
val focusRequester = remember { FocusRequester() }

val updateState = rememberUpdateState()
val playerState = rememberExoPlayerState(state.exoPlayer)
val channelNoInputState = rememberChannelNoInputState { channelNo ->
if (channelNo.toInt() - 1 in 0..<iptvGroupList.flatMap { it.iptvs }.size) {
Expand Down Expand Up @@ -177,7 +179,10 @@ fun HomeContent(
}

if (state.isSettingsVisible) {
SettingsScreen(onClose = { state.changeSettingsVisible(false) })
SettingsScreen(
updateState = updateState,
onClose = { state.changeSettingsVisible(false) },
)
}

if (SP.debugShowFps) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ import androidx.tv.material3.MaterialTheme
import top.yogiczy.mytv.ui.rememberChildPadding
import top.yogiczy.mytv.ui.screens.settings.components.SettingsAppInfo
import top.yogiczy.mytv.ui.screens.settings.components.SettingsMain
import top.yogiczy.mytv.ui.screens.settings.components.UpdateState
import top.yogiczy.mytv.ui.screens.settings.components.rememberUpdateState
import top.yogiczy.mytv.ui.theme.MyTVTheme
import top.yogiczy.mytv.ui.utils.SP

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun SettingsScreen(
modifier: Modifier = Modifier,
updateState: UpdateState = rememberUpdateState(),
onClose: () -> Unit = {},
) {
val childPadding = rememberChildPadding()
Expand All @@ -53,7 +56,7 @@ fun SettingsScreen(

Spacer(modifier = Modifier.height(20.dp))

SettingsMain()
SettingsMain(updateState = updateState)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.tv.foundation.lazy.list.TvLazyRow
import kotlinx.coroutines.launch
import top.yogiczy.mytv.ui.rememberChildPadding
import top.yogiczy.mytv.ui.theme.MyTVTheme
import top.yogiczy.mytv.ui.utils.SP

@Composable
fun SettingsList(
modifier: Modifier = Modifier,
updateState: UpdateState = rememberUpdateState(),
) {
val childPadding = rememberChildPadding()

Expand Down Expand Up @@ -58,11 +61,17 @@ fun SettingsList(
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
item {
val coroutineScope = rememberCoroutineScope()

SettingsItem(
title = "应用更新",
value = "无更新",
description = "最新版本:v1.0.0",
onClick = { },
value = if (updateState.isUpdateAvailable) "新版本" else "无更新",
description = "最新版本:${updateState.latestRelease.tagName}",
onLongClick = {
coroutineScope.launch {
updateState.downloadAndUpdate()
}
},
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import top.yogiczy.mytv.ui.utils.SP
@Composable
fun SettingsMain(
modifier: Modifier = Modifier,
updateState: UpdateState = rememberUpdateState(),
) {
val childPadding = rememberChildPadding()

Expand All @@ -31,7 +32,7 @@ fun SettingsMain(
modifier = Modifier.padding(start = childPadding.start),
)
Spacer(modifier = Modifier.height(6.dp))
SettingsList()
SettingsList(updateState = updateState)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package top.yogiczy.mytv.ui.screens.settings.components

import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import top.yogiczy.mytv.data.entities.GithubRelease
import top.yogiczy.mytv.data.repositories.GithubRepositoryImpl
import top.yogiczy.mytv.data.utils.Constants
import top.yogiczy.mytv.ui.utils.ApkInstaller
import top.yogiczy.mytv.ui.utils.DownloadUtil
import top.yogiczy.mytv.ui.utils.VersionUtil
import java.io.File

@Stable
data class UpdateState(
private val context: Context,
private val packageInfo: PackageInfo,
private val coroutineScope: CoroutineScope,
val latestFile: File = File(context.cacheDir, "latest.apk"),
) {
private var _isChecking by mutableStateOf(false)
val isChecking get() = _isChecking

private var _isUpdating by mutableStateOf(false)
val isUpdating get() = _isUpdating

private var _isUpdateAvailable by mutableStateOf(false)
val isUpdateAvailable get() = _isUpdateAvailable

private var _updateDownloaded by mutableStateOf(false)
val updateDownloaded get() = _updateDownloaded

private var _latestRelease by mutableStateOf(GithubRelease())
val latestRelease get() = _latestRelease

suspend fun checkUpdate() {
if (_isChecking) return
if (_isUpdateAvailable) return

try {
_isChecking = true
_latestRelease = GithubRepositoryImpl().latestRelease()
if (VersionUtil.compareVersion(
_latestRelease.tagName.substring(1),
packageInfo.versionName
) > 0
) {
_isUpdateAvailable = true
Toast.makeText(
context,
"新版本: ${_latestRelease.tagName}",
Toast.LENGTH_SHORT
)
.show()
}
} catch (e: Exception) {
Log.e("UpdateState", e.message ?: e.toString(), e)
Toast.makeText(context, "检查更新失败", Toast.LENGTH_SHORT).show()
} finally {
_isChecking = false
}
}

suspend fun downloadAndUpdate() {
if (!_isUpdateAvailable) return
if (_isUpdating) return

try {
_isUpdating = true
_updateDownloaded = false

var toast = Toast.makeText(
context, "开始下载更新: ${_latestRelease.tagName}", Toast.LENGTH_SHORT
).apply { show() }

DownloadUtil.downloadTo(
"${Constants.GITHUB_PROXY}${_latestRelease.downloadUrl}",
latestFile.path,
downloadListener = object : DownloadUtil.DownloadListener() {
var lastTime = 0L
override fun onProgress(progress: Int) {
coroutineScope.launch {
if (System.currentTimeMillis() - lastTime > 1000) {
lastTime = System.currentTimeMillis()
toast.cancel()
toast = Toast.makeText(
context,
"正在下载更新: $progress%",
Toast.LENGTH_SHORT
).apply { show() }
}
}
}
}
)

_updateDownloaded = true
toast.cancel()
Toast.makeText(
context, "下载更新成功: ${_latestRelease.tagName}", Toast.LENGTH_SHORT
).show()

} catch (e: Exception) {
Toast.makeText(context, "下载更新失败", Toast.LENGTH_SHORT).show()
} finally {
_isUpdating = false
}
}
}

@Composable
fun rememberUpdateState(
context: Context = LocalContext.current,
): UpdateState {
val coroutineScope = rememberCoroutineScope()
val packageInfo = rememberPackageInfo()

val state = remember {
UpdateState(
context = context,
packageInfo = packageInfo,
coroutineScope = coroutineScope,
)
}

LaunchedEffect(Unit) {
state.checkUpdate()
}

val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (context.packageManager.canRequestPackageInstalls())
ApkInstaller.installApk(context, state.latestFile.path)
else
Toast.makeText(context, "未授予安装权限", Toast.LENGTH_SHORT).show()
}
}

LaunchedEffect(state.updateDownloaded) {
if (!state.updateDownloaded) return@LaunchedEffect

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
ApkInstaller.installApk(context, state.latestFile.path)
} else {
if (context.packageManager.canRequestPackageInstalls()) {
ApkInstaller.installApk(context, state.latestFile.path)
} else {
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
launcher.launch(intent)
}
}
}

return state
}
Loading

0 comments on commit ec67a64

Please sign in to comment.