|
@@ -0,0 +1,150 @@
|
|
|
|
|
+package io.legado.app.ui.about
|
|
|
|
|
+
|
|
|
|
|
+import android.app.Application
|
|
|
|
|
+import android.net.Uri
|
|
|
|
|
+import android.os.Bundle
|
|
|
|
|
+import android.view.MenuItem
|
|
|
|
|
+import android.view.View
|
|
|
|
|
+import android.view.ViewGroup
|
|
|
|
|
+import androidx.appcompat.widget.Toolbar
|
|
|
|
|
+import androidx.fragment.app.viewModels
|
|
|
|
|
+import androidx.lifecycle.MutableLiveData
|
|
|
|
|
+import androidx.recyclerview.widget.LinearLayoutManager
|
|
|
|
|
+import io.legado.app.R
|
|
|
|
|
+import io.legado.app.base.BaseDialogFragment
|
|
|
|
|
+import io.legado.app.base.BaseViewModel
|
|
|
|
|
+import io.legado.app.base.adapter.ItemViewHolder
|
|
|
|
|
+import io.legado.app.base.adapter.RecyclerAdapter
|
|
|
|
|
+import io.legado.app.databinding.DialogRecyclerViewBinding
|
|
|
|
|
+import io.legado.app.databinding.Item1lineTextBinding
|
|
|
|
|
+import io.legado.app.help.config.AppConfig
|
|
|
|
|
+import io.legado.app.lib.theme.primaryColor
|
|
|
|
|
+import io.legado.app.ui.widget.dialog.TextDialog
|
|
|
|
|
+import io.legado.app.utils.*
|
|
|
|
|
+import io.legado.app.utils.viewbindingdelegate.viewBinding
|
|
|
|
|
+import kotlinx.coroutines.isActive
|
|
|
|
|
+import java.io.FileFilter
|
|
|
|
|
+
|
|
|
|
|
+class CrashLogsDialog : BaseDialogFragment(R.layout.dialog_recycler_view),
|
|
|
|
|
+ Toolbar.OnMenuItemClickListener {
|
|
|
|
|
+
|
|
|
|
|
+ private val binding by viewBinding(DialogRecyclerViewBinding::bind)
|
|
|
|
|
+ private val viewModel by viewModels<CrashViewModel>()
|
|
|
|
|
+ private val adapter by lazy { LogAdapter() }
|
|
|
|
|
+
|
|
|
|
|
+ override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
|
+ binding.toolBar.setBackgroundColor(primaryColor)
|
|
|
|
|
+ binding.toolBar.setTitle(R.string.crash_log)
|
|
|
|
|
+ binding.toolBar.inflateMenu(R.menu.crash_log)
|
|
|
|
|
+ binding.toolBar.setOnMenuItemClickListener(this)
|
|
|
|
|
+ binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
|
|
|
|
+ binding.recyclerView.adapter = adapter
|
|
|
|
|
+ viewModel.logLiveData.observe(viewLifecycleOwner) {
|
|
|
|
|
+ adapter.setItems(it)
|
|
|
|
|
+ }
|
|
|
|
|
+ viewModel.initData()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun onMenuItemClick(item: MenuItem): Boolean {
|
|
|
|
|
+ when (item.itemId) {
|
|
|
|
|
+ R.id.menu_clear -> viewModel.clearCrashLog()
|
|
|
|
|
+ }
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun showLogFile(fileDoc: FileDoc) {
|
|
|
|
|
+ viewModel.readFile(fileDoc) {
|
|
|
|
|
+ if (isActive) {
|
|
|
|
|
+ showDialogFragment(TextDialog(fileDoc.name, it))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ inner class LogAdapter : RecyclerAdapter<FileDoc, Item1lineTextBinding>(requireContext()) {
|
|
|
|
|
+
|
|
|
|
|
+ override fun getViewBinding(parent: ViewGroup): Item1lineTextBinding {
|
|
|
|
|
+ return Item1lineTextBinding.inflate(inflater, parent, false)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun registerListener(holder: ItemViewHolder, binding: Item1lineTextBinding) {
|
|
|
|
|
+ binding.root.setOnClickListener {
|
|
|
|
|
+ getItemByLayoutPosition(holder.layoutPosition)?.let { item ->
|
|
|
|
|
+ showLogFile(item)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun convert(
|
|
|
|
|
+ holder: ItemViewHolder,
|
|
|
|
|
+ binding: Item1lineTextBinding,
|
|
|
|
|
+ item: FileDoc,
|
|
|
|
|
+ payloads: MutableList<Any>
|
|
|
|
|
+ ) {
|
|
|
|
|
+ binding.textView.text = item.name
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ class CrashViewModel(application: Application) : BaseViewModel(application) {
|
|
|
|
|
+
|
|
|
|
|
+ val logLiveData = MutableLiveData<List<FileDoc>>()
|
|
|
|
|
+
|
|
|
|
|
+ fun initData() {
|
|
|
|
|
+ execute {
|
|
|
|
|
+ val list = arrayListOf<FileDoc>()
|
|
|
|
|
+ context.externalCacheDir
|
|
|
|
|
+ ?.getFile("crash")
|
|
|
|
|
+ ?.listFiles(FileFilter { it.isFile })
|
|
|
|
|
+ ?.forEach {
|
|
|
|
|
+ list.add(FileDoc.fromFile(it))
|
|
|
|
|
+ }
|
|
|
|
|
+ val backupPath = AppConfig.backupPath
|
|
|
|
|
+ if (!backupPath.isNullOrEmpty()) {
|
|
|
|
|
+ val uri = Uri.parse(backupPath)
|
|
|
|
|
+ FileDoc.fromUri(uri, true).list {
|
|
|
|
|
+ !it.isDir
|
|
|
|
|
+ }?.let {
|
|
|
|
|
+ list.addAll(it)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return@execute list.sortedByDescending { it.name }
|
|
|
|
|
+ }.onSuccess {
|
|
|
|
|
+ logLiveData.postValue(it)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fun readFile(fileDoc: FileDoc, success: (String) -> Unit) {
|
|
|
|
|
+ execute {
|
|
|
|
|
+ String(fileDoc.readBytes())
|
|
|
|
|
+ }.onSuccess {
|
|
|
|
|
+ success.invoke(it)
|
|
|
|
|
+ }.onError {
|
|
|
|
|
+ context.toastOnUi(it.localizedMessage)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fun clearCrashLog() {
|
|
|
|
|
+ execute {
|
|
|
|
|
+ context.externalCacheDir
|
|
|
|
|
+ ?.getFile("crash")
|
|
|
|
|
+ ?.let {
|
|
|
|
|
+ FileUtils.delete(it, false)
|
|
|
|
|
+ }
|
|
|
|
|
+ val backupPath = AppConfig.backupPath
|
|
|
|
|
+ if (!backupPath.isNullOrEmpty()) {
|
|
|
|
|
+ val uri = Uri.parse(backupPath)
|
|
|
|
|
+ FileDoc.fromUri(uri, true).list()?.let {
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }.onError {
|
|
|
|
|
+ context.toastOnUi(it.localizedMessage)
|
|
|
|
|
+ }.onFinally {
|
|
|
|
|
+ initData()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|