Rename
This commit is contained in:
1 parent
b86d90021c
commit
6c0757c91b
972 files changed
+6885
-7321
No files matched your search
@@ -0,0 +1,69 @@
|
||||
package io.legato.kazusa.ui.about
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.util.TypedValue
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseActivity
|
||||
import io.legato.kazusa.databinding.ActivityAboutBinding
|
||||
//import io.legado.app.lib.theme.accentColor
|
||||
//import io.legado.app.lib.theme.filletBackground
|
||||
import io.legato.kazusa.utils.openUrl
|
||||
import io.legato.kazusa.utils.share
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
|
||||
|
||||
class AboutActivity : BaseActivity<ActivityAboutBinding>() {
|
||||
|
||||
override val binding by viewBinding(ActivityAboutBinding::inflate)
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
//binding.llAbout.background = filletBackground
|
||||
val fTag = "aboutFragment"
|
||||
var aboutFragment = supportFragmentManager.findFragmentByTag(fTag)
|
||||
if (aboutFragment == null) aboutFragment = AboutFragment()
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.fl_fragment, aboutFragment, fTag)
|
||||
.commit()
|
||||
binding.tvAppSummary.post {
|
||||
kotlin.runCatching {
|
||||
val typedValue = TypedValue()
|
||||
val context = binding.root.context
|
||||
context.theme.resolveAttribute(
|
||||
com.google.android.material.R.attr.colorPrimary,
|
||||
typedValue,
|
||||
true
|
||||
)
|
||||
val accentColor = typedValue.data
|
||||
val spannableString = SpannableString(binding.tvAppSummary.text)
|
||||
val gzh = getString(R.string.legado_gzh)
|
||||
val start = spannableString.indexOf(gzh)
|
||||
spannableString.setSpan(
|
||||
accentColor, start, start + gzh.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
binding.tvAppSummary.text = spannableString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.about, menu)
|
||||
return super.onCompatCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_scoring -> openUrl("market://details?id=$packageName")
|
||||
R.id.menu_share_it -> share(
|
||||
getString(R.string.app_share_description),
|
||||
getString(R.string.app_name)
|
||||
)
|
||||
}
|
||||
return super.onCompatOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package io.legato.kazusa.ui.about
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.constant.AppConst.appInfo
|
||||
import io.legato.kazusa.constant.AppLog
|
||||
import io.legato.kazusa.help.CrashHandler
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.help.coroutine.Coroutine
|
||||
import io.legato.kazusa.help.update.AppUpdate
|
||||
import io.legato.kazusa.ui.widget.dialog.TextDialog
|
||||
import io.legato.kazusa.ui.widget.dialog.WaitDialog
|
||||
import io.legato.kazusa.utils.FileDoc
|
||||
import io.legato.kazusa.utils.compress.ZipUtils
|
||||
import io.legato.kazusa.utils.createFileIfNotExist
|
||||
import io.legato.kazusa.utils.createFolderIfNotExist
|
||||
import io.legato.kazusa.utils.delete
|
||||
import io.legato.kazusa.utils.externalCache
|
||||
import io.legato.kazusa.utils.find
|
||||
import io.legato.kazusa.utils.list
|
||||
import io.legato.kazusa.utils.openInputStream
|
||||
import io.legato.kazusa.utils.openOutputStream
|
||||
import io.legato.kazusa.utils.openUrl
|
||||
import io.legato.kazusa.utils.sendMail
|
||||
import io.legato.kazusa.utils.sendToClip
|
||||
import io.legato.kazusa.utils.showDialogFragment
|
||||
import io.legato.kazusa.utils.toastOnUi
|
||||
import kotlinx.coroutines.delay
|
||||
import splitties.init.appCtx
|
||||
import java.io.File
|
||||
|
||||
class AboutFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private val waitDialog by lazy {
|
||||
WaitDialog(requireContext())
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.about)
|
||||
findPreference<Preference>("update_log")?.summary =
|
||||
"${getString(R.string.version)} ${appInfo.versionName}"
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
listView.overScrollMode = View.OVER_SCROLL_NEVER
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
when (preference.key) {
|
||||
"contributors" -> openUrl(R.string.contributors_url)
|
||||
"update_log" -> showMdFile(getString(R.string.update_log), "updateLog.md")
|
||||
"check_update" -> checkUpdate()
|
||||
"mail" -> requireContext().sendMail(getString(R.string.email))
|
||||
"license" -> showMdFile(getString(R.string.license), "LICENSE.md")
|
||||
"disclaimer" -> showMdFile(getString(R.string.disclaimer), "disclaimer.md")
|
||||
"privacyPolicy" -> showMdFile(getString(R.string.privacy_policy), "privacyPolicy.md")
|
||||
"gzGzh" -> requireContext().sendToClip(getString(R.string.legado_gzh))
|
||||
"crashLog" -> showDialogFragment<CrashLogsDialog>()
|
||||
"saveLog" -> saveLog()
|
||||
"createHeapDump" -> createHeapDump()
|
||||
}
|
||||
return super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun openUrl(@StringRes addressID: Int) {
|
||||
requireContext().openUrl(getString(addressID))
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示md文件
|
||||
*/
|
||||
private fun showMdFile(title: String, fileName: String) {
|
||||
val mdText = String(requireContext().assets.open(fileName).readBytes())
|
||||
showDialogFragment(TextDialog(title, mdText, TextDialog.Mode.MD))
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测更新
|
||||
*/
|
||||
private fun checkUpdate() {
|
||||
waitDialog.show()
|
||||
AppUpdate.gitHubUpdate?.run {
|
||||
check(lifecycleScope)
|
||||
.onSuccess {
|
||||
showDialogFragment(
|
||||
UpdateDialog(it)
|
||||
)
|
||||
}.onError {
|
||||
appCtx.toastOnUi("${getString(R.string.check_update)}\n${it.localizedMessage}")
|
||||
}.onFinally {
|
||||
waitDialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加入qq群
|
||||
*/
|
||||
private fun joinQQGroup(key: String): Boolean {
|
||||
val intent = Intent()
|
||||
intent.data =
|
||||
Uri.parse("mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26k%3D$key")
|
||||
// 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面
|
||||
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
kotlin.runCatching {
|
||||
startActivity(intent)
|
||||
return true
|
||||
}.onFailure {
|
||||
toastOnUi("添加失败,请手动添加")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun saveLog() {
|
||||
Coroutine.async {
|
||||
val backupPath = AppConfig.backupPath ?: let {
|
||||
appCtx.toastOnUi("未设置备份目录")
|
||||
return@async
|
||||
}
|
||||
if (!AppConfig.recordLog) {
|
||||
appCtx.toastOnUi("未开启日志记录,请去其他设置里打开记录日志")
|
||||
delay(3000)
|
||||
}
|
||||
val doc = FileDoc.fromUri(Uri.parse(backupPath), true)
|
||||
copyLogs(doc)
|
||||
copyHeapDump(doc)
|
||||
appCtx.toastOnUi("已保存至备份目录")
|
||||
}.onError {
|
||||
AppLog.put("保存日志出错\n${it.localizedMessage}", it, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createHeapDump() {
|
||||
Coroutine.async {
|
||||
val backupPath = AppConfig.backupPath ?: let {
|
||||
appCtx.toastOnUi("未设置备份目录")
|
||||
return@async
|
||||
}
|
||||
if (!AppConfig.recordHeapDump) {
|
||||
appCtx.toastOnUi("未开启堆转储记录,请去其他设置里打开记录堆转储")
|
||||
delay(3000)
|
||||
}
|
||||
appCtx.toastOnUi("开始创建堆转储")
|
||||
System.gc()
|
||||
CrashHandler.doHeapDump(true)
|
||||
val doc = FileDoc.fromUri(Uri.parse(backupPath), true)
|
||||
if (!copyHeapDump(doc)) {
|
||||
appCtx.toastOnUi("未找到堆转储文件")
|
||||
} else {
|
||||
appCtx.toastOnUi("已保存至备份目录")
|
||||
}
|
||||
}.onError {
|
||||
AppLog.put("保存堆转储失败\n${it.localizedMessage}", it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyLogs(doc: FileDoc) {
|
||||
val cacheDir = appCtx.externalCache
|
||||
val logFiles = File(cacheDir, "logs")
|
||||
val crashFiles = File(cacheDir, "crash")
|
||||
val logcatFile = File(cacheDir, "logcat.txt")
|
||||
|
||||
dumpLogcat(logcatFile)
|
||||
|
||||
val zipFile = File(cacheDir, "logs.zip")
|
||||
ZipUtils.zipFiles(arrayListOf(logFiles, crashFiles, logcatFile), zipFile)
|
||||
|
||||
doc.find("logs.zip")?.delete()
|
||||
|
||||
zipFile.inputStream().use { input ->
|
||||
doc.createFileIfNotExist("logs.zip").openOutputStream().getOrNull()
|
||||
?.use {
|
||||
input.copyTo(it)
|
||||
}
|
||||
}
|
||||
zipFile.delete()
|
||||
}
|
||||
|
||||
private fun copyHeapDump(doc: FileDoc): Boolean {
|
||||
val heapFile = FileDoc.fromFile(File(appCtx.externalCache, "heapDump")).list()
|
||||
?.firstOrNull() ?: return false
|
||||
doc.find("heapDump")?.delete()
|
||||
val heapDumpDoc = doc.createFolderIfNotExist("heapDump")
|
||||
heapFile.openInputStream().getOrNull()?.use { input ->
|
||||
heapDumpDoc.createFileIfNotExist(heapFile.name).openOutputStream().getOrNull()
|
||||
?.use {
|
||||
input.copyTo(it)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun dumpLogcat(file: File) {
|
||||
try {
|
||||
val process = Runtime.getRuntime().exec("logcat -d")
|
||||
file.outputStream().use {
|
||||
process.inputStream.copyTo(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
AppLog.put("保存Logcat失败\n$e", e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package io.legato.kazusa.ui.about
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseDialogFragment
|
||||
import io.legato.kazusa.base.adapter.ItemViewHolder
|
||||
import io.legato.kazusa.base.adapter.RecyclerAdapter
|
||||
import io.legato.kazusa.constant.AppLog
|
||||
import io.legato.kazusa.databinding.DialogRecyclerViewBinding
|
||||
import io.legato.kazusa.databinding.ItemAppLogBinding
|
||||
//import io.legado.app.lib.theme.primaryColor
|
||||
import io.legato.kazusa.ui.widget.dialog.TextDialog
|
||||
import io.legato.kazusa.utils.LogUtils
|
||||
import io.legato.kazusa.utils.setLayout
|
||||
import io.legato.kazusa.utils.showDialogFragment
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
import splitties.views.onClick
|
||||
import java.util.*
|
||||
|
||||
class AppLogDialog : BaseDialogFragment(R.layout.dialog_recycler_view),
|
||||
Toolbar.OnMenuItemClickListener {
|
||||
|
||||
private val binding by viewBinding(DialogRecyclerViewBinding::bind)
|
||||
private val adapter by lazy {
|
||||
LogAdapter(requireContext())
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.run {
|
||||
//toolBar.setBackgroundColor(primaryColor)
|
||||
toolBar.setTitle(R.string.log)
|
||||
toolBar.inflateMenu(R.menu.app_log)
|
||||
toolBar.setOnMenuItemClickListener(this@AppLogDialog)
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
adapter.setItems(AppLog.logs)
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
||||
when (item?.itemId) {
|
||||
R.id.menu_clear -> {
|
||||
AppLog.clear()
|
||||
adapter.clearItems()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
inner class LogAdapter(context: Context) :
|
||||
RecyclerAdapter<Triple<Long, String, Throwable?>, ItemAppLogBinding>(context) {
|
||||
|
||||
override fun getViewBinding(parent: ViewGroup): ItemAppLogBinding {
|
||||
return ItemAppLogBinding.inflate(inflater, parent, false)
|
||||
}
|
||||
|
||||
override fun convert(
|
||||
holder: ItemViewHolder,
|
||||
binding: ItemAppLogBinding,
|
||||
item: Triple<Long, String, Throwable?>,
|
||||
payloads: MutableList<Any>
|
||||
) {
|
||||
binding.textTime.text = LogUtils.logTimeFormat.format(Date(item.first))
|
||||
binding.textMessage.text = item.second
|
||||
}
|
||||
|
||||
override fun registerListener(holder: ItemViewHolder, binding: ItemAppLogBinding) {
|
||||
binding.root.onClick {
|
||||
getItem(holder.layoutPosition)?.let { item ->
|
||||
item.third?.let {
|
||||
showDialogFragment(TextDialog("Log", it.stackTraceToString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package io.legato.kazusa.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.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseDialogFragment
|
||||
import io.legato.kazusa.base.BaseViewModel
|
||||
import io.legato.kazusa.base.adapter.ItemViewHolder
|
||||
import io.legato.kazusa.base.adapter.RecyclerAdapter
|
||||
import io.legato.kazusa.databinding.DialogRecyclerViewBinding
|
||||
import io.legato.kazusa.databinding.Item1lineTextBinding
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
//import io.legado.app.lib.theme.primaryColor
|
||||
import io.legato.kazusa.ui.widget.dialog.TextDialog
|
||||
import io.legato.kazusa.utils.FileDoc
|
||||
import io.legato.kazusa.utils.FileUtils
|
||||
import io.legato.kazusa.utils.delete
|
||||
import io.legato.kazusa.utils.find
|
||||
import io.legato.kazusa.utils.getFile
|
||||
import io.legato.kazusa.utils.list
|
||||
import io.legato.kazusa.utils.setLayout
|
||||
import io.legato.kazusa.utils.showDialogFragment
|
||||
import io.legato.kazusa.utils.toastOnUi
|
||||
import io.legato.kazusa.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 onStart() {
|
||||
super.onStart()
|
||||
setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
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 (lifecycleScope.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)
|
||||
.find("crash")
|
||||
?.list {
|
||||
!it.isDir
|
||||
}?.let {
|
||||
list.addAll(it)
|
||||
}
|
||||
}
|
||||
return@execute list.sortedByDescending { it.name }.distinctBy { 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)
|
||||
.find("crash")
|
||||
?.delete()
|
||||
}
|
||||
}.onError {
|
||||
context.toastOnUi(it.localizedMessage)
|
||||
}.onFinally {
|
||||
initData()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package io.legato.kazusa.ui.about
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseActivity
|
||||
import io.legato.kazusa.base.adapter.ItemViewHolder
|
||||
import io.legato.kazusa.base.adapter.RecyclerAdapter
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.data.entities.ReadRecordShow
|
||||
import io.legato.kazusa.databinding.ActivityReadRecordBinding
|
||||
import io.legato.kazusa.databinding.ItemReadRecordBinding
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.help.config.LocalConfig
|
||||
import io.legato.kazusa.lib.dialogs.alert
|
||||
//import io.legado.app.lib.theme.primaryTextColor
|
||||
import io.legato.kazusa.ui.book.search.SearchActivity
|
||||
import io.legato.kazusa.utils.applyNavigationBarPadding
|
||||
import io.legato.kazusa.utils.cnCompare
|
||||
import io.legato.kazusa.utils.getInt
|
||||
import io.legato.kazusa.utils.putInt
|
||||
import io.legato.kazusa.utils.startActivityForBook
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class ReadRecordActivity : BaseActivity<ActivityReadRecordBinding>() {
|
||||
|
||||
private val adapter by lazy { RecordAdapter(this) }
|
||||
private var sortMode
|
||||
get() = LocalConfig.getInt("readRecordSort")
|
||||
set(value) {
|
||||
LocalConfig.putInt("readRecordSort", value)
|
||||
}
|
||||
private val searchView: SearchView by lazy {
|
||||
binding.titleBar.findViewById(R.id.search_view)
|
||||
}
|
||||
|
||||
override val binding by viewBinding(ActivityReadRecordBinding::inflate)
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
initView()
|
||||
initAllTime()
|
||||
initData()
|
||||
}
|
||||
|
||||
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.book_read_record, menu)
|
||||
return super.onCompatCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
|
||||
menu.findItem(R.id.menu_enable_record)?.isChecked = AppConfig.enableReadRecord
|
||||
when (sortMode) {
|
||||
1 -> menu.findItem(R.id.menu_sort_read_long)?.isChecked = true
|
||||
2 -> menu.findItem(R.id.menu_sort_read_time)?.isChecked = true
|
||||
else -> menu.findItem(R.id.menu_sort_name)?.isChecked = true
|
||||
}
|
||||
return super.onMenuOpened(featureId, menu)
|
||||
}
|
||||
|
||||
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_sort_name -> {
|
||||
sortMode = 0
|
||||
item.isChecked = true
|
||||
initData()
|
||||
}
|
||||
|
||||
R.id.menu_sort_read_long -> {
|
||||
sortMode = 1
|
||||
item.isChecked = true
|
||||
initData()
|
||||
}
|
||||
|
||||
R.id.menu_sort_read_time -> {
|
||||
sortMode = 2
|
||||
item.isChecked = true
|
||||
initData()
|
||||
}
|
||||
|
||||
R.id.menu_enable_record -> {
|
||||
AppConfig.enableReadRecord = !item.isChecked
|
||||
}
|
||||
}
|
||||
return super.onCompatOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
initSearchView()
|
||||
binding.tvBookName.setText(R.string.all_read_time)
|
||||
binding.tvRemove.setOnClickListener {
|
||||
alert(R.string.delete, R.string.sure_del) {
|
||||
yesButton {
|
||||
appDb.readRecordDao.clear()
|
||||
initData()
|
||||
}
|
||||
noButton()
|
||||
}
|
||||
}
|
||||
binding.recyclerView.adapter = adapter
|
||||
binding.recyclerView.applyNavigationBarPadding()
|
||||
}
|
||||
|
||||
private fun initSearchView() {
|
||||
//searchView.applyTint(primaryTextColor)
|
||||
searchView.isSubmitButtonEnabled = true
|
||||
searchView.queryHint = getString(R.string.search)
|
||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
searchView.clearFocus()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
initData(newText)
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun initAllTime() {
|
||||
lifecycleScope.launch {
|
||||
val allTime = withContext(IO) {
|
||||
appDb.readRecordDao.allTime
|
||||
}
|
||||
binding.tvReadingTime.text = formatDuring(allTime)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initData(searchKey: String? = null) {
|
||||
lifecycleScope.launch {
|
||||
val readRecords = withContext(IO) {
|
||||
appDb.readRecordDao.search(searchKey ?: "").let { records ->
|
||||
when (sortMode) {
|
||||
1 -> records.sortedByDescending { it.readTime }
|
||||
2 -> records.sortedByDescending { it.lastRead }
|
||||
else -> records.sortedWith { o1, o2 ->
|
||||
o1.bookName.cnCompare(o2.bookName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter.setItems(readRecords)
|
||||
}
|
||||
}
|
||||
|
||||
inner class RecordAdapter(context: Context) :
|
||||
RecyclerAdapter<ReadRecordShow, ItemReadRecordBinding>(context) {
|
||||
|
||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||
|
||||
override fun getViewBinding(parent: ViewGroup): ItemReadRecordBinding {
|
||||
return ItemReadRecordBinding.inflate(inflater, parent, false)
|
||||
}
|
||||
|
||||
override fun convert(
|
||||
holder: ItemViewHolder,
|
||||
binding: ItemReadRecordBinding,
|
||||
item: ReadRecordShow,
|
||||
payloads: MutableList<Any>,
|
||||
) {
|
||||
binding.apply {
|
||||
tvBookName.text = item.bookName
|
||||
tvReadingTime.text = formatDuring(item.readTime)
|
||||
if (item.lastRead > 0) {
|
||||
tvLastReadTime.text = dateFormat.format(item.lastRead)
|
||||
} else {
|
||||
tvLastReadTime.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerListener(holder: ItemViewHolder, binding: ItemReadRecordBinding) {
|
||||
binding.apply {
|
||||
root.setOnClickListener {
|
||||
val item = getItem(holder.layoutPosition) ?: return@setOnClickListener
|
||||
lifecycleScope.launch {
|
||||
val book = withContext(IO) {
|
||||
appDb.bookDao.findByName(item.bookName).firstOrNull()
|
||||
}
|
||||
if (book == null) {
|
||||
SearchActivity.start(this@ReadRecordActivity, item.bookName)
|
||||
} else {
|
||||
startActivityForBook(book)
|
||||
}
|
||||
}
|
||||
}
|
||||
tvRemove.setOnClickListener {
|
||||
getItem(holder.layoutPosition)?.let { item ->
|
||||
sureDelAlert(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sureDelAlert(item: ReadRecordShow) {
|
||||
alert(R.string.delete) {
|
||||
setMessage(getString(R.string.sure_del_any, item.bookName))
|
||||
yesButton {
|
||||
appDb.readRecordDao.deleteByName(item.bookName)
|
||||
initData()
|
||||
}
|
||||
noButton()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun formatDuring(mss: Long): String {
|
||||
val days = mss / (1000 * 60 * 60 * 24)
|
||||
val hours = mss % (1000 * 60 * 60 * 24) / (1000 * 60 * 60)
|
||||
val minutes = mss % (1000 * 60 * 60) / (1000 * 60)
|
||||
val seconds = mss % (1000 * 60) / 1000
|
||||
val d = if (days > 0) "${days}天" else ""
|
||||
val h = if (hours > 0) "${hours}小时" else ""
|
||||
val m = if (minutes > 0) "${minutes}分钟" else ""
|
||||
val s = if (seconds > 0) "${seconds}秒" else ""
|
||||
var time = "$d$h$m$s"
|
||||
if (time.isBlank()) {
|
||||
time = "0秒"
|
||||
}
|
||||
return time
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package io.legato.kazusa.ui.about
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseDialogFragment
|
||||
import io.legato.kazusa.databinding.DialogUpdateBinding
|
||||
import io.legato.kazusa.help.update.AppUpdate
|
||||
//import io.legado.app.lib.theme.primaryColor
|
||||
import io.legato.kazusa.model.Download
|
||||
import io.legato.kazusa.utils.setLayout
|
||||
import io.legato.kazusa.utils.toastOnUi
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.ext.tables.TablePlugin
|
||||
import io.noties.markwon.html.HtmlPlugin
|
||||
import io.noties.markwon.image.glide.GlideImagesPlugin
|
||||
|
||||
class UpdateDialog() : BaseDialogFragment(R.layout.dialog_update) {
|
||||
|
||||
constructor(updateInfo: AppUpdate.UpdateInfo) : this() {
|
||||
arguments = Bundle().apply {
|
||||
putString("newVersion", updateInfo.tagName)
|
||||
putString("updateBody", updateInfo.updateLog)
|
||||
putString("url", updateInfo.downloadUrl)
|
||||
putString("name", updateInfo.fileName)
|
||||
}
|
||||
}
|
||||
|
||||
val binding by viewBinding(DialogUpdateBinding::bind)
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
||||
//binding.toolBar.setBackgroundColor(primaryColor)
|
||||
binding.toolBar.title = arguments?.getString("newVersion")
|
||||
val updateBody = arguments?.getString("updateBody")
|
||||
if (updateBody == null) {
|
||||
toastOnUi("没有数据")
|
||||
dismiss()
|
||||
return
|
||||
}
|
||||
binding.textView.post {
|
||||
Markwon.builder(requireContext())
|
||||
.usePlugin(GlideImagesPlugin.create(requireContext()))
|
||||
.usePlugin(HtmlPlugin.create())
|
||||
.usePlugin(TablePlugin.create(requireContext()))
|
||||
.build()
|
||||
.setMarkdown(binding.textView, updateBody)
|
||||
}
|
||||
binding.toolBar.inflateMenu(R.menu.app_update)
|
||||
binding.toolBar.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_download -> {
|
||||
val url = arguments?.getString("url")
|
||||
val name = arguments?.getString("name")
|
||||
if (url != null && name != null) {
|
||||
Download.start(requireContext(), url, name)
|
||||
toastOnUi(R.string.download_start)
|
||||
}
|
||||
}
|
||||
}
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user