diff --git a/HISTORY.md b/HISTORY.md index e577712..421a6b0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ ## 更新日誌 +### v1.3.7.7 + +* 修復閃退問題 + ### v1.3.7.6 * 增加錯誤上報 diff --git a/README.md b/README.md index fe468c0..14f5611 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ adb install my-tv-0.apk * 多源管理 * 如果上次播放頻道不在收藏? * 當list為空,顯示group +* 默認頻道菜單顯示 ## 讚賞 diff --git a/app/src/main/java/com/lizongying/mytv0/ChannelFragment.kt b/app/src/main/java/com/lizongying/mytv0/ChannelFragment.kt index 1b3456d..9937aff 100644 --- a/app/src/main/java/com/lizongying/mytv0/ChannelFragment.kt +++ b/app/src/main/java/com/lizongying/mytv0/ChannelFragment.kt @@ -1,5 +1,6 @@ package com.lizongying.mytv0 +import MainViewModel import android.os.Bundle import android.os.Handler import android.view.LayoutInflater @@ -8,8 +9,8 @@ import android.view.ViewGroup import androidx.core.view.marginEnd import androidx.core.view.marginTop import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider import com.lizongying.mytv0.databinding.ChannelBinding -import com.lizongying.mytv0.models.TVList import com.lizongying.mytv0.models.TVModel class ChannelFragment : Fragment() { @@ -21,6 +22,8 @@ class ChannelFragment : Fragment() { private var channel = 0 private var channelCount = 0 + private lateinit var viewModel: MainViewModel + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -47,6 +50,12 @@ class ChannelFragment : Fragment() { return binding.root } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val context = requireActivity() + viewModel = ViewModelProvider(context)[MainViewModel::class.java] + } + fun show(tvViewModel: TVModel) { handler.removeCallbacks(hideRunnable) handler.removeCallbacks(playRunnable) @@ -56,7 +65,7 @@ class ChannelFragment : Fragment() { } fun show(channel: String) { - if (TVList.groupModel.getCurrent()!!.tv.id > 10 && TVList.groupModel.getCurrent()!!.tv.id == this.channel - 1) { + if (viewModel.groupModel.getCurrent()!!.tv.id > 10 && viewModel.groupModel.getCurrent()!!.tv.id == this.channel - 1) { this.channel = 0 channelCount = 0 } diff --git a/app/src/main/java/com/lizongying/mytv0/InitializerProvider.kt b/app/src/main/java/com/lizongying/mytv0/InitializerProvider.kt index 9ec0ff1..3d5a901 100644 --- a/app/src/main/java/com/lizongying/mytv0/InitializerProvider.kt +++ b/app/src/main/java/com/lizongying/mytv0/InitializerProvider.kt @@ -3,14 +3,12 @@ package com.lizongying.mytv0 import android.content.ContentProvider import android.content.ContentValues import android.net.Uri -import com.lizongying.mytv0.models.TVList internal class InitializerProvider : ContentProvider() { // Happens before Application#onCreate.It's fine to init something here override fun onCreate(): Boolean { SP.init(context!!) - TVList.init(context!!) return true } diff --git a/app/src/main/java/com/lizongying/mytv0/MainActivity.kt b/app/src/main/java/com/lizongying/mytv0/MainActivity.kt index 04aeda8..fc5b545 100644 --- a/app/src/main/java/com/lizongying/mytv0/MainActivity.kt +++ b/app/src/main/java/com/lizongying/mytv0/MainActivity.kt @@ -21,7 +21,6 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider -import com.lizongying.mytv0.models.TVList import java.util.Locale @@ -98,6 +97,7 @@ class MainActivity : AppCompatActivity() { setContentView(R.layout.activity_main) viewModel = ViewModelProvider(this)[MainViewModel::class.java] + viewModel.init(this) if (savedInstanceState == null) { supportFragmentManager.beginTransaction() @@ -133,11 +133,11 @@ class MainActivity : AppCompatActivity() { fun ready(tag: String) { Log.i(TAG, "ready $tag") ok++ - if (ok == 3) { + if (ok == 4) { Log.i(TAG, "watch") - TVList.groupModel.change.observe(this) { _ -> + viewModel.groupModel.change.observe(this) { _ -> Log.i(TAG, "groupModel changed") - if (TVList.groupModel.tvGroup.value != null) { + if (viewModel.groupModel.tvGroup.value != null) { watch() Log.i(TAG, "menuFragment update") menuFragment.update() @@ -151,22 +151,22 @@ class MainActivity : AppCompatActivity() { // "播放收藏频道".showToast() // } - - val prevGroup = TVList.groupModel.positionValue - Log.i(TAG, "SP.channel ${SP.channel}") - val tvModel = if (SP.channel > 0) { - val position = if (SP.channel < TVList.listModel.size) { - // R.string.play_default_channel.showToast() - SP.channel - 1 - - } else { - // R.string.default_channel_out_of_range.showToast() - SP.channel = 0 - 0 - } - TVList.groupModel.getPosition(position) - } else { - Log.i(TAG, "group ${TVList.groupModel.positionValue}") + viewModel.channelsOk.observe(this) { it -> + if (it) { + val prevGroup = viewModel.groupModel.positionValue + Log.i(TAG, "SP.channel ${SP.channel}") + val tvModel = if (SP.channel > 0) { + val position = if (SP.channel < viewModel.listModel.size) { + // R.string.play_default_channel.showToast() + SP.channel - 1 + } else { + // R.string.default_channel_out_of_range.showToast() + SP.channel = 0 + 0 + } + viewModel.groupModel.getPosition(position) + } else { + Log.i(TAG, "group ${viewModel.groupModel.positionValue}") // if (SP.position < 0 || SP.position >= TVList.groupModel.getTVListModelNotFilter(1)!! // .size() // ) { @@ -176,33 +176,38 @@ class MainActivity : AppCompatActivity() { // // R.string.play_last_channel.showToast() // SP.position // } - TVList.groupModel.getCurrent() - } - tvModel?.setReady() - Log.i(TAG, "ready tv ${tvModel!!.tv.name}") - TVList.groupModel.setPositionPlaying(TVList.groupModel.positionValue) - TVList.groupModel.getTVListModelNotFilter(TVList.groupModel.positionValue)?.let { - Log.i(TAG, "list name ${it.getName()}") - it.setPositionPlaying(it.positionValue) - } - - val currentGroup = TVList.groupModel.positionValue - if (currentGroup != prevGroup) { - Log.i(TAG, "group change") - menuFragment.updateList(currentGroup) - } + viewModel.groupModel.getCurrent() + } + tvModel?.setReady() + viewModel.groupModel.setPositionPlaying(viewModel.groupModel.positionValue) + viewModel.groupModel.getTVListModelNotFilter(viewModel.groupModel.positionValue) + ?.let { + Log.i(TAG, "list name ${it.getName()}") + it.setPositionPlaying(it.positionValue) + } + + val currentGroup = viewModel.groupModel.positionValue + if (currentGroup != prevGroup) { + Log.i(TAG, "group change") + menuFragment.updateList(currentGroup) + } - TVList.groupModel.isInLikeMode = SP.defaultLike && TVList.groupModel.positionValue == 0 - if (TVList.groupModel.isInLikeMode) { + viewModel.groupModel.isInLikeMode = + SP.defaultLike && viewModel.groupModel.positionValue == 0 + if (viewModel.groupModel.isInLikeMode) { // R.string.favorite_mode.showToast() - } else { + } else { // R.string.standard_mode.showToast() + } + + // TODO group position + viewModel.updateEPG() + } } - // TODO group position - viewModel.updateEPG() + server = SimpleServer(this, viewModel) - server = SimpleServer(this) + viewModel.updateConfig() } } @@ -211,7 +216,7 @@ class MainActivity : AppCompatActivity() { } private fun watch() { - TVList.listModel.forEach { tvModel -> + viewModel.listModel.forEach { tvModel -> tvModel.errInfo.observe(this) { _ -> if (tvModel.errInfo.value != null @@ -252,9 +257,9 @@ class MainActivity : AppCompatActivity() { if (tvModel.like.value != null && tvModel.tv.id != -1) { val liked = tvModel.like.value as Boolean if (liked) { - TVList.groupModel.getTVListModel(0)?.replaceTVModel(tvModel) + viewModel.groupModel.getTVListModel(0)?.replaceTVModel(tvModel) } else { - TVList.groupModel.getTVListModel(0)?.removeTVModel(tvModel.tv.id) + viewModel.groupModel.getTVListModel(0)?.removeTVModel(tvModel.tv.id) } SP.setLike(tvModel.tv.id, liked) } @@ -366,7 +371,7 @@ class MainActivity : AppCompatActivity() { } fun onPlayEnd() { - val tvModel = TVList.groupModel.getCurrent()!! + val tvModel = viewModel.groupModel.getCurrent()!! if (SP.repeatInfo) { infoFragment.show(tvModel) if (SP.channelNum) { @@ -376,18 +381,18 @@ class MainActivity : AppCompatActivity() { } fun play(position: Int) { - if (position > -1 && position < TVList.groupModel.getTVListModelNotFilter(1)!!.size()) { - val prevGroup = TVList.groupModel.positionValue - val tvModel = TVList.groupModel.getPosition(position) + if (position > -1 && position < viewModel.groupModel.getTVListModelNotFilter(1)!!.size()) { + val prevGroup = viewModel.groupModel.positionValue + val tvModel = viewModel.groupModel.getPosition(position) tvModel!!.setReady() Log.i(TAG, "play tv ${tvModel.tv.name}") - TVList.groupModel.setPositionPlaying(TVList.groupModel.positionValue) - TVList.groupModel.getTVListModelNotFilter(TVList.groupModel.positionValue)?.let { + viewModel.groupModel.setPositionPlaying(viewModel.groupModel.positionValue) + viewModel.groupModel.getTVListModelNotFilter(viewModel.groupModel.positionValue)?.let { it.setPositionPlaying(it.positionValue) } - val currentGroup = TVList.groupModel.positionValue + val currentGroup = viewModel.groupModel.positionValue if (currentGroup != prevGroup) { menuFragment.updateList(currentGroup) } @@ -397,50 +402,50 @@ class MainActivity : AppCompatActivity() { } fun prev() { - val prevGroup = TVList.groupModel.positionValue + val prevGroup = viewModel.groupModel.positionValue val tvModel = - if (SP.defaultLike && TVList.groupModel.isInLikeMode && TVList.groupModel.getTVListModel( + if (SP.defaultLike && viewModel.groupModel.isInLikeMode && viewModel.groupModel.getTVListModel( 0 ) != null ) { - TVList.groupModel.getPrev(true) + viewModel.groupModel.getPrev(true) } else { - TVList.groupModel.getPrev() + viewModel.groupModel.getPrev() } tvModel!!.setReady() Log.i(TAG, "tv ${tvModel.tv.name}") - TVList.groupModel.setPositionPlaying(TVList.groupModel.positionValue) - TVList.groupModel.getTVListModelNotFilter(TVList.groupModel.positionValue)?.let { + viewModel.groupModel.setPositionPlaying(viewModel.groupModel.positionValue) + viewModel.groupModel.getTVListModelNotFilter(viewModel.groupModel.positionValue)?.let { it.setPositionPlaying(it.positionValue) } - val currentGroup = TVList.groupModel.positionValue + val currentGroup = viewModel.groupModel.positionValue if (currentGroup != prevGroup) { menuFragment.updateList(currentGroup) } } fun next() { - val prevGroup = TVList.groupModel.positionValue + val prevGroup = viewModel.groupModel.positionValue val tvModel = - if (SP.defaultLike && TVList.groupModel.isInLikeMode && TVList.groupModel.getTVListModel( + if (SP.defaultLike && viewModel.groupModel.isInLikeMode && viewModel.groupModel.getTVListModel( 0 ) != null ) { - TVList.groupModel.getNext(true) + viewModel.groupModel.getNext(true) } else { - TVList.groupModel.getNext() + viewModel.groupModel.getNext() } tvModel!!.setReady() Log.i(TAG, "tv ${tvModel.tv.name}") - TVList.groupModel.setPositionPlaying(TVList.groupModel.positionValue) - TVList.groupModel.getTVListModelNotFilter(TVList.groupModel.positionValue)?.let { + viewModel.groupModel.setPositionPlaying(viewModel.groupModel.positionValue) + viewModel.groupModel.getTVListModelNotFilter(viewModel.groupModel.positionValue)?.let { it.setPositionPlaying(it.positionValue) } - val currentGroup = TVList.groupModel.positionValue + val currentGroup = viewModel.groupModel.positionValue if (currentGroup != prevGroup) { menuFragment.updateList(currentGroup) } diff --git a/app/src/main/java/com/lizongying/mytv0/MainViewModel.kt b/app/src/main/java/com/lizongying/mytv0/MainViewModel.kt index ec3597d..dd5776d 100644 --- a/app/src/main/java/com/lizongying/mytv0/MainViewModel.kt +++ b/app/src/main/java/com/lizongying/mytv0/MainViewModel.kt @@ -1,15 +1,44 @@ +import android.content.Context +import android.net.Uri +import android.util.Log +import androidx.core.net.toFile +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.gson.JsonSyntaxException +import com.lizongying.mytv0.R import com.lizongying.mytv0.SP import com.lizongying.mytv0.Utils.getDateFormat -import com.lizongying.mytv0.models.TVList +import com.lizongying.mytv0.models.EPGXmlParser +import com.lizongying.mytv0.models.SourceType +import com.lizongying.mytv0.models.TV +import com.lizongying.mytv0.models.TVGroupModel +import com.lizongying.mytv0.models.TVListModel +import com.lizongying.mytv0.models.TVModel +import com.lizongying.mytv0.requests.HttpClient +import com.lizongying.mytv0.showToast +import io.github.lizongying.Gua +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File class MainViewModel : ViewModel() { - + val FILE_NAME = "channels.txt" private var timeFormat = if (SP.displaySeconds) "HH:mm:ss" else "HH:mm" + private lateinit var appDirectory: File + var listModel: List = listOf() + val groupModel = TVGroupModel() + + + private val _channelsOk = MutableLiveData() + val channelsOk: LiveData + get() = _channelsOk + fun setDisplaySeconds(displaySeconds: Boolean) { timeFormat = if (displaySeconds) "HH:mm:ss" else "HH:mm" SP.displaySeconds = displaySeconds @@ -20,8 +49,349 @@ class MainViewModel : ViewModel() { } fun updateEPG() { - viewModelScope.launch { - TVList.updateEPG() + if (!SP.epg.isNullOrEmpty()) { + viewModelScope.launch { + updateEPG(SP.epg!!) + } + } + } + + fun updateConfig() { + if (SP.configAutoLoad) { + SP.config?.let { + if (it.startsWith("http")) { + viewModelScope.launch { + update(it) + SP.epg?.let { i -> + updateEPG(i) + } + } + } + } + } + } + + fun init(context: Context) { +// TVList.groupModel.addTVListModel(TVListModel("我的收藏", 0)) +// TVList.groupModel.addTVListModel(TVListModel("全部頻道", 1)) + + groupModel.addTVListModel(TVListModel("我的收藏", 0)) + groupModel.addTVListModel(TVListModel("全部頻道", 1)) + + + appDirectory = context.filesDir + val file = File(appDirectory, FILE_NAME) + val str = if (file.exists()) { + file.readText() + } else { + context.resources.openRawResource(R.raw.channels).bufferedReader() + .use { it.readText() } + } + + try { + str2List(str) + } catch (e: Exception) { + e.printStackTrace() + file.deleteOnExit() + R.string.channel_read_error.showToast() } + + _channelsOk.value = true + } + + private suspend fun updateEPG(epg: String) { + try { + withContext(Dispatchers.IO) { + val request = okhttp3.Request.Builder().url(epg).build() + val response = HttpClient.okHttpClient.newCall(request).execute() + + if (response.isSuccessful) { + val res = EPGXmlParser().parse(response.body!!.byteStream()) + + withContext(Dispatchers.Main) { + for (m in listModel) { + res[m.tv.name]?.let { m.setEpg(it) } + } + } + } else { + Log.e(TAG, "EPG ${response.code}") + R.string.epg_status_err.showToast() + } + } + } catch (e: Exception) { + e.printStackTrace() + R.string.epg_request_err.showToast() + } + } + + suspend fun update(serverUrl: String) { + Log.i(TAG, "request $serverUrl") + try { + withContext(Dispatchers.IO) { + val request = okhttp3.Request.Builder().url(serverUrl).build() + val response = HttpClient.okHttpClient.newCall(request).execute() + + if (response.isSuccessful) { + val file = File(appDirectory, FILE_NAME) + if (!file.exists()) { + file.createNewFile() + } + val str = response.body!!.string() + withContext(Dispatchers.Main) { + if (str2List(str)) { + file.writeText(str) + SP.config = serverUrl + R.string.channel_import_success.showToast() + } else { + R.string.channel_import_error.showToast() + } + } + } else { + Log.e(TAG, "Request status ${response.code}") + R.string.channel_status_error.showToast() + } + } + } catch (e: JsonSyntaxException) { + e.printStackTrace() + Log.e("JSON Parse Error", e.toString()) + R.string.channel_format_error.showToast() + } catch (e: NullPointerException) { + e.printStackTrace() + Log.e("Null Pointer Error", e.toString()) + R.string.channel_read_error.showToast() + } catch (e: Exception) { + e.printStackTrace() + Log.e(TAG, "Request error $e") + R.string.channel_request_error.showToast() + } + } + + fun reset(context: Context) { +// TVList.groupModel.setPosition(0) +// TVList.groupModel.setPositionPlaying(0) + groupModel.setPosition(0) + groupModel.setPositionPlaying(0) + +// val tvListModel = TVList.groupModel.getCurrentList() + val tvListModel = groupModel.getCurrentList() + tvListModel?.setPosition(0) + tvListModel?.setPositionPlaying(0) + + val str = context.resources.openRawResource(R.raw.channels).bufferedReader() + .use { it.readText() } + + try { + str2List(str) + } catch (e: Exception) { + e.printStackTrace() + R.string.channel_read_error.showToast() + } + } + + fun parseUri(uri: Uri) { + if (uri.scheme == "file") { + val file = uri.toFile() + Log.i(TAG, "file $file") + val str = if (file.exists()) { + file.readText() + } else { + R.string.file_not_exist.showToast() + return + } + + try { + if (str2List(str)) { + SP.config = uri.toString() + R.string.channel_import_success.showToast() + } else { + R.string.channel_import_error.showToast() + } + } catch (e: Exception) { + e.printStackTrace() + file.deleteOnExit() + R.string.channel_read_error.showToast() + } + } else { + CoroutineScope(Dispatchers.IO).launch { + update(uri.toString()) + } + } + } + + fun str2List(str: String): Boolean { + var string = str + val g = Gua() + if (g.verify(str)) { + string = g.decode(str) + } + if (string.isBlank()) { + return false + } + + val list: List + + when (string[0]) { + '[' -> { + try { + val type = object : com.google.gson.reflect.TypeToken>() {}.type + list = com.google.gson.Gson().fromJson(string, type) + Log.i(TAG, "导入频道 ${list.size}") + } catch (e: Exception) { + Log.i(TAG, "parse error $string") + Log.i(TAG, e.message, e) + return false + } + } + + '#' -> { + val lines = string.lines() + val nameRegex = Regex("""tvg-name="([^"]+)"""") + val logRegex = Regex("""tvg-logo="([^"]+)"""") + val epgRegex = Regex("""x-tvg-url="([^"]+)"""") + val groupRegex = Regex("""group-title="([^"]+)"""") + + val l = mutableListOf() + val tvMap = mutableMapOf>() + for ((index, line) in lines.withIndex()) { + val trimmedLine = line.trim() + if (trimmedLine.startsWith("#EXTM3U")) { + SP.epg = epgRegex.find(trimmedLine)?.groupValues?.get(1)?.trim() + } else if (trimmedLine.startsWith("#EXTINF")) { + val info = trimmedLine.split(",") + val title = info.last().trim() + var name = nameRegex.find(info.first())?.groupValues?.get(1)?.trim() + name = name ?: title + var group = groupRegex.find(info.first())?.groupValues?.get(1)?.trim() + group = group ?: "" + val logo = logRegex.find(info.first())?.groupValues?.get(1)?.trim() + val uris = + if (index + 1 < lines.size) listOf(lines[index + 1].trim()) else emptyList() + val tv = TV( + -1, + name, + title, + "", + logo ?: "", + "", + uris, + mapOf(), + group, + SourceType.UNKNOWN, + listOf(), + ) + + if (!tvMap.containsKey(group + name)) { + tvMap[group + name] = listOf() + } + tvMap[group + name] = tvMap[group + name]!! + tv + } + } + for ((_, tv) in tvMap) { + val uris = tv.map { t -> t.uris }.flatten() + val t0 = tv[0] + val t1 = TV( + -1, + t0.name, + t0.name, + "", + t0.logo, + "", + uris, + mapOf(), + t0.group, + SourceType.UNKNOWN, + listOf(), + ) + l.add(t1) + } + list = l + Log.i(TAG, "导入频道 ${list.size}") + } + + else -> { + val lines = string.lines() + var group = "" + val l = mutableListOf() + val tvMap = mutableMapOf>() + for (line in lines) { + val trimmedLine = line.trim() + if (trimmedLine.isNotEmpty()) { + if (trimmedLine.contains("#genre#")) { + group = trimmedLine.split(',', limit = 2)[0].trim() + } else { + val arr = trimmedLine.split(',').map { it.trim() } + val title = arr.first().trim() + val uris = arr.drop(1) + + if (!tvMap.containsKey(group + title)) { + tvMap[group + title] = listOf() + } + tvMap[group + title] = tvMap[group + title]!! + uris + } + } + } + for ((title, uris) in tvMap) { + val tv = TV( + -1, + "", + title.removePrefix(group), + "", + "", + "", + uris, + mapOf(), + group, + SourceType.UNKNOWN, + listOf(), + ) + + l.add(tv) + } + list = l + Log.i(TAG, "导入频道 ${list.size}") + } + } + +// TVList.groupModel.initTVGroup() + groupModel.initTVGroup() + + val map: MutableMap> = mutableMapOf() + for (v in list) { + if (v.group !in map) { + map[v.group] = mutableListOf() + } + map[v.group]?.add(TVModel(v)) + } + + val listModelNew: MutableList = mutableListOf() + var groupIndex = 2 + var id = 0 + for ((k, v) in map) { + val listTVModel = TVListModel(k, groupIndex) + for ((listIndex, v1) in v.withIndex()) { + v1.tv.id = id + v1.setLike(SP.getLike(id)) + v1.setGroupIndex(groupIndex) + v1.listIndex = listIndex + listTVModel.addTVModel(v1) + listModelNew.add(v1) + id++ + } + groupModel.addTVListModel(listTVModel) + groupIndex++ + } + + listModel = listModelNew + + // 全部频道 + (groupModel.tvGroup.value as List)[1].setTVListModel(listModel) + + groupModel.setChange() + + return true + } + + companion object { + private const val TAG = "MainViewModel" } } \ No newline at end of file diff --git a/app/src/main/java/com/lizongying/mytv0/MenuFragment.kt b/app/src/main/java/com/lizongying/mytv0/MenuFragment.kt index 92dcc42..57d1ac0 100644 --- a/app/src/main/java/com/lizongying/mytv0/MenuFragment.kt +++ b/app/src/main/java/com/lizongying/mytv0/MenuFragment.kt @@ -1,5 +1,6 @@ package com.lizongying.mytv0 +import MainViewModel import android.os.Bundle import android.util.Log import android.view.KeyEvent @@ -10,9 +11,9 @@ import android.view.View.VISIBLE import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.lizongying.mytv0.databinding.MenuBinding -import com.lizongying.mytv0.models.TVList import com.lizongying.mytv0.models.TVListModel import com.lizongying.mytv0.models.TVModel @@ -26,19 +27,29 @@ class MenuFragment : Fragment(), GroupAdapter.ItemListener, ListAdapter.ItemList private var groupWidth = 0 private var listWidth = 0 + private lateinit var viewModel: MainViewModel + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - val application = requireActivity().applicationContext as MyTVApplication - val context = requireContext() + _binding = MenuBinding.inflate(inflater, container, false) + return binding.root + } - Log.i(TAG, "TVList.groupModel ${TVList.groupModel}") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val context = requireActivity() + val application = requireActivity().applicationContext as MyTVApplication + viewModel = ViewModelProvider(context)[MainViewModel::class.java] + + + Log.i(TAG, "groupModel ${viewModel.groupModel}") groupAdapter = GroupAdapter( context, binding.group, - TVList.groupModel, + viewModel.groupModel, ) binding.group.adapter = groupAdapter binding.group.layoutManager = @@ -51,15 +62,17 @@ class MenuFragment : Fragment(), GroupAdapter.ItemListener, ListAdapter.ItemList } groupAdapter.setItemListener(this) - var listTVModel = TVList.groupModel.getTVListModelNotFilter(TVList.groupModel.positionValue) + var listTVModel = + viewModel.groupModel.getTVListModelNotFilter(viewModel.groupModel.positionValue) - Log.i(TAG, "listTVModel0 ${TVList.groupModel.positionValue} $listTVModel") + Log.i(TAG, "listTVModel0 ${viewModel.groupModel.positionValue} $listTVModel") if (listTVModel == null) { - TVList.groupModel.setPosition(0) + viewModel.groupModel.setPosition(0) } - listTVModel = TVList.groupModel.getTVListModelNotFilter(TVList.groupModel.positionValue) - Log.i(TAG, "listTVModel1 ${TVList.groupModel.positionValue} $listTVModel") + listTVModel = + viewModel.groupModel.getTVListModelNotFilter(viewModel.groupModel.positionValue) + Log.i(TAG, "listTVModel1 ${viewModel.groupModel.positionValue} $listTVModel") listAdapter = ListAdapter( context, binding.list, @@ -80,23 +93,24 @@ class MenuFragment : Fragment(), GroupAdapter.ItemListener, ListAdapter.ItemList binding.menu.setOnClickListener { hideSelf() } - - return binding.root + (activity as MainActivity).ready(TAG) } fun update() { view?.post { - groupAdapter.update(TVList.groupModel) + groupAdapter.update(viewModel.groupModel) - var listTVModel = TVList.groupModel.getTVListModelNotFilter(TVList.groupModel.positionValue) + var listTVModel = + viewModel.groupModel.getTVListModelNotFilter(viewModel.groupModel.positionValue) - Log.i(TAG, "listTVModel3 ${TVList.groupModel.positionValue} $listTVModel") + Log.i(TAG, "listTVModel3 ${viewModel.groupModel.positionValue} $listTVModel") if (listTVModel == null) { - TVList.groupModel.setPosition(0) + viewModel.groupModel.setPosition(0) } - listTVModel = TVList.groupModel.getTVListModelNotFilter(TVList.groupModel.positionValue) + listTVModel = + viewModel.groupModel.getTVListModelNotFilter(viewModel.groupModel.positionValue) - Log.i(TAG, "listTVModel4 ${TVList.groupModel.positionValue} $listTVModel") + Log.i(TAG, "listTVModel4 ${viewModel.groupModel.positionValue} $listTVModel") if (listTVModel != null) { (binding.list.adapter as ListAdapter).update(listTVModel) } @@ -120,10 +134,10 @@ class MenuFragment : Fragment(), GroupAdapter.ItemListener, ListAdapter.ItemList } fun updateList(position: Int) { - TVList.groupModel.setPosition(position) + viewModel.groupModel.setPosition(position) SP.positionGroup = position - TVList.groupModel.getTVListModelNotFilter(position)?.let { + viewModel.groupModel.getTVListModelNotFilter(position)?.let { (binding.list.adapter as ListAdapter).update(it) } } @@ -154,10 +168,10 @@ class MenuFragment : Fragment(), GroupAdapter.ItemListener, ListAdapter.ItemList } override fun onItemClicked(position: Int, type: String) { - TVList.groupModel.getTVListModel()?.setPosition(position) - TVList.groupModel.getCurrent()?.setReady() - TVList.groupModel.setPositionPlaying(TVList.groupModel.positionValue) - TVList.groupModel.getTVListModelNotFilter(TVList.groupModel.positionValue)?.let { + viewModel.groupModel.getTVListModel()?.setPosition(position) + viewModel.groupModel.getCurrent()?.setReady() + viewModel.groupModel.setPositionPlaying(viewModel.groupModel.positionValue) + viewModel.groupModel.getTVListModelNotFilter(viewModel.groupModel.positionValue)?.let { it.setPositionPlaying(it.positionValue) } (activity as MainActivity).hideMenuFragment() @@ -174,8 +188,8 @@ class MenuFragment : Fragment(), GroupAdapter.ItemListener, ListAdapter.ItemList groupAdapter.focusable(false) listAdapter.focusable(true) - if (TVList.groupModel.positionPlayingValue == TVList.groupModel.positionValue) { - TVList.groupModel.getCurrentList()?.let { + if (viewModel.groupModel.positionPlayingValue == viewModel.groupModel.positionValue) { + viewModel.groupModel.getCurrentList()?.let { listAdapter.toPosition(it.positionPlayingValue) } } else { @@ -201,7 +215,7 @@ class MenuFragment : Fragment(), GroupAdapter.ItemListener, ListAdapter.ItemList listAdapter.focusable(false) listAdapter.clear() Log.i(TAG, "group toPosition on left") - groupAdapter.scrollToPositionAndSelect(TVList.groupModel.positionValue) + groupAdapter.scrollToPositionAndSelect(viewModel.groupModel.positionValue) return true } // KeyEvent.KEYCODE_DPAD_RIGHT -> { @@ -229,7 +243,7 @@ class MenuFragment : Fragment(), GroupAdapter.ItemListener, ListAdapter.ItemList // listAdapter.focusable(true) // } - if (TVList.groupModel.tvGroupValue.size < 2 || TVList.groupModel.getTVListModelNotFilter( + if (viewModel.groupModel.tvGroupValue.size < 2 || viewModel.groupModel.getTVListModelNotFilter( 1 )?.size() == 0 ) { @@ -237,21 +251,21 @@ class MenuFragment : Fragment(), GroupAdapter.ItemListener, ListAdapter.ItemList return } - val position = TVList.groupModel.positionPlayingValue - if (position != TVList.groupModel.positionValue + val position = viewModel.groupModel.positionPlayingValue + if (position != viewModel.groupModel.positionValue ) { updateList(position) } - listAdapter.toPosition(TVList.groupModel.getTVListModelNotFilter(position)!!.positionPlayingValue) + listAdapter.toPosition(viewModel.groupModel.getTVListModelNotFilter(position)!!.positionPlayingValue) } if (binding.group.isVisible) { // groupAdapter.focusable(true) // listAdapter.focusable(false) - val position = TVList.groupModel.positionPlayingValue - Log.i(TAG, "group position $position/${TVList.groupModel.tvGroupValue.size}") - if (position != TVList.groupModel.positionValue) { - TVList.groupModel.setPosition(position) + val position = viewModel.groupModel.positionPlayingValue + Log.i(TAG, "group position $position/${viewModel.groupModel.tvGroupValue.size}") + if (position != viewModel.groupModel.positionValue) { + viewModel.groupModel.setPosition(position) } groupAdapter.scrollToPositionAndSelect(position) } diff --git a/app/src/main/java/com/lizongying/mytv0/SettingFragment.kt b/app/src/main/java/com/lizongying/mytv0/SettingFragment.kt index 96c582a..61136cf 100644 --- a/app/src/main/java/com/lizongying/mytv0/SettingFragment.kt +++ b/app/src/main/java/com/lizongying/mytv0/SettingFragment.kt @@ -18,11 +18,6 @@ import androidx.core.view.marginTop import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.lizongying.mytv0.databinding.SettingBinding -import com.lizongying.mytv0.models.TVList -import com.lizongying.mytv0.models.TVList.updateEPG -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlin.math.max import kotlin.math.min @@ -105,12 +100,6 @@ class SettingFragment : Fragment() { val switchShowAllChannels = _binding?.switchShowAllChannels switchShowAllChannels?.isChecked = SP.showAllChannels - switchShowAllChannels?.setOnCheckedChangeListener { _, isChecked -> - SP.showAllChannels = isChecked - TVList.groupModel.tvGroup.value?.let { TVList.groupModel.setTVListModelList(it) } - mainActivity.update() - mainActivity.settingActive() - } val switchCompactMenu = _binding?.switchCompactMenu switchCompactMenu?.isChecked = SP.compactMenu @@ -288,6 +277,7 @@ class SettingFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val context = requireActivity() + val mainActivity = (activity as MainActivity) viewModel = ViewModelProvider(context)[MainViewModel::class.java] binding.switchDisplaySeconds.setOnCheckedChangeListener { _, isChecked -> @@ -296,19 +286,19 @@ class SettingFragment : Fragment() { binding.clear.setOnClickListener { SP.config = SP.DEFAULT_CONFIG_URL - TVList.reset(context) + viewModel.reset(context) confirmConfig() SP.channel = SP.DEFAULT_CHANNEL confirmChannel() - context.deleteFile(TVList.FILE_NAME) + context.deleteFile(viewModel.FILE_NAME) SP.deleteLike() SP.position = 0 - val tvModel = TVList.groupModel.getPosition(0) + val tvModel = viewModel.groupModel.getPosition(0) tvModel?.setReady() - TVList.groupModel.setPositionPlaying(TVList.groupModel.positionValue) - TVList.groupModel.getTVListModelNotFilter(TVList.groupModel.positionValue)?.let { + viewModel.groupModel.setPositionPlaying(viewModel.groupModel.positionValue) + viewModel.groupModel.getTVListModelNotFilter(viewModel.groupModel.positionValue)?.let { it.setPositionPlaying(it.positionValue) } SP.showAllChannels = SP.DEFAULT_SHOW_ALL_CHANNELS @@ -317,14 +307,17 @@ class SettingFragment : Fragment() { viewModel.setDisplaySeconds(SP.DEFAULT_DISPLAY_SECONDS) SP.epg = SP.DEFAULT_EPG - if (!SP.epg.isNullOrEmpty()) { - CoroutineScope(Dispatchers.IO).launch { - updateEPG() - } - } + viewModel.updateEPG() R.string.config_restored.showToast() } + + binding.switchShowAllChannels.setOnCheckedChangeListener { _, isChecked -> + SP.showAllChannels = isChecked + viewModel.groupModel.tvGroup.value?.let { viewModel.groupModel.setTVListModelList(it) } + mainActivity.update() + mainActivity.settingActive() + } } private fun confirmConfig() { @@ -342,7 +335,7 @@ class SettingFragment : Fragment() { if (uri.scheme == "file") { requestReadPermissions() } else { - TVList.parseUri(uri) + viewModel.parseUri(uri) } } else { R.string.invalid_config_address.showToast() @@ -351,7 +344,8 @@ class SettingFragment : Fragment() { } private fun confirmChannel() { - SP.channel = min(max(SP.channel, 0), TVList.groupModel.getTVListModelNotFilter(1)!!.size()) + SP.channel = + min(max(SP.channel, 0), viewModel.groupModel.getTVListModelNotFilter(1)!!.size()) (activity as MainActivity).settingActive() } @@ -434,7 +428,7 @@ class SettingFragment : Fragment() { PERMISSIONS_REQUEST_CODE ) } else { - TVList.parseUri(uri) + viewModel.parseUri(uri) } } @@ -446,7 +440,7 @@ class SettingFragment : Fragment() { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_READ_EXTERNAL_STORAGE_REQUEST_CODE) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - TVList.parseUri(uri) + viewModel.parseUri(uri) } else { R.string.authorization_failed.showToast() } diff --git a/app/src/main/java/com/lizongying/mytv0/SimpleServer.kt b/app/src/main/java/com/lizongying/mytv0/SimpleServer.kt index 9da89dd..d42b286 100644 --- a/app/src/main/java/com/lizongying/mytv0/SimpleServer.kt +++ b/app/src/main/java/com/lizongying/mytv0/SimpleServer.kt @@ -1,20 +1,21 @@ package com.lizongying.mytv0 +import MainViewModel import android.content.Context import android.net.Uri import android.os.Handler import android.os.Looper import android.util.Log import com.google.gson.Gson -import com.lizongying.mytv0.models.TVList import fi.iki.elonen.NanoHTTPD import java.io.File import java.io.IOException import java.nio.charset.StandardCharsets -class SimpleServer(private val context: Context) : NanoHTTPD(PORT) { +class SimpleServer(private val context: Context, private val viewModel: MainViewModel) : + NanoHTTPD(PORT) { private val handler = Handler(Looper.getMainLooper()) init { @@ -81,8 +82,8 @@ class SimpleServer(private val context: Context) : NanoHTTPD(PORT) { try { readBody(session)?.let { handler.post { - if (TVList.str2List(it)) { - File(context.filesDir, TVList.FILE_NAME).writeText(it) + if (viewModel.str2List(it)) { + File(context.filesDir, viewModel.FILE_NAME).writeText(it) SP.config = "file://" R.string.channel_import_success.showToast() } else { @@ -112,7 +113,7 @@ class SimpleServer(private val context: Context) : NanoHTTPD(PORT) { val uri = Uri.parse(url) Log.i(TAG, "uri $uri") handler.post { - TVList.parseUri(uri) + viewModel.parseUri(uri) } } } diff --git a/app/src/main/java/com/lizongying/mytv0/Utils.kt b/app/src/main/java/com/lizongying/mytv0/Utils.kt index 59f1bb3..494288d 100644 --- a/app/src/main/java/com/lizongying/mytv0/Utils.kt +++ b/app/src/main/java/com/lizongying/mytv0/Utils.kt @@ -8,7 +8,6 @@ import com.lizongying.mytv0.ISP.CHINA_MOBILE import com.lizongying.mytv0.ISP.CHINA_TELECOM import com.lizongying.mytv0.ISP.CHINA_UNICOM import com.lizongying.mytv0.ISP.UNKNOWN -import com.lizongying.mytv0.models.TVList import com.lizongying.mytv0.requests.TimeResponse import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/com/lizongying/mytv0/models/TVList.kt b/app/src/main/java/com/lizongying/mytv0/models/TVList.kt deleted file mode 100644 index ebd51ae..0000000 --- a/app/src/main/java/com/lizongying/mytv0/models/TVList.kt +++ /dev/null @@ -1,369 +0,0 @@ -package com.lizongying.mytv0.models - -import android.content.Context -import android.net.Uri -import android.util.Log -import androidx.core.net.toFile -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.google.gson.JsonSyntaxException -import com.lizongying.mytv0.ISP -import com.lizongying.mytv0.R -import com.lizongying.mytv0.SP -import com.lizongying.mytv0.requests.HttpClient -import com.lizongying.mytv0.showToast -import io.github.lizongying.Gua -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.File - -object TVList { - private const val TAG = "TVList" - const val FILE_NAME = "channels.txt" - private lateinit var appDirectory: File - private lateinit var serverUrl: String - private lateinit var list: List - var listModel: List = listOf() - val groupModel = TVGroupModel() - private var epg = SP.epg - - fun init(context: Context) { - groupModel.addTVListModel(TVListModel("我的收藏", 0)) - groupModel.addTVListModel(TVListModel("全部頻道", 1)) - - appDirectory = context.filesDir - val file = File(appDirectory, FILE_NAME) - val str = if (file.exists()) { - Log.i(TAG, "read $file") - file.readText() - } else { - Log.i(TAG, "read resource") - context.resources.openRawResource(R.raw.channels).bufferedReader() - .use { it.readText() } - } - - try { - str2List(str) - } catch (e: Exception) { - e.printStackTrace() - file.deleteOnExit() - R.string.channel_read_error.showToast() - } - - if (SP.configAutoLoad) { - SP.config?.let { - if (it.startsWith("http")) { - update(it) - } - } - } - } - - suspend fun updateEPG() { - try { - withContext(Dispatchers.IO) { - val request = okhttp3.Request.Builder().url(epg!!).build() - val response = HttpClient.okHttpClient.newCall(request).execute() - - if (response.isSuccessful) { - val epg = EPGXmlParser().parse(response.body!!.byteStream()) - - withContext(Dispatchers.Main) { - for (m in listModel) { - epg[m.tv.name]?.let { m.setEpg(it) } - } - } - } else { - Log.e(TAG, "EPG ${response.code}") - R.string.epg_status_err.showToast() - } - } - } catch (e: Exception) { - e.printStackTrace() - R.string.epg_request_err.showToast() - } - } - - private fun update() { - CoroutineScope(Dispatchers.IO).launch { - try { - Log.i(TAG, "request $serverUrl") - val request = okhttp3.Request.Builder().url(serverUrl).build() - val response = HttpClient.okHttpClient.newCall(request).execute() - - if (response.isSuccessful) { - val file = File(appDirectory, FILE_NAME) - if (!file.exists()) { - file.createNewFile() - } - val str = response.body!!.string() - withContext(Dispatchers.Main) { - if (str2List(str)) { - file.writeText(str) - SP.config = serverUrl - R.string.channel_import_success.showToast() - - if (!epg.isNullOrEmpty()) { - CoroutineScope(Dispatchers.IO).launch { - updateEPG() - } - } - } else { - R.string.channel_import_error.showToast() - } - } - } else { - Log.e(TAG, "Request status ${response.code}") - R.string.channel_status_error.showToast() - } - } catch (e: JsonSyntaxException) { - e.printStackTrace() - Log.e("JSON Parse Error", e.toString()) - R.string.channel_format_error.showToast() - } catch (e: NullPointerException) { - e.printStackTrace() - Log.e("Null Pointer Error", e.toString()) - R.string.channel_read_error.showToast() - } catch (e: Exception) { - e.printStackTrace() - Log.e(TAG, "Request error $e") - R.string.channel_request_error.showToast() - } - } - } - - private fun update(serverUrl: String) { - this.serverUrl = serverUrl - update() - } - - fun reset(context: Context) { - groupModel.setPosition(0) - groupModel.setPositionPlaying(0) - - val tvListModel= groupModel.getCurrentList() - tvListModel?.setPosition(0) - tvListModel?.setPositionPlaying(0) - - val str = context.resources.openRawResource(R.raw.channels).bufferedReader() - .use { it.readText() } - - try { - str2List(str) - } catch (e: Exception) { - e.printStackTrace() - R.string.channel_read_error.showToast() - } - } - - fun parseUri(uri: Uri) { - if (uri.scheme == "file") { - val file = uri.toFile() - Log.i(TAG, "file $file") - val str = if (file.exists()) { - file.readText() - } else { - R.string.file_not_exist.showToast() - return - } - - try { - if (str2List(str)) { - SP.config = uri.toString() - R.string.channel_import_success.showToast() - } else { - R.string.channel_import_error.showToast() - } - } catch (e: Exception) { - e.printStackTrace() - file.deleteOnExit() - R.string.channel_read_error.showToast() - } - } else { - update(uri.toString()) - } - } - - fun str2List(str: String): Boolean { - var string = str - val g = Gua() - if (g.verify(str)) { - string = g.decode(str) - } - if (string.isBlank()) { - return false - } - - when (string[0]) { - '[' -> { - try { - val type = object : com.google.gson.reflect.TypeToken>() {}.type - list = com.google.gson.Gson().fromJson(string, type) - Log.i(TAG, "导入频道 ${list.size}") - } catch (e: Exception) { - Log.i(TAG, "parse error $string") - Log.i(TAG, e.message, e) - return false - } - } - - '#' -> { - val lines = string.lines() - val nameRegex = Regex("""tvg-name="([^"]+)"""") - val logRegex = Regex("""tvg-logo="([^"]+)"""") - val epgRegex = Regex("""x-tvg-url="([^"]+)"""") - val groupRegex = Regex("""group-title="([^"]+)"""") - - val l = mutableListOf() - val tvMap = mutableMapOf>() - for ((index, line) in lines.withIndex()) { - val trimmedLine = line.trim() - if (trimmedLine.startsWith("#EXTM3U")) { - epg = epgRegex.find(trimmedLine)?.groupValues?.get(1)?.trim() - SP.epg = epg - } else if (trimmedLine.startsWith("#EXTINF")) { - val info = trimmedLine.split(",") - val title = info.last().trim() - var name = nameRegex.find(info.first())?.groupValues?.get(1)?.trim() - name = name ?: title - var group = groupRegex.find(info.first())?.groupValues?.get(1)?.trim() - group = group ?: "" - val logo = logRegex.find(info.first())?.groupValues?.get(1)?.trim() - val uris = - if (index + 1 < lines.size) listOf(lines[index + 1].trim()) else emptyList() - val tv = TV( - -1, - name, - title, - "", - logo ?: "", - "", - uris, - mapOf(), - group, - SourceType.UNKNOWN, - listOf(), - ) - - if (!tvMap.containsKey(group + name)) { - tvMap[group + name] = listOf() - } - tvMap[group + name] = tvMap[group + name]!! + tv - } - } - for ((_, tv) in tvMap) { - val uris = tv.map { t -> t.uris }.flatten() - val t0 = tv[0] - val t1 = TV( - -1, - t0.name, - t0.name, - "", - t0.logo, - "", - uris, - mapOf(), - t0.group, - SourceType.UNKNOWN, - listOf(), - ) - l.add(t1) - } - list = l - Log.i(TAG, "导入频道 ${list.size}") - } - - else -> { - val lines = string.lines() - var group = "" - val l = mutableListOf() - val tvMap = mutableMapOf>() - for (line in lines) { - val trimmedLine = line.trim() - if (trimmedLine.isNotEmpty()) { - if (trimmedLine.contains("#genre#")) { - group = trimmedLine.split(',', limit = 2)[0].trim() - } else { - val arr = trimmedLine.split(',').map { it.trim() } - val title = arr.first().trim() - val uris = arr.drop(1) - - if (!tvMap.containsKey(group + title)) { - tvMap[group + title] = listOf() - } - tvMap[group + title] = tvMap[group + title]!! + uris - } - } - } - for ((title, uris) in tvMap) { - val tv = TV( - -1, - "", - title.removePrefix(group), - "", - "", - "", - uris, - mapOf(), - group, - SourceType.UNKNOWN, - listOf(), - ) - - l.add(tv) - } - list = l - Log.i(TAG, "导入频道 ${list.size}") - } - } - - groupModel.initTVGroup() - - val map: MutableMap> = mutableMapOf() - for (v in list) { - if (v.group !in map) { - map[v.group] = mutableListOf() - } - map[v.group]?.add(TVModel(v)) - } - - val listModelNew: MutableList = mutableListOf() - var groupIndex = 2 - var id = 0 - for ((k, v) in map) { - val listTVModel = TVListModel(k, groupIndex) - for ((listIndex, v1) in v.withIndex()) { - v1.tv.id = id - v1.setLike(SP.getLike(id)) - v1.setGroupIndex(groupIndex) - v1.listIndex = listIndex - listTVModel.addTVModel(v1) - listModelNew.add(v1) - id++ - } - groupModel.addTVListModel(listTVModel) - groupIndex++ - } - - listModel = listModelNew - - // 全部频道 - (groupModel.tvGroup.value as List)[1].setTVListModel(listModel) - -// for (i in groupModel.tvGroup.value as List) { -//// Log.i(TAG, "tvGroup ${i.getName()} ${i.getGroupIndex()}") -// if (i.tvList.value == null) { -// continue -// } -// for (ii in i.tvList.value as List) { -//// Log.i(TAG, "tvList ${ii.tv.id} ${ii.tv.name} ${i.getGroupIndex()}") -// } -// } - - groupModel.setChange() - - return true - } -} \ No newline at end of file diff --git a/version.json b/version.json index 5f11bd9..4dcb324 100644 --- a/version.json +++ b/version.json @@ -1 +1 @@ -{"version_code": 16975622, "version_name": "v1.3.7.6"} +{"version_code": 16975623, "version_name": "v1.3.7.7"}