Refactor logcat handling to use LogcatViewModel

This commit is contained in:
2dust committed 2026-01-20 10:46:46 +08:00
1 parent bb1f677d45
commit 41b0983582
3 files changed
+103 -85

No files matched your search

@@ -2,37 +2,34 @@ package com.v2ray.ang.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityLogcatBinding
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.LogcatViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
private val binding by lazy { ActivityLogcatBinding.inflate(layoutInflater) }
private var logsetsAll: MutableList<String> = mutableListOf()
var logsets: MutableList<String> = mutableListOf()
private val adapter by lazy { LogcatRecyclerAdapter(this) }
private val viewModel: LogcatViewModel by viewModels()
private lateinit var adapter: LogcatRecyclerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//setContentView(binding.root)
setContentViewWithToolbar(binding.root, showHomeAsUp = true, title = getString(R.string.title_logcat))
adapter = LogcatRecyclerAdapter(viewModel, ::onLogLongClick)
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
addCustomDividerToRecyclerView(binding.recyclerView, this, R.drawable.custom_divider)
@@ -40,58 +37,12 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
binding.refreshLayout.setOnRefreshListener(this)
logsets.add(getString(R.string.pull_down_to_refresh))
toast(getString(R.string.pull_down_to_refresh))
}
private fun getLogcat() {
try {
binding.refreshLayout.isRefreshing = true
lifecycleScope.launch(Dispatchers.Default) {
val lst = LinkedHashSet<String>()
lst.add("logcat")
lst.add("-d")
lst.add("-v")
lst.add("time")
lst.add("-s")
lst.add("GoLog,${ANG_PACKAGE},AndroidRuntime,System.err")
val process = withContext(Dispatchers.IO) {
Runtime.getRuntime().exec(lst.toTypedArray())
}
val allText = process.inputStream.bufferedReader().use { it.readLines() }.reversed()
launch(Dispatchers.Main) {
logsetsAll = allText.toMutableList()
logsets = allText.toMutableList()
refreshData()
binding.refreshLayout.isRefreshing = false
}
}
} catch (e: IOException) {
Log.e(AppConfig.TAG, "Failed to get logcat", e)
}
}
private fun clearLogcat() {
try {
lifecycleScope.launch(Dispatchers.Default) {
val lst = LinkedHashSet<String>()
lst.add("logcat")
lst.add("-c")
withContext(Dispatchers.IO) {
val process = Runtime.getRuntime().exec(lst.toTypedArray())
process.waitFor()
}
launch(Dispatchers.Main) {
logsetsAll.clear()
logsets.clear()
refreshData()
}
}
} catch (e: IOException) {
Log.e(AppConfig.TAG, "Failed to clear logcat", e)
}
private fun onLogLongClick(log: String): Boolean {
Utils.setClipboard(this, log)
return true
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -104,12 +55,14 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
override fun onQueryTextSubmit(query: String?): Boolean = false
override fun onQueryTextChange(newText: String?): Boolean {
filterLogs(newText)
viewModel.filter(newText)
refreshData()
return false
}
})
searchView.setOnCloseListener {
filterLogs("")
viewModel.filter("")
refreshData()
false
}
}
@@ -119,33 +72,33 @@ class LogcatActivity : BaseActivity(), SwipeRefreshLayout.OnRefreshListener {
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.copy_all -> {
Utils.setClipboard(this, logsets.joinToString("\n"))
val all = viewModel.getAll().joinToString("\n")
Utils.setClipboard(this, all)
toastSuccess(R.string.toast_success)
true
}
R.id.clear_all -> {
clearLogcat()
lifecycleScope.launch(Dispatchers.IO) {
viewModel.clearLogcat()
withContext(Dispatchers.Main) {
refreshData()
}
}
true
}
else -> super.onOptionsItemSelected(item)
}
private fun filterLogs(content: String?): Boolean {
val key = content?.trim()
logsets = if (key.isNullOrEmpty()) {
logsetsAll.toMutableList()
} else {
logsetsAll.filter { it.contains(key) }.toMutableList()
}
refreshData()
return true
}
override fun onRefresh() {
getLogcat()
lifecycleScope.launch(Dispatchers.IO) {
viewModel.loadLogcat()
withContext(Dispatchers.Main) {
binding.refreshLayout.isRefreshing = false
refreshData()
}
}
}
@SuppressLint("NotifyDataSetChanged")
@@ -6,17 +6,20 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.v2ray.ang.AppConfig
import com.v2ray.ang.databinding.ItemRecyclerLogcatBinding
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.LogcatViewModel
class LogcatRecyclerAdapter(val activity: LogcatActivity) : RecyclerView.Adapter<LogcatRecyclerAdapter.MainViewHolder>() {
private var mActivity: LogcatActivity = activity
class LogcatRecyclerAdapter(
private val viewModel: LogcatViewModel,
private val onLongClick: ((String) -> Boolean)? = null
) : RecyclerView.Adapter<LogcatRecyclerAdapter.MainViewHolder>() {
override fun getItemCount() = mActivity.logsets.size
override fun getItemCount() = viewModel.getAll().size
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
try {
val log = mActivity.logsets[position]
val logs = viewModel.getAll()
val log = logs[position]
if (log.isEmpty()) {
holder.itemSubSettingBinding.logTag.text = ""
holder.itemSubSettingBinding.logContent.text = ""
@@ -27,8 +30,7 @@ class LogcatRecyclerAdapter(val activity: LogcatActivity) : RecyclerView.Adapter
}
holder.itemView.setOnLongClickListener {
Utils.setClipboard(mActivity, log)
true
onLongClick?.invoke(log) ?: false
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Error binding log view data", e)
@@ -0,0 +1,63 @@
package com.v2ray.ang.viewmodel
import androidx.lifecycle.ViewModel
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import java.io.IOException
import java.util.LinkedHashSet
class LogcatViewModel : ViewModel() {
private val logsetsAll: MutableList<String> = mutableListOf()
private var filteredLogs: List<String> = emptyList()
private var currentFilter: String = ""
fun getAll(): List<String> = filteredLogs
fun loadLogcat() {
try {
val lst = LinkedHashSet<String>()
lst.add("logcat")
lst.add("-d")
lst.add("-v")
lst.add("time")
lst.add("-s")
lst.add("GoLog,${ANG_PACKAGE},AndroidRuntime,System.err")
val process = Runtime.getRuntime().exec(lst.toTypedArray())
val allText = process.inputStream.bufferedReader().use { it.readLines() }.reversed()
logsetsAll.clear()
logsetsAll.addAll(allText)
applyFilter()
} catch (e: IOException) {
android.util.Log.e(AppConfig.TAG, "Failed to get logcat", e)
}
}
fun clearLogcat() {
try {
val lst = LinkedHashSet<String>()
lst.add("logcat")
lst.add("-c")
val process = Runtime.getRuntime().exec(lst.toTypedArray())
process.waitFor()
logsetsAll.clear()
filteredLogs = emptyList()
} catch (e: IOException) {
android.util.Log.e(AppConfig.TAG, "Failed to clear logcat", e)
}
}
fun filter(content: String?) {
currentFilter = content?.trim() ?: ""
applyFilter()
}
private fun applyFilter() {
filteredLogs = if (currentFilter.isEmpty()) {
logsetsAll.toList()
} else {
logsetsAll.filter { it.contains(currentFilter) }
}
}
}