diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 648bb7323..ef2c71ab2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -223,6 +223,10 @@ android:name=".ui.book.chapterlist.ChapterListActivity" android:launchMode="singleTop" android:screenOrientation="behind" /> + , indexWithinChapter: Int, query: String): Array{ + // + // calculate search result's pageIndex + var content = "" + pages.map{ + content+= it.text + } + var count = 1 + var index = content.indexOf(query) + while(count != indexWithinChapter){ + index = content.indexOf(query, index + 1); + count += 1 + } + val contentPosition = index + var pageIndex = 0 + var length = pages[pageIndex].text.length + while (length < contentPosition){ + pageIndex += 1 + if (pageIndex >pages.size){ + pageIndex = pages.size + break + } + length += pages[pageIndex].text.length + } + + // calculate search result's lineIndex + val currentPage = pages[pageIndex] + var lineIndex = 0 + length = length - currentPage.text.length + currentPage.textLines[lineIndex].text.length + while (length < contentPosition){ + lineIndex += 1 + if (lineIndex >currentPage.textLines.size){ + lineIndex = currentPage.textLines.size + break + } + length += currentPage.textLines[lineIndex].text.length + } + + // charIndex + val currentLine = currentPage.textLines[lineIndex] + length -= currentLine.text.length + val charIndex = contentPosition - length + return arrayOf(pageIndex, lineIndex, charIndex) + } + /** * 内容加载完成 */ @@ -426,4 +473,5 @@ object ReadBook { fun pageChanged() fun contentLoadFinish() } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 7840123db..7120e047a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -9,6 +9,7 @@ import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Bundle import android.os.Handler +import android.util.Log import android.view.* import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.core.view.get @@ -47,6 +48,8 @@ import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.TextPageFactory import io.legado.app.ui.book.read.page.delegate.PageDelegate +import io.legado.app.ui.book.searchContent.SearchListActivity +import io.legado.app.ui.book.searchContent.SearchResult import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.login.SourceLogin import io.legado.app.ui.replacerule.ReplaceRuleActivity @@ -57,6 +60,9 @@ import kotlinx.android.synthetic.main.activity_book_read.* import kotlinx.android.synthetic.main.view_read_menu.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.async +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.startActivity @@ -79,6 +85,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo private val requestCodeChapterList = 568 private val requestCodeEditSource = 111 private val requestCodeReplace = 312 + private val requestCodeSearchResult = 123 private var menu: Menu? = null private var textActionMenu: TextActionMenu? = null @@ -96,6 +103,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override var isAutoPage = false private var screenTimeOut: Long = 0 private var timeBatteryReceiver: TimeBatteryReceiver? = null + private var loadStates: Boolean = false override val pageFactory: TextPageFactory get() = page_view.pageFactory override val headerHeight: Int get() = page_view.curPage.headerHeight @@ -532,6 +540,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo intent.removeExtra("readAloud") ReadBook.readAloud() } + loadStates = true } /** @@ -543,6 +552,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo page_view.upContent(relativePosition, resetPageOffset) seek_read_page.progress = ReadBook.durPageIndex } + loadStates = false } /** @@ -667,6 +677,19 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } + /** + * 打开搜索界面 + */ + //todo: change request code + override fun openSearchList() { + ReadBook.book?.let { + startActivityForResult( + requestCodeSearchResult, + Pair("bookUrl", it.bookUrl) + ) + } + } + /** * 替换规则变化 */ @@ -747,11 +770,36 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo requestCodeChapterList -> data?.getIntExtra("index", ReadBook.durChapterIndex)?.let { index -> if (index != ReadBook.durChapterIndex) { + val pageIndex = data.getIntExtra("pageIndex", 0) + viewModel.openChapter(index, pageIndex) + } + } + requestCodeSearchResult -> + data?.getIntExtra("index", ReadBook.durChapterIndex)?.let { index -> + launch(IO){ + val indexWithinChapter = data.getIntExtra("indexWithinChapter", 0) + val query = data.getStringExtra("query") viewModel.openChapter(index) + // block until load correct chapter and pages + var pages = ReadBook.curTextChapter?.pages + while (ReadBook.durChapterIndex != index || pages == null ){ + delay(100L) + pages = ReadBook.curTextChapter?.pages + } + val positions = ReadBook.searchResultPositions(pages, indexWithinChapter, query!!) + //todo: show selected text + val job1 = async(Main){ + ReadBook.skipToPage(positions[0]) + page_view.curPage.selectStartMoveIndex(positions[0], positions[1], 0) + page_view.curPage.selectEndMoveIndex(positions[0], positions[1], 0 + query.length ) + page_view.isTextSelected = true + } + job1.await() } } requestCodeReplace -> onReplaceRuleSave() } + } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt index 6d069ff68..26423e701 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt @@ -200,6 +200,12 @@ class ReadMenu : FrameLayout { } } + ll_search.onClick { + runMenuOut { + callBack?.openSearchList() + } + } + //朗读 ll_read_aloud.onClick { runMenuOut { @@ -291,6 +297,7 @@ class ReadMenu : FrameLayout { fun autoPage() fun openReplaceRule() fun openChapterList() + fun openSearchList() fun showReadStyle() fun showMoreSetting() fun showReadAloudDialog() diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt new file mode 100644 index 000000000..ec1cbc0c7 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt @@ -0,0 +1,93 @@ +package io.legado.app.ui.book.searchContent + +import android.os.Bundle +import android.view.Menu +import androidx.appcompat.widget.SearchView +import androidx.core.view.isGone +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import io.legado.app.R +import io.legado.app.base.VMBaseActivity +import io.legado.app.lib.theme.ATH +import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.primaryTextColor +import io.legado.app.utils.getViewModel +import io.legado.app.utils.gone +import io.legado.app.utils.visible +import kotlinx.android.synthetic.main.activity_chapter_list.* +import kotlinx.android.synthetic.main.view_tab_layout.* + + +class SearchListActivity : VMBaseActivity(R.layout.activity_search_list) { + // todo: 完善搜索界面UI + override val viewModel: SearchListViewModel + get() = getViewModel(SearchListViewModel::class.java) + + private var searchView: SearchView? = null + + override fun onActivityCreated(savedInstanceState: Bundle?) { + tab_layout.isTabIndicatorFullWidth = false + tab_layout.setSelectedTabIndicatorColor(accentColor) + intent.getStringExtra("bookUrl")?.let { + viewModel.initBook(it) { + view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager) + tab_layout.setupWithViewPager(view_pager) + } + } + } + + override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.search_view, menu) + val search = menu.findItem(R.id.menu_search) + searchView = search.actionView as SearchView + ATH.setTint(searchView!!, primaryTextColor) + searchView?.maxWidth = resources.displayMetrics.widthPixels + searchView?.onActionViewCollapsed() + searchView?.setOnCloseListener { + tab_layout.visible() + //to do clean + false + } + searchView?.setOnSearchClickListener { tab_layout.gone() } + searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + if (viewModel.lastQuery != query){ + viewModel.startContentSearch(query) + } + return false + } + + override fun onQueryTextChange(newText: String): Boolean { + + return false + } + }) + return super.onCompatCreateOptionsMenu(menu) + } + + private inner class TabFragmentPageAdapter(fm: FragmentManager) : + FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + override fun getItem(position: Int): Fragment { + return SearchListFragment() + } + + override fun getCount(): Int { + return 1 + } + + override fun getPageTitle(position: Int): CharSequence? { + return "Search" + } + + } + + override fun onBackPressed() { + if (tab_layout.isGone) { + searchView?.onActionViewCollapsed() + tab_layout.visible() + } else { + super.onBackPressed() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt new file mode 100644 index 000000000..d2c98b955 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt @@ -0,0 +1,51 @@ +package io.legado.app.ui.book.searchContent + +import android.content.Context +import android.os.Build +import android.text.Html +import android.util.Log +import android.view.View +import androidx.annotation.RequiresApi +import androidx.core.text.HtmlCompat +import io.legado.app.R +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.data.entities.BookChapter +import io.legado.app.help.BookHelp +import io.legado.app.lib.theme.accentColor +import io.legado.app.utils.getCompatColor +import io.legado.app.utils.visible +import kotlinx.android.synthetic.main.item_bookmark.view.* +import kotlinx.android.synthetic.main.item_search_list.view.* +import org.jetbrains.anko.sdk27.listeners.onClick + +class SearchListAdapter(context: Context, val callback: Callback) : + SimpleRecyclerAdapter(context, R.layout.item_search_list) { + + val cacheFileNames = hashSetOf() + + override fun convert(holder: ItemViewHolder, item: SearchResult, payloads: MutableList) { + with(holder.itemView) { + val isDur = callback.durChapterIndex() == item.chapterIndex + if (payloads.isEmpty()) { + tv_search_result.text = item.parseText(item.presentText) + if (isDur){ + tv_search_result.paint.isFakeBoldText = true + } + } + } + } + + override fun registerListener(holder: ItemViewHolder) { + holder.itemView.onClick { + getItem(holder.layoutPosition)?.let { + callback.openSearchResult(it) + } + } + } + + interface Callback { + fun openSearchResult(searchResult: SearchResult) + fun durChapterIndex(): Int + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt new file mode 100644 index 000000000..38ac013df --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt @@ -0,0 +1,239 @@ +package io.legado.app.ui.book.searchContent + +import android.annotation.SuppressLint +import android.app.Activity.RESULT_OK +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.View +import androidx.lifecycle.LiveData +import com.hankcs.hanlp.HanLP +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.VMBaseFragment +import io.legado.app.constant.EventBus +import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookChapter +import io.legado.app.help.AppConfig +import io.legado.app.help.BookHelp +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor +import io.legado.app.service.help.ReadBook +import io.legado.app.ui.book.read.page.entities.TextPage +import io.legado.app.ui.book.read.page.provider.ChapterProvider +import io.legado.app.ui.widget.recycler.UpLinearLayoutManager +import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.ColorUtils +import io.legado.app.utils.getViewModelOfActivity +import io.legado.app.utils.observeEvent +import kotlinx.android.synthetic.main.fragment_search_list.* +import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import org.jetbrains.anko.sdk27.listeners.onClick +import java.util.regex.Pattern + +class SearchListFragment : VMBaseFragment(R.layout.fragment_search_list), + SearchListAdapter.Callback, + SearchListViewModel.SearchListCallBack{ + override val viewModel: SearchListViewModel + get() = getViewModelOfActivity(SearchListViewModel::class.java) + + lateinit var adapter: SearchListAdapter + private lateinit var mLayoutManager: UpLinearLayoutManager + private var searchResultCounts = 0 + private var durChapterIndex = 0 + private var searchResultList: MutableList = mutableListOf() + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + viewModel.searchCallBack = this + val bbg = bottomBackground + val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg)) + ll_search_base_info.setBackgroundColor(bbg) + tv_current_search_info.setTextColor(btc) + iv_search_content_top.setColorFilter(btc) + iv_search_content_bottom.setColorFilter(btc) + initRecyclerView() + initView() + initBook() + } + + private fun initRecyclerView() { + adapter = SearchListAdapter(requireContext(), this) + mLayoutManager = UpLinearLayoutManager(requireContext()) + recycler_view.layoutManager = mLayoutManager + recycler_view.addItemDecoration(VerticalDivider(requireContext())) + recycler_view.adapter = adapter + } + + private fun initView() { + iv_search_content_top.onClick { mLayoutManager.scrollToPositionWithOffset(0, 0) } + iv_search_content_bottom.onClick { + if (adapter.itemCount > 0) { + mLayoutManager.scrollToPositionWithOffset(adapter.itemCount - 1, 0) + } + } + } + + @SuppressLint("SetTextI18n") + private fun initBook() { + launch { + + tv_current_search_info.text = "搜索结果:$searchResultCounts" + viewModel.book?.let { + initCacheFileNames(it) + durChapterIndex = it.durChapterIndex + } + } + } + + private fun initCacheFileNames(book: Book) { + launch(IO) { + adapter.cacheFileNames.addAll(BookHelp.getChapterFiles(book)) + withContext(Main) { + adapter.notifyItemRangeChanged(0, adapter.getActualItemCount(), true) + } + } + } + + override fun observeLiveBus() { + observeEvent(EventBus.SAVE_CONTENT) { chapter -> + viewModel.book?.bookUrl?.let { bookUrl -> + if (chapter.bookUrl == bookUrl) { + adapter.cacheFileNames.add(BookHelp.formatChapterName(chapter)) + adapter.notifyItemChanged(chapter.index, true) + } + } + } + } + + @SuppressLint("SetTextI18n") + override fun startContentSearch(newText: String) { + // 按章节搜索内容 + if (!newText.isBlank()) { + adapter.clearItems() + searchResultList.clear() + searchResultCounts = 0 + viewModel.lastQuery = newText + var searchResults = listOf() + launch(Main){ + App.db.bookChapterDao().getChapterList(viewModel.bookUrl).map{ chapter -> + val job = async(IO){ + if (isLocalBook || adapter.cacheFileNames.contains(BookHelp.formatChapterName(chapter))) { + searchResults = searchChapter(newText, chapter) + } + } + job.await() + if (searchResults.isNotEmpty()){ + searchResultList.addAll(searchResults) + tv_current_search_info.text = "搜索结果:$searchResultCounts" + adapter.addItems(searchResults) + searchResults = listOf() + } + } + } + } + } + + private suspend fun searchChapter(query: String, chapter: BookChapter?): List { + val searchResults: MutableList = mutableListOf() + var positions : List = listOf() + var replaceContents: List? = null + var totalContents = "" + if (chapter != null){ + viewModel.book?.let { bookSource -> + val bookContent = BookHelp.getContent(bookSource, chapter) + if (bookContent != null){ + //搜索替换后的正文 + val job = async(IO) { + chapter.title = when (AppConfig.chineseConverterType) { + 1 -> HanLP.convertToSimplifiedChinese(chapter.title) + 2 -> HanLP.convertToTraditionalChinese(chapter.title) + else -> chapter.title + } + replaceContents = BookHelp.disposeContent( + chapter.title, + bookSource.name, + bookSource.bookUrl, + bookContent, + bookSource.useReplaceRule + ) + } + job.await() + while (replaceContents == null){ + delay(100L) + } + totalContents = replaceContents!!.joinToString("") + positions = searchPosition(totalContents, query) + var count = 1 + positions.map{ + val construct = constructText(totalContents, it, query) + val result = SearchResult(index = searchResultCounts, + indexWithinChapter = count, + text = construct[1] as String, + chapterTitle = chapter.title, + query = query, + chapterIndex = chapter.index, + newPosition = construct[0] as Int, + contentPosition = it + ) + count += 1 + searchResultCounts += 1 + searchResults.add(result) + } + } + } + } + return searchResults + } + + private fun searchPosition(content: String, pattern: String): List { + val position : MutableList = mutableListOf() + var index = content.indexOf(pattern) + while(index >= 0){ + position.add(index) + index = content.indexOf(pattern, index + 1); + } + return position + } + + private fun constructText(content: String, position: Int, query: String): Array{ + // 构建关键词周边文字,在搜索结果里显示 + // todo: 判断段落,只在关键词所在段落内分割 + // todo: 利用标点符号分割完整的句 + // todo: length和设置结合,自由调整周边文字长度 + val length = 20 + var po1 = position - length + var po2 = position + query.length + length + if (po1 <0) { + po1 = 0 + } + if (po2 > content.length){ + po2 = content.length + } + val newPosition = position - po1 + val newText = content.substring(po1, po2) + return arrayOf(newPosition, newText) + } + + val isLocalBook: Boolean + get() = viewModel.book?.isLocalBook() == true + + override fun openSearchResult(searchResult: SearchResult) { + + val searchData = Intent() + searchData.putExtra("index", searchResult.chapterIndex) + searchData.putExtra("contentPosition", searchResult.contentPosition) + searchData.putExtra("query", searchResult.query) + searchData.putExtra("indexWithinChapter", searchResult.indexWithinChapter) + activity?.setResult(RESULT_OK, searchData) + activity?.finish() + + + } + + override fun durChapterIndex(): Int { + return durChapterIndex + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt new file mode 100644 index 000000000..060d74067 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt @@ -0,0 +1,33 @@ +package io.legado.app.ui.book.searchContent + + +import android.app.Application +import io.legado.app.App +import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.Book + +class SearchListViewModel(application: Application) : BaseViewModel(application) { + var bookUrl: String = "" + var book: Book? = null + var searchCallBack: SearchListCallBack? = null + var lastQuery: String = "" + + fun initBook(bookUrl: String, success: () -> Unit) { + this.bookUrl = bookUrl + execute { + book = App.db.bookDao().getBook(bookUrl) + }.onSuccess { + success.invoke() + } + } + + fun startContentSearch(newText: String) { + searchCallBack?.startContentSearch(newText) + } + + + interface SearchListCallBack { + fun startContentSearch(newText: String) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt new file mode 100644 index 000000000..6b0d9df5b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt @@ -0,0 +1,40 @@ +package io.legado.app.ui.book.searchContent + +import android.text.Spanned +import android.util.Log +import androidx.core.text.HtmlCompat +import io.legado.app.ui.book.read.page.entities.TextPage + +data class SearchResult( + var index: Int = 0, + var indexWithinChapter: Int = 0, + var text: String = "", + var chapterTitle: String = "", + val query: String, + var pageSize: Int = 0, + var chapterIndex: Int = 0, + var pageIndex: Int = 0, + var newPosition: Int = 0, + var contentPosition: Int =0 +) { + val presentText: String + get(){ + return colorPresentText(newPosition, query, text) + + "($chapterTitle)" + } + + fun colorPresentText(position: Int, center: String, targetText: String): String{ + val sub1 = text.substring(0, position) + val sub2 = text.substring(position + center.length, targetText.length) + return "$sub1" + + "$center" + + "$sub2" + } + + fun parseText(targetText: String): Spanned { + return HtmlCompat.fromHtml(targetText, HtmlCompat.FROM_HTML_MODE_LEGACY) + } + + + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_search_list.xml b/app/src/main/res/layout/activity_search_list.xml new file mode 100644 index 000000000..159f77324 --- /dev/null +++ b/app/src/main/res/layout/activity_search_list.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_list.xml b/app/src/main/res/layout/fragment_search_list.xml new file mode 100644 index 000000000..79fefe11f --- /dev/null +++ b/app/src/main/res/layout/fragment_search_list.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_search_list.xml b/app/src/main/res/layout/item_search_list.xml new file mode 100644 index 000000000..2dcaf1436 --- /dev/null +++ b/app/src/main/res/layout/item_search_list.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_read_menu.xml b/app/src/main/res/layout/view_read_menu.xml index 1b9bc2a98..b63fba1ff 100644 --- a/app/src/main/res/layout/view_read_menu.xml +++ b/app/src/main/res/layout/view_read_menu.xml @@ -260,6 +260,45 @@ android:textSize="12sp" /> + + + + + + + + + + 切換默認主題 分享選中書源 時間排序 + 搜索 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index c14a9a485..006b4ff45 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -761,6 +761,7 @@ 切換默認主題 分享選中書源 時間排序 + 搜索 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 036bcd6f9..78487a2be 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -761,5 +761,6 @@ 使用保存主题,导入,分享主题 切换默认主题 时间排序 + 搜索 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 92888ae19..893230b30 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -763,5 +763,6 @@ Save, Import, Share theme Share selected sources Sort by update time + Search \ No newline at end of file