This commit is contained in:
HapeLee committed 2025-06-04 13:33:51 +08:00
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
}
}
}