Просмотр исходного кода

更新关于界面
搜索书籍界面切换至SearchBar与SearchView

HapeLee 1 год назад
Родитель
Сommit
ccaf8973fe

+ 188 - 31
app/src/main/java/io/legato/kazusa/ui/about/AboutActivity.kt

@@ -1,53 +1,97 @@
 package io.legato.kazusa.ui.about
 
+//import io.legado.app.lib.theme.accentColor
+//import io.legado.app.lib.theme.filletBackground
 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 androidx.annotation.StringRes
+import androidx.core.net.toUri
+import androidx.lifecycle.lifecycleScope
 import io.legato.kazusa.R
 import io.legato.kazusa.base.BaseActivity
+import io.legato.kazusa.constant.AppLog
 import io.legato.kazusa.databinding.ActivityAboutBinding
-//import io.legado.app.lib.theme.accentColor
-//import io.legado.app.lib.theme.filletBackground
+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.share
+import io.legato.kazusa.utils.showDialogFragment
+import io.legato.kazusa.utils.toastOnUi
 import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
+import kotlinx.coroutines.delay
+import splitties.init.appCtx
+import java.io.File
 
 
 class AboutActivity : BaseActivity<ActivityAboutBinding>() {
 
     override val binding by viewBinding(ActivityAboutBinding::inflate)
 
+    private val waitDialog by lazy {
+        WaitDialog(this).setText(R.string.checking_update)
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        setSupportActionBar(binding.topBar)
 
-        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(
-                    androidx.appcompat.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
-            }
+        binding.btnUpdate.setOnClickListener {
+            checkUpdate()
+        }
+
+        binding.btnGithub.setOnClickListener {
+            openUrl(R.string.github_url)
+        }
+
+        binding.btnWeb.setOnClickListener {
+            openUrl(R.string.legado_url)
+        }
+
+        binding.llContributors.setOnClickListener {
+            openUrl(R.string.contributors_url)
+        }
+
+        binding.llUpdateLog.setOnClickListener {
+            showMdFile(getString(R.string.update_log), "updateLog.md")
+        }
+
+        binding.llPrivacyPolicy.setOnClickListener {
+            showMdFile(getString(R.string.privacy_policy), "privacyPolicy.md")
+        }
+
+        binding.llLicense.setOnClickListener {
+            showMdFile(getString(R.string.license), "LICENSE.md")
+        }
+
+        binding.llDisclaimer.setOnClickListener {
+            showMdFile(getString(R.string.disclaimer), "disclaimer.md")
+        }
+
+        binding.llCrashLog.setOnClickListener {
+            showDialogFragment<CrashLogsDialog>()
+        }
+
+        binding.llSaveLog.setOnClickListener {
+            saveLog()
+        }
+
+        binding.llCreateHeapDump.setOnClickListener {
+            createHeapDump()
         }
     }
 
@@ -58,7 +102,6 @@ class AboutActivity : BaseActivity<ActivityAboutBinding>() {
 
     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)
@@ -67,4 +110,118 @@ class AboutActivity : BaseActivity<ActivityAboutBinding>() {
         return super.onCompatOptionsItemSelected(item)
     }
 
+    private fun showMdFile(title: String, fileName: String) {
+        val mdText = String(this.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()
+                }
+        }
+    }
+
+    @Suppress("SameParameterValue")
+    private fun openUrl(@StringRes addressID: Int) {
+        this.openUrl(getString(addressID))
+    }
+
+    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(backupPath.toUri(), 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(backupPath.toUri(), 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 - 213
app/src/main/java/io/legato/kazusa/ui/about/AboutFragment.kt

@@ -1,213 +0,0 @@
-package io.legato.kazusa.ui.about
-
-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
-import androidx.core.net.toUri
-
-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 =
-//            "mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26k%3D$key".toUri()
-//        // 此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(backupPath.toUri(), 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(backupPath.toUri(), 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)
-        }
-    }
-
-}

+ 2 - 1
app/src/main/java/io/legato/kazusa/ui/book/search/BookAdapter.kt

@@ -5,8 +5,8 @@ import android.view.ViewGroup
 import io.legato.kazusa.base.adapter.ItemViewHolder
 import io.legato.kazusa.base.adapter.RecyclerAdapter
 import io.legato.kazusa.data.entities.Book
-import io.legato.kazusa.databinding.ItemFilletTextBinding
 import io.legato.kazusa.databinding.ItemSearchHistoryBinding
+import io.legato.kazusa.utils.gone
 
 
 class BookAdapter(context: Context, val callBack: CallBack) :
@@ -39,6 +39,7 @@ class BookAdapter(context: Context, val callBack: CallBack) :
                 }
             }
         }
+        binding.btnDelete.gone()
     }
 
     interface CallBack {

+ 5 - 1
app/src/main/java/io/legato/kazusa/ui/book/search/HistoryKeyAdapter.kt

@@ -4,7 +4,6 @@ import android.view.ViewGroup
 import io.legato.kazusa.base.adapter.ItemViewHolder
 import io.legato.kazusa.base.adapter.RecyclerAdapter
 import io.legato.kazusa.data.entities.SearchKeyword
-import io.legato.kazusa.databinding.ItemFilletTextBinding
 import io.legato.kazusa.databinding.ItemSearchHistoryBinding
 import io.legato.kazusa.ui.widget.anima.explosion_field.ExplosionField
 import splitties.views.onLongClick
@@ -47,6 +46,11 @@ class HistoryKeyAdapter(activity: SearchActivity, val callBack: CallBack) :
                 }
             }
         }
+        binding.btnDelete.setOnClickListener {
+            getItemByLayoutPosition(holder.layoutPosition)?.let {
+                callBack.deleteHistory(it)
+            }
+        }
     }
 
     interface CallBack {

+ 78 - 67
app/src/main/java/io/legato/kazusa/ui/book/search/SearchActivity.kt

@@ -5,17 +5,18 @@ import android.content.Intent
 import android.os.Bundle
 import android.view.Menu
 import android.view.MenuItem
-import android.view.View.GONE
 import android.view.View.VISIBLE
-import android.widget.TextView
+import android.view.inputmethod.EditorInfo
 import androidx.activity.viewModels
-import androidx.appcompat.widget.SearchView
 import androidx.core.os.bundleOf
 import androidx.core.view.isVisible
+import androidx.core.widget.doAfterTextChanged
 import androidx.lifecycle.lifecycleScope
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.google.android.flexbox.FlexboxLayoutManager
+import com.google.android.material.search.SearchBar
+import com.google.android.material.search.SearchView
 import io.legato.kazusa.R
 import io.legato.kazusa.base.VMBaseActivity
 import io.legato.kazusa.constant.AppLog
@@ -25,15 +26,12 @@ import io.legato.kazusa.data.entities.Book
 import io.legato.kazusa.data.entities.SearchKeyword
 import io.legato.kazusa.databinding.ActivityBookSearchBinding
 import io.legato.kazusa.lib.dialogs.alert
-//import io.legado.app.lib.theme.accentColor
-//import io.legado.app.lib.theme.backgroundColor
-//import io.legado.app.lib.theme.primaryColor
-//import io.legado.app.lib.theme.primaryTextColor
 import io.legato.kazusa.ui.about.AppLogDialog
 import io.legato.kazusa.ui.book.info.BookInfoActivity
 import io.legato.kazusa.ui.book.source.manage.BookSourceActivity
 import io.legato.kazusa.utils.applyNavigationBarMargin
 import io.legato.kazusa.utils.applyNavigationBarPadding
+import io.legato.kazusa.utils.applyStatusBarPadding
 import io.legato.kazusa.utils.getPrefBoolean
 import io.legato.kazusa.utils.gone
 import io.legato.kazusa.utils.invisible
@@ -73,9 +71,10 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
             setHasStableIds(true)
         }
     }
-    private val searchView: SearchView by lazy {
-        binding.titleBar.findViewById(R.id.search_view)
-    }
+
+    private val searchBar: SearchBar by lazy { binding.searchBar }
+    private val searchView: SearchView by lazy { binding.searchView }
+
     private var menu: Menu? = null
     private var groups: List<String>? = null
     private var historyFlowJob: Job? = null
@@ -85,9 +84,9 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        //binding.llInputHelp.setBackgroundColor(backgroundColor)
+        setSupportActionBar(binding.searchBar)
         initRecyclerView()
-        initSearchView()
+        initMaterialSearch()
         initOtherView()
         initData()
         receiptIntent(intent)
@@ -154,8 +153,8 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
                     !getPrefBoolean(PreferKey.precisionSearch)
                 )
                 precisionSearchMenuItem?.isChecked = getPrefBoolean(PreferKey.precisionSearch)
-                searchView.query?.toString()?.trim()?.let {
-                    searchView.setQuery(it, true)
+                searchView.text.toString().trim().let {
+                    searchView.setText(it)
                 }
             }
 
@@ -174,44 +173,63 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
         return super.onCompatOptionsItemSelected(item)
     }
 
-    private fun initSearchView() {
-        //searchView.applyTint(primaryTextColor)
-        searchView.isSubmitButtonEnabled = true
-        searchView.queryHint = getString(R.string.search_book_key)
-        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
-            override fun onQueryTextSubmit(query: String): Boolean {
-                searchView.clearFocus()
-                query.trim().let { searchKey ->
+    private fun initMaterialSearch() {
+
+        binding.appBar.applyStatusBarPadding()
+
+        searchBar.setOnMenuItemClickListener { item ->
+            onCompatOptionsItemSelected(item)
+        }
+
+        searchBar.setNavigationOnClickListener {
+            finish()
+        }
+
+        searchView.setupWithSearchBar(searchBar)
+
+        binding.searchView.show()
+        binding.searchView.editText.requestFocus()
+
+        searchView.editText.hint = getString(R.string.search_book_key)
+        searchView.editText.setOnEditorActionListener { _, actionId, _ ->
+            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+                val key = searchView.text.toString().trim()
+                if (key.isNotEmpty()) {
                     isManualStopSearch = false
-                    viewModel.saveSearchKey(searchKey)
+                    viewModel.saveSearchKey(key)
                     viewModel.searchKey = ""
-                    viewModel.search(searchKey)
+                    viewModel.search(key)
+                    visibleInputHelp()
+                    searchView.hide()
+                    return@setOnEditorActionListener true
                 }
-                visibleInputHelp(false)
-                return true
             }
+            false
+        }
 
-            override fun onQueryTextChange(newText: String): Boolean {
-                viewModel.stop()
-                binding.fbStartStop.invisible()
-                upHistory(newText.trim())
-                return false
+        searchView.editText.doAfterTextChanged {
+            viewModel.stop()
+            binding.fbStartStop.invisible()
+            upHistory(it?.toString()?.trim().orEmpty())
+        }
+
+        searchView.addTransitionListener { _, _, newState ->
+            if (newState == SearchView.TransitionState.HIDDEN) {
+                // 当 SearchView 关闭时,将搜索文本设置回 SearchBar
+                searchBar.setText(searchView.text)
             }
-        })
-//        searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
-//            if (binding.refreshProgressBar.isAutoLoading || (!hasFocus && adapter.isNotEmpty() && searchView.query.isNotBlank())) {
-//                visibleInputHelp(false)
-//            } else {
-//                visibleInputHelp(true)
-//            }
-//        }
-        visibleInputHelp(true)
+        }
+
+        searchView.addTransitionListener { _, oldState, newState ->
+            if (oldState == SearchView.TransitionState.HIDDEN && newState == SearchView.TransitionState.SHOWN) {
+                searchView.editText.requestFocus()
+            }
+        }
+
+        visibleInputHelp()
     }
 
     private fun initRecyclerView() {
-//        binding.recyclerView.setEdgeEffectColor(primaryColor)
-//        binding.rvBookshelfSearch.setEdgeEffectColor(primaryColor)
-//        binding.rvHistoryKey.setEdgeEffectColor(primaryColor)
         binding.rvBookshelfSearch.layoutManager = FlexboxLayoutManager(this)
         binding.rvBookshelfSearch.adapter = bookAdapter
         binding.rvBookshelfSearch.applyNavigationBarMargin()
@@ -261,16 +279,10 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
     }
 
     private fun initOtherView() {
-//        binding.fbStartStop.backgroundTintList =
-//            Selector.colorBuild()
-//                .setDefaultColor(accentColor)
-//                .setPressedColor(ColorUtils.darkenColor(accentColor))
-//                .create()
         binding.fbStartStop.setOnClickListener {
             if (viewModel.isSearchLiveData.value == true) {
                 isManualStopSearch = true
                 viewModel.stop()
-                //binding.refreshProgressBar.visibility = GONE
             } else {
                 viewModel.search("")
             }
@@ -281,8 +293,8 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
     private fun initData() {
         viewModel.searchScope.stateLiveData.observe(this) {
             if (!binding.llInputHelp.isVisible) {
-                searchView.query?.toString()?.trim()?.let {
-                    searchView.setQuery(it, true)
+                searchView.text.toString().trim().let {
+                    searchView.setText(it)
                 }
             }
         }
@@ -295,6 +307,11 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
         }
         viewModel.searchBookLiveData.observe(this) {
             adapter.setItems(it)
+            if (it.isNullOrEmpty()) {
+                binding.tvEmptyMsg.visible()
+            } else {
+                binding.tvEmptyMsg.gone()
+            }
         }
         lifecycleScope.launch {
             appDb.bookSourceDao.flowEnabledGroups().collect {
@@ -313,10 +330,9 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
         }
         val key = intent?.getStringExtra("key")
         if (key.isNullOrBlank()) {
-            searchView.findViewById<TextView>(androidx.appcompat.R.id.search_src_text)
-                .requestFocus()
+            searchView.editText.requestFocus()
         } else {
-            searchView.setQuery(key, true)
+            searchView.setText(key)
         }
     }
 
@@ -338,13 +354,9 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
     /**
      * 打开关闭输入帮助
      */
-    private fun visibleInputHelp(visible: Boolean) {
-        if (visible) {
-            upHistory(searchView.query.toString())
-            binding.llInputHelp.visibility = VISIBLE
-        } else {
-            binding.llInputHelp.visibility = GONE
-        }
+    private fun visibleInputHelp() {
+        upHistory(searchView.text.toString())
+        binding.llInputHelp.visibility = VISIBLE
     }
 
     /**
@@ -401,7 +413,6 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
      * 搜索结束
      */
     private fun searchFinally() {
-        //binding.refreshProgressBar.isAutoLoading = false
         binding.refreshProgressBar.gone()
         if (!isManualStopSearch && viewModel.hasMore) {
             binding.fbStartStop.setImageResource(R.drawable.ic_play_24dp)
@@ -425,7 +436,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
                         appCtx.putPrefBoolean(PreferKey.precisionSearch, false)
                         precisionSearchMenuItem?.isChecked = false
                         viewModel.searchKey = ""
-                        viewModel.search(searchView.query.toString())
+                        viewModel.search(searchView.text.toString())
                     }
                 } else {
                     setMessage("${displayScope}分组搜索结果为空,是否切换到全部分组?")
@@ -469,16 +480,16 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
     override fun searchHistory(key: String) {
         lifecycleScope.launch {
             when {
-                searchView.query.toString() == key -> {
-                    searchView.setQuery(key, true)
+                searchView.text.toString() == key -> {
+                    searchView.setText(key)
                 }
 
                 withContext(IO) { appDb.bookDao.findByName(key).isEmpty() } -> {
-                    searchView.setQuery(key, true)
+                    searchView.setText(key)
                 }
 
                 else -> {
-                    searchView.setQuery(key, false)
+                    searchView.setText(key)
                 }
             }
         }

+ 36 - 8
app/src/main/java/io/legato/kazusa/ui/widget/dialog/WaitDialog.kt

@@ -2,17 +2,23 @@ package io.legato.kazusa.ui.widget.dialog
 
 import android.app.Dialog
 import android.content.Context
+import android.view.LayoutInflater
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import io.legato.kazusa.R
 import io.legato.kazusa.databinding.DialogWaitBinding
 
+class WaitDialog(context: Context) {
 
-@Suppress("unused")
-class WaitDialog(context: Context) : Dialog(context) {
+    private val binding: DialogWaitBinding =
+        DialogWaitBinding.inflate(LayoutInflater.from(context))
 
-    val binding = DialogWaitBinding.inflate(layoutInflater)
+    private val dialog: Dialog = MaterialAlertDialogBuilder(context)
+        .setView(binding.root)
+        .setCancelable(false)
+        .create()
 
     init {
-        setCanceledOnTouchOutside(false)
-        setContentView(binding.root)
+        binding.tvMsg.setText(R.string.loading)
     }
 
     fun setText(text: String): WaitDialog {
@@ -20,9 +26,31 @@ class WaitDialog(context: Context) : Dialog(context) {
         return this
     }
 
-    fun setText(res: Int): WaitDialog {
-        binding.tvMsg.setText(res)
+    fun setText(resId: Int): WaitDialog {
+        binding.tvMsg.setText(resId)
         return this
     }
 
-}
+    fun show(): WaitDialog {
+        dialog.show()
+        return this
+    }
+
+    fun dismiss() {
+        dialog.dismiss()
+    }
+
+    fun isShowing(): Boolean {
+        return dialog.isShowing
+    }
+
+    fun setOnCancelListener(listener: () -> Unit): WaitDialog {
+        dialog.setOnCancelListener { listener() }
+        return this
+    }
+
+    fun setCancelable(cancelable: Boolean): WaitDialog {
+        dialog.setCancelable(cancelable)
+        return this
+    }
+}

+ 9 - 2
app/src/main/res/drawable/ic_cfg_web.xml

@@ -1,3 +1,10 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
-  <path android:fillColor="@android:color/white" android:pathData="M480,420L480,420Q480,420 480,420Q480,420 480,420L480,420Q480,420 480,420Q480,420 480,420L480,420Q480,420 480,420Q480,420 480,420ZM80,800L80,720L480,720L480,800L80,800ZM200,680Q167,680 143.5,656.5Q120,633 120,600L120,240Q120,207 143.5,183.5Q167,160 200,160L760,160Q793,160 816.5,183.5Q840,207 840,240L200,240Q200,240 200,240Q200,240 200,240L200,600Q200,600 200,600Q200,600 200,600L480,600L480,680L200,680ZM800,720L800,400Q800,400 800,400Q800,400 800,400L640,400Q640,400 640,400Q640,400 640,400L640,720Q640,720 640,720Q640,720 640,720L800,720Q800,720 800,720Q800,720 800,720ZM620,800Q595,800 577.5,782.5Q560,765 560,740L560,380Q560,355 577.5,337.5Q595,320 620,320L820,320Q845,320 862.5,337.5Q880,355 880,380L880,740Q880,765 862.5,782.5Q845,800 820,800L620,800ZM720,500Q733,500 741.5,491Q750,482 750,470Q750,457 741.5,448.5Q733,440 720,440Q708,440 699,448.5Q690,457 690,470Q690,482 699,491Q708,500 720,500ZM720,560Q720,560 720,560Q720,560 720,560L720,560Q720,560 720,560Q720,560 720,560L720,560Q720,560 720,560Q720,560 720,560L720,560Q720,560 720,560Q720,560 720,560Z"/>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,420L480,420Q480,420 480,420Q480,420 480,420L480,420Q480,420 480,420Q480,420 480,420L480,420Q480,420 480,420Q480,420 480,420ZM80,800L80,720L480,720L480,800L80,800ZM200,680Q167,680 143.5,656.5Q120,633 120,600L120,240Q120,207 143.5,183.5Q167,160 200,160L760,160Q793,160 816.5,183.5Q840,207 840,240L200,240Q200,240 200,240Q200,240 200,240L200,600Q200,600 200,600Q200,600 200,600L480,600L480,680L200,680ZM800,720L800,400Q800,400 800,400Q800,400 800,400L640,400Q640,400 640,400Q640,400 640,400L640,720Q640,720 640,720Q640,720 640,720L800,720Q800,720 800,720Q800,720 800,720ZM620,800Q595,800 577.5,782.5Q560,765 560,740L560,380Q560,355 577.5,337.5Q595,320 620,320L820,320Q845,320 862.5,337.5Q880,355 880,380L880,740Q880,765 862.5,782.5Q845,800 820,800L620,800ZM720,500Q733,500 741.5,491Q750,482 750,470Q750,457 741.5,448.5Q733,440 720,440Q708,440 699,448.5Q690,457 690,470Q690,482 699,491Q708,500 720,500ZM720,560Q720,560 720,560Q720,560 720,560L720,560Q720,560 720,560Q720,560 720,560L720,560Q720,560 720,560Q720,560 720,560L720,560Q720,560 720,560Q720,560 720,560Z" />
 </vector>

+ 10 - 0
app/src/main/res/drawable/ic_github.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12 1C5.923 1 1 5.923 1 12c0 4.867 3.149 8.979 7.521 10.436.55.096.756-.233.756-.522 0-.262-.013-1.128-.013-2.049-2.764.509-3.479-.674-3.699-1.292-.124-.317-.66-1.293-1.127-1.554-.385-.207-.936-.715-.014-.729.866-.014 1.485.797 1.691 1.128.99 1.663 2.571 1.196 3.204.907.096-.715.385-1.196.701-1.471-2.448-.275-5.005-1.224-5.005-5.432 0-1.196.426-2.186 1.128-2.956-.111-.275-.496-1.402.11-2.915 0 0 .921-.288 3.024 1.128a10.193 10.193 0 0 1 2.75-.371c.936 0 1.871.123 2.75.371 2.104-1.43 3.025-1.128 3.025-1.128.605 1.513.221 2.64.111 2.915.701.77 1.127 1.747 1.127 2.956 0 4.222-2.571 5.157-5.019 5.432.399.344.743 1.004.743 2.035 0 1.471-.014 2.654-.014 3.025 0 .289.206.632.756.522C19.851 20.979 23 16.854 23 12c0-6.077-4.922-11-11-11Z" />
+</vector>

+ 295 - 21
app/src/main/res/layout/activity_about.xml

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
@@ -7,41 +7,315 @@
     android:orientation="vertical"
     tools:context="io.legato.kazusa.ui.about.AboutActivity">
 
-    <io.legato.kazusa.ui.widget.TitleBar
-        android:id="@+id/title_bar"
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/app_bar"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        app:title="@string/about">
+        android:fitsSystemWindows="true">
 
-        <LinearLayout
-            android:id="@+id/ll_about"
+        <com.google.android.material.appbar.CollapsingToolbarLayout
+            style="?attr/collapsingToolbarLayoutMediumStyle"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
+            app:collapsedTitleTextAppearance="?attr/textAppearanceTitleLarge"
+            app:expandedTitleTextAppearance="?attr/textAppearanceHeadlineLarge"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
+            app:titleCollapseMode="scale">
+
+            <com.google.android.material.appbar.MaterialToolbar
+                android:id="@+id/top_bar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                app:layout_collapseMode="pin"
+                app:navigationIcon="@drawable/ic_arrow_back"
+                app:title="@string/about" />
+
+        </com.google.android.material.appbar.CollapsingToolbarLayout>
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <androidx.core.widget.NestedScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:background="@drawable/shape_card_view"
+            android:layout_marginVertical="8dp"
             android:orientation="vertical"
-            android:layout_margin="16dp"
-            android:padding="16dp">
+            app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
-            <TextView
+            <com.google.android.material.card.MaterialCardView
+                android:id="@+id/cd_about"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_gravity="center"
+                android:layout_marginStart="16dp"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:strokeWidth="0dp">
+
+                <ImageView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@mipmap/ic_launcher" />
+            </com.google.android.material.card.MaterialCardView>
+
+            <TextView
+                android:id="@+id/tv_name"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
                 android:text="@string/app_name"
+                android:layout_marginEnd="16dp"
                 android:textSize="20sp"
-                android:textStyle="bold" />
+                android:textStyle="bold"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@id/cd_about"
+                app:layout_constraintTop_toTopOf="@id/cd_about" />
 
             <TextView
-                android:id="@+id/tv_app_summary"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
+                android:layout_marginEnd="16dp"
+                android:text="@string/about_description"
+                android:textSize="14sp"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@id/cd_about"
+                app:layout_constraintTop_toBottomOf="@id/tv_name" />
+
+            <LinearLayout
+                android:id="@+id/ll_btn"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:text="@string/about_description" />
-        </LinearLayout>
+                android:layout_marginHorizontal="16dp"
+                android:layout_marginVertical="8dp"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/cd_about">
 
-    </io.legato.kazusa.ui.widget.TitleBar>
+                <com.google.android.material.card.MaterialCardView
+                    android:layout_width="8dp"
+                    android:layout_height="match_parent"
+                    android:layout_marginVertical="8dp"
+                    android:layout_marginEnd="12dp"
+                    app:cardBackgroundColor="?attr/colorTertiary"
+                    app:strokeWidth="0dp" />
 
-    <FrameLayout
-        android:id="@+id/fl_fragment"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+                <com.google.android.material.button.MaterialButton
+                    android:id="@+id/btn_web"
+                    style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:layout_marginEnd="8dp"
+                    app:icon="@drawable/ic_web_outline" />
+
+                <com.google.android.material.button.MaterialButton
+                    android:id="@+id/btn_github"
+                    style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    app:icon="@drawable/ic_github" />
+
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1" />
+
+                <com.google.android.material.button.MaterialButton
+                    android:id="@+id/btn_update"
+                    style="@style/Widget.Material3Expressive.Button.TonalButton"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/check_update"
+                    app:icon="@drawable/ic_import" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_contributors"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="?attr/selectableItemBackground"
+                android:clickable="true"
+                android:focusable="true"
+                android:minHeight="60dp"
+                android:orientation="vertical"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="16dp"
+                app:layout_constraintTop_toBottomOf="@id/ll_btn">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="@string/contributors"
+                    android:textSize="16sp" />
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="2dp"
+                    android:text="@string/contributors_summary"
+                    android:textSize="13sp" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_update_log"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="?attr/selectableItemBackground"
+                android:clickable="true"
+                android:focusable="true"
+                android:minHeight="60dp"
+                android:orientation="vertical"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="16dp"
+                app:layout_constraintTop_toBottomOf="@id/ll_contributors">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="@string/update_log"
+                    android:textSize="16sp" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_privacy_policy"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="?attr/selectableItemBackground"
+                android:clickable="true"
+                android:focusable="true"
+                android:minHeight="60dp"
+                android:orientation="vertical"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="16dp"
+                app:layout_constraintTop_toBottomOf="@id/ll_update_log">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="@string/privacy_policy"
+                    android:textSize="16sp" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_license"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="?attr/selectableItemBackground"
+                android:clickable="true"
+                android:focusable="true"
+                android:minHeight="60dp"
+                android:orientation="vertical"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="16dp"
+                app:layout_constraintTop_toBottomOf="@id/ll_privacy_policy">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="@string/license"
+                    android:textSize="16sp" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_disclaimer"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="?attr/selectableItemBackground"
+                android:clickable="true"
+                android:focusable="true"
+                android:minHeight="60dp"
+                android:orientation="vertical"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="16dp"
+                app:layout_constraintTop_toBottomOf="@id/ll_license">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="@string/disclaimer"
+                    android:textSize="16sp" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_crash_log"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="?attr/selectableItemBackground"
+                android:clickable="true"
+                android:focusable="true"
+                android:minHeight="60dp"
+                android:orientation="vertical"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="16dp"
+                app:layout_constraintTop_toBottomOf="@id/ll_disclaimer">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="@string/crash_log"
+                    android:textSize="16sp" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_save_log"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="?attr/selectableItemBackground"
+                android:clickable="true"
+                android:focusable="true"
+                android:minHeight="60dp"
+                android:orientation="vertical"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="16dp"
+                app:layout_constraintTop_toBottomOf="@id/ll_crash_log">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="@string/save_log"
+                    android:textSize="16sp" />
+
+            </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/ll_create_heap_dump"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="?attr/selectableItemBackground"
+                android:clickable="true"
+                android:focusable="true"
+                android:minHeight="60dp"
+                android:orientation="vertical"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="16dp"
+                app:layout_constraintTop_toBottomOf="@id/ll_save_log">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    android:text="@string/create_heap_dump"
+                    android:textSize="16sp" />
+
+            </LinearLayout>
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </androidx.core.widget.NestedScrollView>
 
-</LinearLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 108 - 75
app/src/main/res/layout/activity_book_search.xml

@@ -1,34 +1,66 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".ui.book.search.SearchActivity">
 
-    <io.legato.kazusa.ui.widget.TitleBar
-        android:id="@+id/title_bar"
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/app_bar"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        app:contentInsetStartWithNavigation="0dp"
-        app:contentLayout="@layout/view_search"
-        app:layout_constraintTop_toTopOf="parent"
-        app:title="搜索" />
+        android:backgroundTint="?attr/colorSurface"
+        app:layout_constraintTop_toTopOf="parent">
 
-    <com.google.android.material.progressindicator.LinearProgressIndicator
-        android:id="@+id/refresh_progress_bar"
-        android:layout_width="match_parent"
-        android:layout_height="2dp"
-        android:visibility="gone"
-        android:indeterminate="true"
-        app:layout_constraintTop_toBottomOf="@id/title_bar" />
+        <com.google.android.material.search.SearchBar
+            android:id="@+id/search_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:hint="@string/search_book_key"
+            app:menu="@menu/book_search"
+            app:navigationIconTint="?attr/colorOnSurface" />
+
+        <com.google.android.material.progressindicator.LinearProgressIndicator
+            android:id="@+id/refresh_progress_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:indeterminate="true"
+            android:visibility="gone"
+            app:layout_constraintTop_toBottomOf="@id/app_bar">
+
+        </com.google.android.material.progressindicator.LinearProgressIndicator>
+
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <LinearLayout
+        android:id="@+id/tv_empty_msg"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_margin="16dp"
+        android:gravity="center"
+        android:orientation="vertical"
+        android:visibility="visible">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="(๑`^´๑)"
+            android:textSize="30sp" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="8dp"
+            android:text="@string/search_empty" />
+    </LinearLayout>
 
     <io.legato.kazusa.ui.widget.dynamiclayout.DynamicFrameLayout
         android:id="@+id/content_view"
         android:layout_width="match_parent"
-        android:layout_height="0dp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/refresh_progress_bar">
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
         <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/recycler_view"
@@ -39,72 +71,75 @@
 
     </io.legato.kazusa.ui.widget.dynamiclayout.DynamicFrameLayout>
 
-    <LinearLayout
-        android:id="@+id/ll_input_help"
+    <com.google.android.material.search.SearchView
+        android:id="@+id/search_view"
         android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:clickable="true"
-        android:focusable="true"
-        android:orientation="vertical"
-        android:visibility="visible"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/title_bar">
-
-        <TextView
-            android:id="@+id/tv_book_show"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingHorizontal="16dp"
-            android:paddingVertical="12dp"
-            android:text="@string/bookshelf"
-            android:visibility="visible" />
-
-        <androidx.recyclerview.widget.RecyclerView
-            android:id="@+id/rv_bookshelf_search"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingHorizontal="16dp"
-            android:paddingVertical="8dp"
-            android:visibility="visible" />
+        android:layout_height="match_parent">
 
         <LinearLayout
+            android:id="@+id/ll_input_help"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:paddingHorizontal="16dp"
-            android:paddingVertical="4dp">
+            android:layout_height="match_parent"
+            android:clickable="true"
+            android:focusable="true"
+            android:orientation="vertical"
+            android:visibility="visible">
 
             <TextView
-                android:id="@+id/tv_history"
-                android:layout_width="wrap_content"
+                android:id="@+id/tv_book_show"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="16dp"
+                android:text="@string/bookshelf"
                 android:textSize="16sp"
                 android:textStyle="bold"
-                android:layout_gravity="center_vertical"
-                android:text="@string/searchHistory" />
-
-            <View
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1" />
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/tv_clear_history"
-                style="@style/Widget.Material3Expressive.Button.IconButton.Outlined"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                app:iconGravity="textStart"
-                app:icon="@drawable/ic_clear_all" />
+                android:visibility="visible" />
 
-        </LinearLayout>
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/rv_bookshelf_search"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:visibility="visible" />
 
-        <androidx.recyclerview.widget.RecyclerView
-            android:id="@+id/rv_history_key"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:paddingHorizontal="12dp" />
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:paddingHorizontal="16dp"
+                android:paddingVertical="4dp">
+
+                <TextView
+                    android:id="@+id/tv_history"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    android:text="@string/searchHistory"
+                    android:textSize="16sp"
+                    android:textStyle="bold" />
+
+                <View
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1" />
+
+                <com.google.android.material.button.MaterialButton
+                    android:id="@+id/tv_clear_history"
+                    style="@style/Widget.Material3Expressive.Button.IconButton.Outlined"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    app:icon="@drawable/ic_clear_all"
+                    app:iconGravity="textStart" />
+
+            </LinearLayout>
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/rv_history_key"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" />
 
-    </LinearLayout>
+        </LinearLayout>
+    </com.google.android.material.search.SearchView>
 
     <com.google.android.material.floatingactionbutton.FloatingActionButton
         android:id="@+id/fb_start_stop"
@@ -114,9 +149,7 @@
         android:contentDescription="@string/stop"
         android:src="@drawable/ic_stop_black_24dp"
         android:visibility="invisible"
-        app:fabSize="mini"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintRight_toRightOf="parent" />
+        android:layout_gravity="bottom|end" />
 
-</androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
 

+ 2 - 1
app/src/main/res/layout/item_search.xml

@@ -4,7 +4,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:padding="16dp"
+    android:paddingHorizontal="16dp"
+    android:paddingVertical="8dp"
     android:background="?attr/selectableItemBackground"
     android:orientation="horizontal"
     android:scrollbars="none">

+ 19 - 14
app/src/main/res/layout/item_search_history.xml

@@ -1,30 +1,35 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/rootView"
-    android:layout_width="wrap_content"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="horizontal"
-    android:layout_margin="2dp"
-    android:paddingHorizontal="16dp"
-    android:paddingVertical="8dp"
-    android:background="@drawable/selector_fillet_btn_bg"
-    android:gravity="center"
+    android:background="?attr/selectableItemBackground"
     android:clickable="true"
     android:focusable="true"
-    android:minHeight="36dp"
-    android:backgroundTint="?attr/colorSurfaceContainer"
     android:clipToPadding="false"
-    android:clipChildren="false">
+    android:gravity="center_vertical">
 
     <TextView
         android:id="@+id/textView"
-        android:layout_width="wrap_content"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:text="关键词"
-        android:textSize="14sp"
-        android:textColor="?attr/colorOnSurface"
+        android:layout_marginHorizontal="16dp"
+        android:layout_marginVertical="16dp"
+        android:layout_weight="1"
         android:maxLines="1"
         android:ellipsize="end"
-        android:paddingHorizontal="4dp" />
+        android:text="@string/text"
+        android:textSize="16sp" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btn_delete"
+        style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        app:backgroundTint="@android:color/transparent"
+        app:icon="@drawable/ic_baseline_close" />
 
 </LinearLayout>

+ 0 - 1
app/src/main/res/layout/view_search.xml

@@ -3,7 +3,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/search_view"
-    android:theme="?attr/actionBarStyle"
     android:background="@drawable/bg_searchview"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"

+ 0 - 5
app/src/main/res/menu/about.xml

@@ -10,9 +10,4 @@
         app:showAsAction="always"
         tools:ignore="AlwaysShowAction" />
 
-    <item
-        android:id="@+id/menu_scoring"
-        android:title="@string/scoring"
-        android:icon="@drawable/ic_scoring"
-        app:showAsAction="always" />
 </menu>

+ 2 - 0
app/src/main/res/values-zh/strings.xml

@@ -1234,5 +1234,7 @@
     <string name="list">列表</string>
     <string name="grid">网格</string>
     <string name="descending_order">降序</string>
+    <string name="checking_update">检查更新中...</string>
+    <string name="search_empty">没有内容哦</string>
 
 </resources>

+ 2 - 1
app/src/main/res/values/non_translat.xml

@@ -5,7 +5,8 @@
     <string name="email" translatable="false">gekunfei@live.com</string>
 
     <string name="contributors_url" translatable="false">https://github.com/gedoor/legado/graphs/contributors</string>
-
+    <string name="github_url" translatable="false">https://github.com/HapeLee/legado</string>
+    <string name="legado_url" translatable="false">https://gedoor.github.io/</string>
     <string name="http_ip" translatable="false">http://%1$s:%2$d</string>
     <string name="git_hub" translatable="false">GitHub</string>
     <string name="diy_edit_source_group_title" translatable="false">【%s】</string>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -1237,5 +1237,7 @@
     <string name="number_rows_columns">每行列数</string>
     <string name="ascending_order">升序</string>
     <string name="descending_order">降序</string>
+    <string name="checking_update">检查更新中...</string>
+    <string name="search_empty">没有内容哦</string>
 
 </resources>

+ 24 - 33
app/src/main/res/xml/about.xml

@@ -22,43 +22,34 @@
         app:allowDividerBelow="false"
         app:iconSpaceReserved="false" />
 
-    <io.legato.kazusa.lib.prefs.PreferenceCategory
-        android:title="@string/other"
-        app:allowDividerAbove="true"
-        app:allowDividerBelow="false"
-        app:iconSpaceReserved="false"
-        app:layout="@layout/view_preference_category">
-
-        <io.legato.kazusa.lib.prefs.Preference
-            android:key="crashLog"
-            android:title="@string/crash_log"
-            app:iconSpaceReserved="false" />
-
-        <io.legato.kazusa.lib.prefs.Preference
-            android:key="saveLog"
-            android:title="@string/save_log"
-            app:iconSpaceReserved="false" />
+    <io.legato.kazusa.lib.prefs.Preference
+        android:key="privacyPolicy"
+        android:title="@string/privacy_policy"
+        app:iconSpaceReserved="false" />
 
-        <io.legato.kazusa.lib.prefs.Preference
-            android:key="createHeapDump"
-            android:title="@string/create_heap_dump"
-            app:iconSpaceReserved="false" />
+    <io.legato.kazusa.lib.prefs.Preference
+        android:key="license"
+        android:title="@string/license"
+        app:iconSpaceReserved="false" />
 
-        <io.legato.kazusa.lib.prefs.Preference
-            android:key="privacyPolicy"
-            android:title="@string/privacy_policy"
-            app:iconSpaceReserved="false" />
+    <io.legato.kazusa.lib.prefs.Preference
+        android:key="disclaimer"
+        android:title="@string/disclaimer"
+        app:iconSpaceReserved="false" />
 
-        <io.legato.kazusa.lib.prefs.Preference
-            android:key="license"
-            android:title="@string/license"
-            app:iconSpaceReserved="false" />
+    <io.legato.kazusa.lib.prefs.Preference
+        android:key="crashLog"
+        android:title="@string/crash_log"
+        app:iconSpaceReserved="false" />
 
-        <io.legato.kazusa.lib.prefs.Preference
-            android:key="disclaimer"
-            android:title="@string/disclaimer"
-            app:iconSpaceReserved="false" />
+    <io.legato.kazusa.lib.prefs.Preference
+        android:key="saveLog"
+        android:title="@string/save_log"
+        app:iconSpaceReserved="false" />
 
-    </io.legato.kazusa.lib.prefs.PreferenceCategory>
+    <io.legato.kazusa.lib.prefs.Preference
+        android:key="createHeapDump"
+        android:title="@string/create_heap_dump"
+        app:iconSpaceReserved="false" />
 
 </androidx.preference.PreferenceScreen>