Skip to content

Commit

Permalink
♻️ 优化代码
Browse files Browse the repository at this point in the history
  • Loading branch information
yaoxieyoulei committed Apr 17, 2024
1 parent f9331c8 commit 30793d8
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 133 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package top.yogiczy.mytv.data.repositories


import kotlinx.coroutines.flow.Flow
import top.yogiczy.mytv.data.entities.EpgList

interface EpgRepository {
fun getEpgs(filteredChannels: List<String>): Flow<EpgList>
suspend fun getEpgs(filteredChannels: List<String>): EpgList
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import android.content.Context
import android.util.Log
import android.util.Xml
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
Expand All @@ -27,10 +25,9 @@ import javax.inject.Singleton

@Singleton
class EpgRepositoryImpl(private val context: Context) : EpgRepository {
override fun getEpgs(filteredChannels: List<String>) = flow {
override suspend fun getEpgs(filteredChannels: List<String>): EpgList {
if (!SP.epgEnable) {
emit(EpgList())
return@flow
return EpgList()
}

val xml = getXml()
Expand All @@ -40,22 +37,19 @@ class EpgRepositoryImpl(private val context: Context) : EpgRepository {
val cache = getCache()
if (cache != null) {
Log.d(TAG, "使用缓存epg")
emit(cache)
return@flow
return cache
}
}

val epgList = parseFromXml(xml, filteredChannels)
val cacheFile = getCacheFile()
cacheFile.writeText(Json.encodeToString(epgList.value))
setCache(epgList)
SP.epgCacheHash = hashCode

emit(epgList)
return@flow
return epgList
}

private suspend fun fetchXml(retry: Int = 0): String = withContext(Dispatchers.IO) {
Log.d(TAG, "获取远程xml($retry): ${Constants.EPG_XML_URL}")
private suspend fun fetchXml(): String = withContext(Dispatchers.IO) {
Log.d(TAG, "获取远程xml: ${Constants.EPG_XML_URL}")

val client = OkHttpClient()
val request = Request.Builder().url(Constants.EPG_XML_URL).build()
Expand All @@ -70,10 +64,6 @@ class EpgRepositoryImpl(private val context: Context) : EpgRepository {
}
} catch (e: Exception) {
Log.e(TAG, "获取远程xml失败", e)
if (retry < 10) {
delay(3_000)
return@withContext fetchXml(retry + 1)
}
throw Exception("获取远程EPG失败,请检查网络连接", e.cause)
}
}
Expand All @@ -82,9 +72,13 @@ class EpgRepositoryImpl(private val context: Context) : EpgRepository {
return File(context.cacheDir, "epg.xml")
}

private fun getCacheXml(): String {
private suspend fun getCacheXml() = withContext(Dispatchers.IO) {
val file = getCacheXmlFile()
return if (file.exists()) file.readText() else ""
if (file.exists()) file.readText() else ""
}

private suspend fun setCacheXml(xml: String) = withContext(Dispatchers.IO) {
getCacheXmlFile().writeText(xml)
}

private suspend fun getXml(): String {
Expand All @@ -103,9 +97,8 @@ class EpgRepositoryImpl(private val context: Context) : EpgRepository {
}
}

val xml = fetchXml(0)
val cacheFile = getCacheXmlFile()
cacheFile.writeText(xml)
val xml = fetchXml()
setCacheXml(xml)
SP.epgXmlCacheTime = System.currentTimeMillis()
SP.epgCacheHash = 0

Expand All @@ -115,7 +108,7 @@ class EpgRepositoryImpl(private val context: Context) : EpgRepository {
private suspend fun parseFromXml(
xmlString: String,
filteredChannels: List<String> = emptyList(),
) = withContext(Dispatchers.IO) {
) = withContext(Dispatchers.Default) {
val parser: XmlPullParser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(StringReader(xmlString))
Expand Down Expand Up @@ -144,8 +137,9 @@ class EpgRepositoryImpl(private val context: Context) : EpgRepository {
fun parseTime(time: String): Long {
if (time.length < 14) return 0

return SimpleDateFormat("yyyyMMddHHmmss Z", Locale.getDefault())
.parse(time)?.time ?: 0
return SimpleDateFormat("yyyyMMddHHmmss Z", Locale.getDefault()).parse(
time
)?.time ?: 0
}

if (epgMap.containsKey(channelId)) {
Expand Down Expand Up @@ -176,15 +170,19 @@ class EpgRepositoryImpl(private val context: Context) : EpgRepository {
return File(context.cacheDir, "epg.json")
}

private fun getCache(): EpgList? {
private suspend fun getCache() = withContext(Dispatchers.IO) {
val file = getCacheFile()
return if (file.exists()) {
if (file.exists()) {
EpgList(Json.decodeFromString<List<Epg>>(file.readText()))
} else {
null
}
}

private suspend fun setCache(epgList: EpgList) = withContext(Dispatchers.IO) {
getCacheFile().writeText(Json.encodeToString(epgList.value))
}

companion object {
const val TAG = "EpgRepositoryImpl"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ 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) {
override suspend fun latestRelease() = withContext(Dispatchers.IO) {
Log.d(TAG, "获取最新release: ${Constants.GITHUB_RELEASE_LATEST_URL}")

val client = OkHttpClient()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package top.yogiczy.mytv.data.repositories

import kotlinx.coroutines.flow.Flow
import top.yogiczy.mytv.data.entities.IptvGroupList

interface IptvRepository {
fun getIptvGroups(): Flow<IptvGroupList>
suspend fun getIptvGroups(): IptvGroupList
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package top.yogiczy.mytv.data.repositories
import android.content.Context
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
Expand All @@ -20,25 +18,22 @@ import javax.inject.Singleton

@Singleton
class IptvRepositoryImpl(private val context: Context) : IptvRepository {
override fun getIptvGroups() = flow {
override suspend fun getIptvGroups(): IptvGroupList {
val now = System.currentTimeMillis()

if (now - SP.iptvSourceCacheTime < 24 * 60 * 60 * 1000) {
val cache = getCache()
if (cache.isNotBlank()) {
Log.d(TAG, "使用缓存直播源")
emit(parseSource(cache))
return@flow
return parseSource(cache)
}
}

val data = fetchSource()

getCacheFile().writeText(data)
setCache(data)
SP.iptvSourceCacheTime = now

emit(parseSource(data))
return@flow
return parseSource(data)
}

private fun getSource() = SP.iptvCustomSource.ifBlank { Constants.IPTV_SOURCE_URL }
Expand All @@ -47,8 +42,8 @@ class IptvRepositoryImpl(private val context: Context) : IptvRepository {
return if (getSource().endsWith(".m3u")) SourceType.M3U else SourceType.TVBOX
}

private suspend fun fetchSource(retry: Int = 0): String = withContext(Dispatchers.IO) {
Log.d(TAG, "获取远程直播源($retry): ${getSource()}")
private suspend fun fetchSource(): String = withContext(Dispatchers.IO) {
Log.d(TAG, "获取远程直播源: ${getSource()}")

val client = OkHttpClient()
val request = Request.Builder().url(getSource()).build()
Expand All @@ -63,10 +58,6 @@ class IptvRepositoryImpl(private val context: Context) : IptvRepository {
}
} catch (e: Exception) {
Log.e(TAG, "获取远程直播源失败", e)
if (retry < 10) {
delay(3_000)
return@withContext fetchSource(retry + 1)
}
throw Exception("获取远程直播源失败,请检查网络连接", e.cause)
}
}
Expand All @@ -76,8 +67,12 @@ class IptvRepositoryImpl(private val context: Context) : IptvRepository {
else File(context.cacheDir, "iptv-tvbox.txt")
}

private fun getCache(): String {
return if (getCacheFile().exists()) getCacheFile().readText() else ""
private suspend fun getCache() = withContext(Dispatchers.IO) {
if (getCacheFile().exists()) getCacheFile().readText() else ""
}

private suspend fun setCache(data: String) = withContext(Dispatchers.IO) {
getCacheFile().writeText(data)
}

private fun parseSourceM3u(data: String): IptvGroupList {
Expand All @@ -88,10 +83,8 @@ class IptvRepositoryImpl(private val context: Context) : IptvRepository {
if (!line.startsWith("#EXTINF")) return@forEachIndexed

val name = line.split(",").last()
val channelName =
Regex("tvg-name=\"(.+?)\"").find(line)?.groupValues?.get(1) ?: name
val groupName =
Regex("group-title=\"(.+?)\"").find(line)?.groupValues?.get(1) ?: "其他"
val channelName = Regex("tvg-name=\"(.+?)\"").find(line)?.groupValues?.get(1) ?: name
val groupName = Regex("group-title=\"(.+?)\"").find(line)?.groupValues?.get(1) ?: "其他"

if (SP.iptvSourceSimplify) {
if (!name.lowercase().startsWith("cctv") && !name.lowercase()
Expand Down
30 changes: 21 additions & 9 deletions app/src/main/java/top/yogiczy/mytv/ui/screens/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fun HomeScreen(
)
}

is HomeScreenUiState.Loading -> HomeScreenLoading()
is HomeScreenUiState.Loading -> HomeScreenLoading(s.message)
is HomeScreenUiState.Error -> HomeScreenError(s.message)
}
}
Expand All @@ -57,28 +57,40 @@ fun AppBanner(modifier: Modifier = Modifier) {

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
private fun HomeScreenLoading(modifier: Modifier = Modifier) {
private fun HomeScreenLoading(message: String?) {
val childPadding = rememberChildPadding()

Box(modifier = modifier.fillMaxSize()) {
Box(modifier = Modifier.fillMaxSize()) {
AppBanner(modifier = Modifier.align(Alignment.Center))

Text(
text = "加载中...",
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
Column(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(start = childPadding.start, bottom = childPadding.bottom),
)
) {
Text(
text = "加载中...",
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
)

if (message != null) {
Text(
text = message,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f),
modifier = Modifier.sizeIn(maxWidth = 500.dp),
)
}
}
}
}

@Preview(device = "id:Android TV (720p)")
@Composable
private fun HomeScreenLoadingPreview() {
MyTVTheme {
HomeScreenLoading()
HomeScreenLoading("获取远程直播源(2/10)...")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.retry
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.launch
import top.yogiczy.mytv.data.entities.EpgList
import top.yogiczy.mytv.data.entities.EpgProgrammeList
Expand All @@ -23,21 +27,31 @@ class HomeScreeViewModel @Inject constructor(
iptvRepository: IptvRepository,
epgRepository: EpgRepository,
) : ViewModel() {
var uiState = mutableStateOf<HomeScreenUiState>(HomeScreenUiState.Loading)
var uiState = mutableStateOf<HomeScreenUiState>(HomeScreenUiState.Loading(""))

init {
viewModelScope.launch {
iptvRepository.getIptvGroups()
flow { emit(iptvRepository.getIptvGroups()) }
.retryWhen { _, attempt ->
if (attempt >= 10) return@retryWhen false

uiState.value =
HomeScreenUiState.Loading("获取远程直播源(${attempt + 1}/10)...")
delay(3000)
true
}
.catch { uiState.value = HomeScreenUiState.Error(it.message) }
.map {
uiState.value = HomeScreenUiState.Ready(iptvGroupList = it)
it
}
// 开始获取epg
.flatMapLatest { iptvGroupList ->
val channels =
iptvGroupList.flatMap { it.iptvs }.map { iptv -> iptv.channelName }
epgRepository.getEpgs(channels)
flow { emit(epgRepository.getEpgs(channels)) }
}
.retry(10) { delay(3000); true }
.catch { emit(EpgList()) }
.map { epgList ->
// 移除过期节目
Expand All @@ -63,7 +77,7 @@ class HomeScreeViewModel @Inject constructor(
}

sealed interface HomeScreenUiState {
data object Loading : HomeScreenUiState
data class Loading(val message: String?) : HomeScreenUiState
data class Error(val message: String?) : HomeScreenUiState
data class Ready(
val iptvGroupList: IptvGroupList = IptvGroupList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ fun SettingsList(
value = if (updateState.isUpdateAvailable) "新版本" else "无更新",
description = "最新版本:${updateState.latestRelease.tagName}" + if (updateState.isUpdateAvailable) "(长按更新)" else "",
onClick = {
if (updateState.isUpdateAvailable) showDialog = true
if (updateState.isUpdateAvailable) {
showDialog = true
}
},
onLongClick = {
coroutineScope.launch {
Expand Down
Loading

0 comments on commit 30793d8

Please sign in to comment.