新增书籍内容搜索

master
fengyuecanzhu 3 years ago
parent c34a46e529
commit aeaa853ece
  1. 4
      .idea/assetWizardSettings.xml
  2. 13
      .idea/misc.xml
  3. 1
      app/src/main/AndroidManifest.xml
  4. 7
      app/src/main/assets/updatelog.fy
  5. 4
      app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
  6. 12
      app/src/main/java/xyz/fycz/myreader/entity/SearchWord1.kt
  7. 14
      app/src/main/java/xyz/fycz/myreader/entity/SearchWord2.kt
  8. 2
      app/src/main/java/xyz/fycz/myreader/experiment/BookWCEstimate.kt
  9. 5
      app/src/main/java/xyz/fycz/myreader/model/SearchEngine.java
  10. 195
      app/src/main/java/xyz/fycz/myreader/model/SearchWordEngine.kt
  11. 2
      app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java
  12. 18
      app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java
  13. 16
      app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java
  14. 155
      app/src/main/java/xyz/fycz/myreader/ui/activity/SearchWordActivity.kt
  15. 20
      app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchAdapter.kt
  16. 2
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/FindBookHolder.java
  17. 2
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java
  18. 56
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchWord1Holder.kt
  19. 37
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchWord2Holder.kt
  20. 11
      app/src/main/java/xyz/fycz/myreader/ui/fragment/DIYSourceFragment.java
  21. 1
      app/src/main/java/xyz/fycz/myreader/util/help/ChapterContentHelp.java
  22. 7
      app/src/main/java/xyz/fycz/myreader/util/utils/KeyWordUtils.java
  23. 2
      app/src/main/java/xyz/fycz/myreader/widget/RefreshProgressBar.java
  24. 2
      app/src/main/java/xyz/fycz/myreader/widget/page/EpubPageLoader.java
  25. 3
      app/src/main/java/xyz/fycz/myreader/widget/page/LocalPageLoader.java
  26. 2
      app/src/main/java/xyz/fycz/myreader/widget/page/NetPageLoader.java
  27. 86
      app/src/main/java/xyz/fycz/myreader/widget/page/PageLoader.java
  28. 2
      app/src/main/java/xyz/fycz/myreader/widget/page/TxtChapter.java
  29. 12
      app/src/main/res/drawable/ic_search_word.xml
  30. 9
      app/src/main/res/layout/activity_search_book.xml
  31. 77
      app/src/main/res/layout/activity_search_word.xml
  32. 2
      app/src/main/res/layout/dialog_textview.xml
  33. 0
      app/src/main/res/layout/item_search_book.xml
  34. 21
      app/src/main/res/layout/item_search_word1.xml
  35. 20
      app/src/main/res/layout/item_search_word2.xml
  36. 61
      app/src/main/res/menu/menu_read.xml
  37. 3
      app/src/main/res/values/strings.xml
  38. 4
      app/version_code.properties

@ -14,8 +14,8 @@
<option name="values">
<map>
<entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="ic_wc" />
<entry key="sourceFile" value="F:\SVG图标\数字.svg" />
<entry key="outputName" value="ic_search_word" />
<entry key="sourceFile" value="F:\SVG图标\搜索_o.svg" />
</map>
</option>
</PersistentState>

@ -4,6 +4,7 @@
<option name="filePathToZoomLevelMap">
<map>
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_re_seg.xml" value="0.1962962962962963" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_search_word.xml" value="0.21944444444444444" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_about.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_donate.xml" value="0.536" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_feedback.xml" value="0.12132725430597771" />
@ -11,17 +12,29 @@
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_group_manager.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_manage_group.xml" value="0.21195652173913043" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_more_setting.xml" value="0.264" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_read.xml" value="0.17028985507246377" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_search_book.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_search_word.xml" value="0.144" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/dialog_textview.xml" value="0.1947463768115942" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/fingerprint_dialog.xml" value="0.20425724637681159" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_file_category.xml" value="0.2318840579710145" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_find_book_2.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_local_source.xml" value="0.1947463768115942" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_private_bookcase.xml" value="0.21195652173913043" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/fragment_webdav_setting.xml" value="0.4" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/gridview_book_detailed_item.xml" value="0.1947463768115942" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_book_source.xml" value="0.21195652173913043" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_group.xml" value="0.4" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_local_source.xml" value="0.21195652173913043" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_replace_rule.xml" value="0.1947463768115942" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_search_book.xml" value="0.1947463768115942" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_search_word1.xml" value="0.2889273356401384" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_search_word2.xml" value="0.1947463768115942" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/item_search_word_1.xml" value="0.1947463768115942" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/layout_about_content.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/layout_book_detail_header.xml" value="0.536" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/listview_search_book_item.xml" value="0.22010869565217392" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/search_book_item.xml" value="0.1947463768115942" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/view_file_picker.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/menu/menu_book.xml" value="0.13703703703703704" />
<entry key="..\:/android/FYReader/app/src/main/res/menu/menu_book_detail.xml" value="0.24947916666666667" />

@ -122,6 +122,7 @@
<activity android:name=".ui.activity.ReadRecordActivity" />
<activity android:name=".ui.activity.FindBookActivity" />
<activity android:name=".ui.activity.GroupManagerActivity" />
<activity android:name=".ui.activity.SearchWordActivity" />
<receiver android:name=".util.notification.NotificationClickReceiver" />
<receiver android:name=".ui.presenter.BookcasePresenter$cancelDownloadReceiver" />

@ -1,3 +1,10 @@
2021.12.06
风月读书v2.2.4
更新内容:
1、新增书籍内容搜索
2、修复内容替换bug
3、修复书源导入bug
2021.12.04
风月读书v2.2.3
更新内容:

@ -65,6 +65,9 @@ public class APPCONST {
public static final String RESULT_HISTORY_CHAPTER = "result_history_chapter";
public static final String RESULT_UP_MENU = "result_up_menu";
public static final String SEARCH_KEY = "searchKey";
public static final String BOOK_KEY = "bookKey";
public static final String CHAPTERS_KEY = "chaptersKey";
public static final String PAGE_LOADER_KEY = "pageLoaderKey";
public static final String[] READ_STYLE_NIGHT = {"#94928c", "#393431"};//黑夜
@ -95,6 +98,7 @@ public class APPCONST {
public static final int REQUEST_SELECT_COVER = 1012;
public static final int REQUEST_EDIT_BOOK = 1013;
public static final int REQUEST_GROUP_MANAGER = 1014;
public static final int REQUEST_SEARCH_WORD = 1015;
public static final int REQUEST_READ = 1;

@ -0,0 +1,12 @@
package xyz.fycz.myreader.entity
/**
* @author fengyue
* @date 2021/12/5 20:18
*/
data class SearchWord1(
var bookId: String,
var chapterNum: Int,
var chapterTitle: String,
var searchWord2List: MutableList<SearchWord2>
)

@ -0,0 +1,14 @@
package xyz.fycz.myreader.entity
/**
* @author fengyue
* @date 2021/12/5 20:03
*/
data class SearchWord2(
var keyword: String,
var chapterNum: Int,
var dataStr: String,
var dataIndex: Int,
var index: Int,
var count: Int
)

@ -18,6 +18,7 @@ class BookWCEstimate {
/**
* -1本地文件不存在
* -2书籍章节缓存数量少于20
* -3epub书籍暂不支持计算字数
*/
fun getWordCount(book: Book): Int {
if (book.type == "本地书籍") {
@ -29,6 +30,7 @@ class BookWCEstimate {
}
private fun getLocalWordCount(book: Book): Int {
if (book.chapterUrl.endsWith(".epub")) return -3
val file = File(book.chapterUrl)
if (!file.exists()) return -1
return -countChar(file)

@ -88,7 +88,8 @@ public class SearchEngine {
* 关闭引擎
*/
public void closeSearchEngine() {
executorService.shutdown();
if (executorService != null)
executorService.shutdown();
if (!compositeDisposable.isDisposed())
compositeDisposable.dispose();
compositeDisposable = null;
@ -170,7 +171,7 @@ public class SearchEngine {
}
});
} else {
if (searchFinishNum >= mSourceList.size()) {
if (searchFinishNum == mSourceList.size()) {
if (searchSuccessNum == 0) {
searchListener.searchBookError(new Throwable("未搜索到内容"));
}

@ -0,0 +1,195 @@
package xyz.fycz.myreader.model
import io.reactivex.*
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import xyz.fycz.myreader.R
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.entity.SearchWord1
import xyz.fycz.myreader.entity.SearchWord2
import xyz.fycz.myreader.greendao.entity.Book
import xyz.fycz.myreader.greendao.entity.Chapter
import xyz.fycz.myreader.greendao.service.ChapterService
import xyz.fycz.myreader.util.SharedPreUtils
import xyz.fycz.myreader.util.ToastUtils
import xyz.fycz.myreader.util.help.ChapterContentHelp
import xyz.fycz.myreader.widget.page.PageLoader
import java.io.File
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
/**
* @author fengyue
* @date 2021/12/5 21:17
*/
class SearchWordEngine(
private val book: Book,
private val chapters: List<Chapter>,
private val pageLoader: PageLoader
) {
private val TAG = "SearchWordEngine"
//线程池
private var executorService: ExecutorService
private var scheduler: Scheduler
private var compositeDisposable: CompositeDisposable
private lateinit var searchListener: OnSearchListener
private val threadsNum =
SharedPreUtils.getInstance().getInt(App.getmContext().getString(R.string.threadNum), 8);
private var searchSiteIndex = 0
private var searchSuccessNum = 0
private var searchFinishNum = 0
private var isLocalBook = false
fun setOnSearchListener(searchListener: OnSearchListener) {
this.searchListener = searchListener
}
init {
executorService = Executors.newFixedThreadPool(threadsNum)
scheduler = Schedulers.from(executorService)
compositeDisposable = CompositeDisposable()
}
fun stopSearch() {
compositeDisposable.dispose()
compositeDisposable = CompositeDisposable()
searchListener.loadFinish(searchSuccessNum == 0)
}
/**
* 关闭引擎
*/
fun closeSearchEngine() {
executorService.shutdown()
if (!compositeDisposable.isDisposed) compositeDisposable.dispose()
}
/**
* 搜索关键字(模糊搜索)
*
* @param keyword
*/
fun search(keyword: String) {
if ("本地书籍" == book.type) {
isLocalBook = true
if (!File(book.chapterUrl).exists()) {
ToastUtils.showWarring("当前书籍源文件不存在,无法搜索!")
searchListener.loadFinish(true)
return
}
}
if (chapters.isEmpty()) {
ToastUtils.showWarring("当前书籍章节目录为空,无法搜索!")
searchListener.loadFinish(true)
return
}
searchSuccessNum = 0
searchSiteIndex = -1
searchFinishNum = 0
for (i in 0 until Math.min(threadsNum, chapters.size)) {
searchOnEngine(keyword)
}
}
@Synchronized
private fun searchOnEngine(keyword: String) {
searchSiteIndex++
if (searchSiteIndex < chapters.size) {
val chapterNum = searchSiteIndex
val chapter = chapters[chapterNum]
Observable.create(ObservableOnSubscribe<SearchWord1> { emitter ->
val searchWord1 =
SearchWord1(book.id, chapterNum, chapter.title, mutableListOf())
if (!isLocalBook && !ChapterService.isChapterCached(book.id, chapter.title)) {
emitter.onNext(searchWord1)
return@ObservableOnSubscribe
}
var content = pageLoader.getChapterReader(chapter)
content = pageLoader.contentHelper.replaceContent(
book.name + "-" + book.author,
book.source,
content,
true
)
if (book.reSeg) {
content = ChapterContentHelp.LightNovelParagraph2(content, chapter.title)
}
val allLine: List<String> = content.split("\n")
var count = 0
allLine.forEach {
var index: Int = -1
while (it.indexOf(keyword, index + 1).also { index = it } != -1) {
var leftI = 0
var rightI = it.length
var leftS = ""
var rightS = ""
if (leftI < index - 20) {
leftI = index - 20
leftS = "..."
}
if (rightI > index + 20) {
rightI = index + 20
rightS = "..."
}
val str = leftS + it.substring(leftI, rightI) + rightS
val searchWord2 =
SearchWord2(
keyword,
chapterNum,
str,
index - leftI + leftS.length,
index,
count
)
searchWord1.searchWord2List.add(searchWord2)
count++
}
}
emitter.onNext(searchWord1)
emitter.onComplete()
}).subscribeOn(scheduler).observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<SearchWord1?> {
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onNext(searchWord1: SearchWord1) {
searchFinishNum++
if (searchWord1.searchWord2List.isNotEmpty()) {
searchSuccessNum++
searchListener.loadMore(searchWord1)
}
searchOnEngine(keyword)
}
override fun onError(e: Throwable) {
searchFinishNum++
searchOnEngine(keyword)
if (App.isDebug()) e.printStackTrace()
}
override fun onComplete() {
}
})
} else {
if (searchFinishNum == chapters.size) {
if (searchSuccessNum == 0) {
ToastUtils.showWarring("搜索结果为空")
searchListener.loadFinish(true)
} else {
searchListener.loadFinish(false)
}
}
}
}
interface OnSearchListener {
fun loadFinish(isEmpty: Boolean)
fun loadMore(item: SearchWord1)
}
}

@ -671,6 +671,8 @@ public class BookDetailedActivity extends BaseActivity {
ToastUtils.showWarring("书籍源文件不存在,无法计算字数");
} else if (count == -2) {
ToastUtils.showWarring("已缓存章节数量不足20,无法计算字数");
} else if (count == -3) {
ToastUtils.showWarring("epub书籍暂不支持计算字数");
} else {
String tip = "\n";
if (count > 0) {

@ -778,6 +778,16 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
sourceIntent.putExtra(APPCONST.BOOK_SOURCE, source);
startActivity(sourceIntent);
}
} else if (itemId == R.id.action_search) {
BitIntentDataManager.getInstance().putData(APPCONST.BOOK_KEY, mBook);
BitIntentDataManager.getInstance().putData(APPCONST.CHAPTERS_KEY, mChapters);
BitIntentDataManager.getInstance().putData(APPCONST.PAGE_LOADER_KEY, mPageLoader);
//切换菜单
toggleMenu(true);
//跳转
mHandler.postDelayed(() -> {
startActivityForResult(new Intent(this, SearchWordActivity.class), APPCONST.REQUEST_SEARCH_WORD);
}, mBottomOutAnim.getDuration());
}
return super.onOptionsItemSelected(item);
}
@ -837,6 +847,13 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
String zipPath = getPath(this, data.getData());
binding.readCustomizeLayoutMenu.zip2Layout(zipPath);
break;
case APPCONST.REQUEST_SEARCH_WORD:
int chapterNum = data.getIntExtra("chapterNum", chapterPos);
int countInChapter = data.getIntExtra("countInChapter", 0);
String keyword = data.getStringExtra("keyword");
if (!TextUtils.isEmpty(keyword))
mPageLoader.skipToSearch(chapterNum, countInChapter, keyword);
break;
}
}
super.onActivityResult(requestCode, resultCode, data);
@ -1876,7 +1893,6 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
cursorShow();
//set current word selected
binding.readPvContent.invalidate();
// hideSnackBar();
}

@ -117,6 +117,18 @@ public class SearchBookActivity extends BaseActivity {
getSupportActionBar().setTitle("搜索");
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus){
App.getHandler().postDelayed(() -> {
binding.etSearchKey.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.showSoftInput(binding.etSearchKey, InputMethodManager.SHOW_IMPLICIT);
}, 400);
}
}
@Override
protected void initData(Bundle savedInstanceState) {
super.initData(savedInstanceState);
@ -226,9 +238,6 @@ public class SearchBookActivity extends BaseActivity {
search();
});
initHistoryList();
App.getHandler().postDelayed(() -> {
binding.etSearchKey.requestFocus();
}, 200);
}
@Override
@ -625,6 +634,7 @@ public class SearchBookActivity extends BaseActivity {
@Override
protected void onDestroy() {
stopSearch();
searchEngine.closeSearchEngine();
super.onDestroy();
}

@ -0,0 +1,155 @@
package xyz.fycz.myreader.ui.activity
import android.os.Bundle
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
import org.jetbrains.anko.contentView
import xyz.fycz.myreader.R
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.base.BaseActivity
import xyz.fycz.myreader.base.BitIntentDataManager
import xyz.fycz.myreader.base.adapter.BaseListAdapter
import xyz.fycz.myreader.base.adapter.IViewHolder
import xyz.fycz.myreader.base.adapter2.onClick
import xyz.fycz.myreader.common.APPCONST
import xyz.fycz.myreader.databinding.ActivitySearchWordBinding
import xyz.fycz.myreader.entity.SearchWord1
import xyz.fycz.myreader.greendao.entity.Book
import xyz.fycz.myreader.greendao.entity.Chapter
import xyz.fycz.myreader.model.SearchWordEngine
import xyz.fycz.myreader.ui.adapter.holder.SearchWord1Holder
import xyz.fycz.myreader.ui.adapter.holder.SearchWord2Holder
import xyz.fycz.myreader.util.ToastUtils
import xyz.fycz.myreader.widget.page.PageLoader
/**
* @author fengyue
* @date 2021/12/5 19:57
*/
class SearchWordActivity : BaseActivity() {
private lateinit var binding: ActivitySearchWordBinding
private lateinit var book: Book
private lateinit var searchWordEngine: SearchWordEngine
private lateinit var adapter: BaseListAdapter<SearchWord1>
override fun bindView() {
binding = ActivitySearchWordBinding.inflate(layoutInflater)
setContentView(binding.root)
}
override fun setUpToolbar(toolbar: Toolbar?) {
super.setUpToolbar(toolbar)
setStatusBarColor(R.color.colorPrimary, true)
supportActionBar?.title = getString(R.string.search_word)
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
App.getHandler().postDelayed({
binding.etSearchKey.requestFocus()
val imm =
getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(
binding.etSearchKey,
InputMethodManager.SHOW_IMPLICIT
)
}, 400)
}
}
override fun initData(savedInstanceState: Bundle?) {
super.initData(savedInstanceState)
book = BitIntentDataManager.getInstance().getData(APPCONST.BOOK_KEY) as Book
val chapters =
BitIntentDataManager.getInstance().getData(APPCONST.CHAPTERS_KEY) as List<Chapter>
val pageLoader =
BitIntentDataManager.getInstance().getData(APPCONST.PAGE_LOADER_KEY) as PageLoader
searchWordEngine = SearchWordEngine(book, chapters, pageLoader)
}
override fun initWidget() {
//enter事件
binding.etSearchKey.setOnEditorActionListener { _, i, keyEvent ->
if (i == EditorInfo.IME_ACTION_UNSPECIFIED) {
search()
return@setOnEditorActionListener keyEvent.keyCode == KeyEvent.KEYCODE_ENTER
}
false
}
searchWordEngine.setOnSearchListener(object : SearchWordEngine.OnSearchListener {
override fun loadFinish(isEmpty: Boolean) {
binding.fabSearchStop.visibility = View.GONE
binding.rpb.isAutoLoading = false
}
@Synchronized
override fun loadMore(item: SearchWord1) {
if (adapter.itemSize == 0) {
adapter.addItem(item)
} else {
for ((index, searchWord1) in adapter.items.withIndex()) {
if (index == 0 && item.chapterNum < searchWord1.chapterNum) {
adapter.addItem(0, item)
break
} else if (item.chapterNum >= searchWord1.chapterNum &&
item.chapterNum < adapter.items[index + 1].chapterNum
) {
adapter.addItem(index + 1, item)
break
} else if (index == adapter.itemSize - 1) {
adapter.addItem(item)
break
}
}
}
}
})
adapter = object : BaseListAdapter<SearchWord1>() {
override fun createViewHolder(viewType: Int): IViewHolder<SearchWord1> {
return SearchWord1Holder(this@SearchWordActivity)
}
}
binding.rvSearchWord1.layoutManager = LinearLayoutManager(this)
binding.rvSearchWord1.adapter = adapter
}
override fun initClick() {
binding.tvSearchConform.onClick { search() }
binding.fabSearchStop.onClick { stopSearch() }
}
private fun search() {
val keyword = binding.etSearchKey.text.toString()
if (keyword.isNotEmpty()) {
adapter.clear()
binding.fabSearchStop.visibility = View.VISIBLE
binding.rpb.isAutoLoading = true
searchWordEngine.search(keyword)
//收起软键盘
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(
binding.etSearchKey.windowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
}
}
private fun stopSearch() {
binding.fabSearchStop.visibility = View.GONE
binding.rpb.isAutoLoading = false
searchWordEngine.stopSearch()
}
override fun onDestroy() {
super.onDestroy()
searchWordEngine.stopSearch()
searchWordEngine.closeSearchEngine()
}
}

@ -14,7 +14,7 @@ import xyz.fycz.myreader.application.SysManager
import xyz.fycz.myreader.base.BitIntentDataManager
import xyz.fycz.myreader.base.adapter2.DiffRecyclerAdapter
import xyz.fycz.myreader.base.adapter2.ItemViewHolder
import xyz.fycz.myreader.databinding.SearchBookItemBinding
import xyz.fycz.myreader.databinding.ItemSearchBookBinding
import xyz.fycz.myreader.entity.SearchBookBean
import xyz.fycz.myreader.greendao.entity.Book
import xyz.fycz.myreader.model.mulvalmap.ConMVMap
@ -32,7 +32,7 @@ import xyz.fycz.myreader.util.utils.StringUtils
class SearchAdapter(
context: Context,
val keyword: String
) : DiffRecyclerAdapter<SearchBookBean, SearchBookItemBinding>(context) {
) : DiffRecyclerAdapter<SearchBookBean, ItemSearchBookBinding>(context) {
private val mBooks: ConMVMap<SearchBookBean, Book> = ConMVMap()
private lateinit var mList: List<SearchBookBean>
private val tagList: MutableList<String> = ArrayList()
@ -79,13 +79,13 @@ class SearchAdapter(
}
}
override fun getViewBinding(parent: ViewGroup): SearchBookItemBinding {
return SearchBookItemBinding.inflate(inflater, parent, false)
override fun getViewBinding(parent: ViewGroup): ItemSearchBookBinding {
return ItemSearchBookBinding.inflate(inflater, parent, false)
}
override fun convert(
holder: ItemViewHolder,
binding: SearchBookItemBinding,
binding: ItemSearchBookBinding,
item: SearchBookBean,
payloads: MutableList<Any>
) {
@ -99,7 +99,7 @@ class SearchAdapter(
}
}
private fun bind(binding: SearchBookItemBinding, data: SearchBookBean) {
private fun bind(binding: ItemSearchBookBinding, data: SearchBookBean) {
var aBooks = mBooks.getValues(data)
if (aBooks == null || aBooks.size == 0) {
aBooks = ArrayList()
@ -124,7 +124,7 @@ class SearchAdapter(
}
}
private fun bindChange(binding: SearchBookItemBinding, data: SearchBookBean, payload: Bundle) {
private fun bindChange(binding: ItemSearchBookBinding, data: SearchBookBean, payload: Bundle) {
binding.run {
initTagList(this, data)
payload.keySet().forEach {
@ -146,7 +146,7 @@ class SearchAdapter(
}
}
private fun initTagList(binding: SearchBookItemBinding, data: SearchBookBean) {
private fun initTagList(binding: ItemSearchBookBinding, data: SearchBookBean) {
tagList.clear()
val type = data.type
if (!type.isNullOrEmpty()){
@ -166,7 +166,7 @@ class SearchAdapter(
}
}
private fun upLast(binding: SearchBookItemBinding, lastChapter: String?) {
private fun upLast(binding: ItemSearchBookBinding, lastChapter: String?) {
binding.run {
if (lastChapter.isNullOrEmpty()) {
tvBookNewestChapter.visibility = View.GONE
@ -180,7 +180,7 @@ class SearchAdapter(
}
}
override fun registerListener(holder: ItemViewHolder, binding: SearchBookItemBinding) {
override fun registerListener(holder: ItemViewHolder, binding: ItemSearchBookBinding) {
binding.root.setOnClickListener {
getItem(holder.layoutPosition)?.let {
val books = mBooks.getValues(it)

@ -50,7 +50,7 @@ public class FindBookHolder extends ViewHolderImpl<Book> {
@Override
protected int getItemLayoutId() {
return R.layout.search_book_item;
return R.layout.item_search_book;
}
@Override

@ -60,7 +60,7 @@ public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
@Override
protected int getItemLayoutId() {
return R.layout.search_book_item;
return R.layout.item_search_book;
}
@Override

@ -0,0 +1,56 @@
package xyz.fycz.myreader.ui.adapter.holder
import android.app.Activity
import android.content.Intent
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import xyz.fycz.myreader.R
import xyz.fycz.myreader.base.adapter.BaseListAdapter
import xyz.fycz.myreader.base.adapter.IViewHolder
import xyz.fycz.myreader.base.adapter.ViewHolderImpl
import xyz.fycz.myreader.entity.SearchWord1
import xyz.fycz.myreader.entity.SearchWord2
/**
* @author fengyue
* @date 2021/12/5 20:28
*/
class SearchWord1Holder(var activity: AppCompatActivity) : ViewHolderImpl<SearchWord1>() {
private lateinit var tvChapterTitle: TextView
private lateinit var rvWordList: RecyclerView
private lateinit var adapter: BaseListAdapter<SearchWord2>
override fun getItemLayoutId(): Int {
return R.layout.item_search_word1
}
override fun initView() {
tvChapterTitle = findById(R.id.tv_chapter_title)
rvWordList = findById(R.id.rv_search_word2)
}
override fun onBind(holder: RecyclerView.ViewHolder?, data: SearchWord1?, pos: Int) {
tvChapterTitle.text = data?.chapterTitle
adapter = object : BaseListAdapter<SearchWord2>() {
override fun createViewHolder(viewType: Int): IViewHolder<SearchWord2> {
return SearchWord2Holder()
}
}
rvWordList.layoutManager = LinearLayoutManager(context)
rvWordList.adapter = adapter
adapter.refreshItems(data?.searchWord2List)
adapter.setOnItemClickListener { _, pos1 ->
val searchWord2 = adapter.getItem(pos1)
val intent = Intent()
intent.putExtra("chapterNum", searchWord2.chapterNum)
intent.putExtra("countInChapter", searchWord2.count)
intent.putExtra("keyword", searchWord2.keyword)
activity.setResult(Activity.RESULT_OK, intent)
activity.finish()
}
}
}

@ -0,0 +1,37 @@
package xyz.fycz.myreader.ui.adapter.holder
import android.graphics.Color
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import xyz.fycz.myreader.R
import xyz.fycz.myreader.base.adapter.ViewHolderImpl
import xyz.fycz.myreader.entity.SearchWord2
/**
* @author fengyue
* @date 2021/12/5 20:46
*/
class SearchWord2Holder : ViewHolderImpl<SearchWord2>() {
private lateinit var tvSearchWord: TextView
override fun getItemLayoutId(): Int {
return R.layout.item_search_word2
}
override fun initView() {
tvSearchWord = findById(R.id.tv_search_word)
}
override fun onBind(holder: RecyclerView.ViewHolder, data: SearchWord2, pos: Int) {
val spannableString = SpannableString(data.dataStr)
spannableString.setSpan(
ForegroundColorSpan(Color.RED),
data.dataIndex, data.dataIndex + data.keyword.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
tvSearchWord.text = spannableString
}
}

@ -16,6 +16,7 @@ import android.webkit.MimeTypeMap;
import android.widget.PopupMenu;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -299,6 +300,7 @@ public class DIYSourceFragment extends BaseFragment {
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NonNull List<BookSource> sources) {
mBookSources = sources;
@ -324,6 +326,11 @@ public class DIYSourceFragment extends BaseFragment {
dialog.show();
Single.create((SingleOnSubscribe<String>) emitter -> {
// String json = FileUtils.readInStream(DocumentUtil.getFileInputSteam(getContext(), data.getData()));
DocumentFile file = DocumentFile.fromSingleUri(getContext(), data.getData());
if (!file.getName().endsWith(".txt") && !file.getType().equals(".json")) {
emitter.onError(new Throwable("文件格式错误"));
return;
}
String json = new String(DocumentUtil.readBytes(getContext(), data.getData()), StandardCharsets.UTF_8);
emitter.onSuccess(json);
}).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<String>() {
@ -341,7 +348,7 @@ public class DIYSourceFragment extends BaseFragment {
@Override
public void onError(Throwable e) {
ToastUtils.showError("文件读取失败");
ToastUtils.showError("文件读取失败\n" + e.getLocalizedMessage());
dialog.dismiss();
}
});
@ -435,6 +442,7 @@ public class DIYSourceFragment extends BaseFragment {
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NonNull Boolean aBoolean) {
if (aBoolean) {
@ -466,6 +474,7 @@ public class DIYSourceFragment extends BaseFragment {
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onSuccess(@NonNull File share) {
ShareUtils.share(sourceActivity, share, "书源分享", "text/plain");

@ -1291,6 +1291,7 @@ public class ChapterContentHelp {
private boolean isUseTo(String useTo, String bookTag, String bookSource) {
String[] useTos = useTo.split(";");
return useTo.length() <= 1
|| TextUtils.isEmpty(useTo)
|| TextUtils.isEmpty(useTos[0])
|| TextUtils.isEmpty(useTos[1])
|| useTo.contains(bookTag)

@ -13,15 +13,18 @@ import android.widget.TextView;
public class KeyWordUtils {
public static void setKeyWord(TextView textView, String str, String keyWord){
setKeyWord(textView, str, keyWord, Color.RED);
}
public static void setKeyWord(TextView textView, String str, String keyWord, int colorId){
int start = str.indexOf(keyWord);
if (start == -1){
textView.setText(str);
}else {
SpannableString spannableString = new SpannableString(str);
spannableString.setSpan(new ForegroundColorSpan(Color.RED),
spannableString.setSpan(new ForegroundColorSpan(colorId),
start, start + keyWord.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);
}
}
}

@ -20,7 +20,7 @@ public class RefreshProgressBar extends View {
private int bgColor = 0x00000000;
private int secondColor = 0xFFC1C1C1;
private int fontColor = 0xFF363636;
private int speed = 1;
private int speed = 2;
private int secondFinalProgress = 0;
private Paint paint;
private Boolean isAutoLoading = false;

@ -279,7 +279,7 @@ public class EpubPageLoader extends PageLoader {
}
@Override
protected String getChapterReader(Chapter chapter) throws Exception {
public String getChapterReader(Chapter chapter) throws Exception {
Log.d("getChapterReader", chapter.getTitle());
/*byte[] content = getChapterContent(chapter).getBytes();
ByteArrayInputStream bais = new ByteArrayInputStream(content);

@ -370,9 +370,8 @@ public class LocalPageLoader extends PageLoader {
@Override
protected String getChapterReader(Chapter chapter) throws Exception {
public String getChapterReader(Chapter chapter) throws Exception {
if (chapter.getEnd() > 0) {
Log.d("getChapterReader", chapter.getTitle());
//从文件中获取数据
/*byte[] content = getContent(chapter);
ByteArrayInputStream bais = new ByteArrayInputStream(content);

@ -102,7 +102,7 @@ public class NetPageLoader extends PageLoader {
}
@Override
protected String getChapterReader(Chapter chapter) throws FileNotFoundException {
public String getChapterReader(Chapter chapter) throws FileNotFoundException {
/*File file = new File(APPCONST.BOOK_CACHE_PATH + mCollBook.getId()
+ File.separator + chapter.getTitle() + FileUtils.SUFFIX_FY);
if (!file.exists()) return null;

@ -168,7 +168,7 @@ public abstract class PageLoader {
private int readAloudParagraph = -1; //正在朗读章节
private Bitmap bgBitmap;
private ChapterContentHelp contentHelper = new ChapterContentHelp();
public ChapterContentHelp contentHelper = new ChapterContentHelp();
protected Disposable mChapterDis = null;
@ -863,7 +863,7 @@ public abstract class PageLoader {
* @param chapter
* @return
*/
protected abstract String getChapterReader(Chapter chapter) throws Exception;
public abstract String getChapterReader(Chapter chapter) throws Exception;
/**
* 章节数据是否存在
@ -2126,6 +2126,88 @@ public abstract class PageLoader {
return null;
}
public void skipToSearch(int chapterNum, int countInChapter, String keyword) {
skipToChapter(chapterNum);
int[] position = searchWordPositions(countInChapter, keyword);
skipToPage(position[0]);
mPageView.setFirstSelectTxtChar(mCurPage.txtLists.get(position[1]).
getCharsData().get(position[2]));
switch (position[3]) {
case 0:
mPageView.setLastSelectTxtChar(mCurPage.txtLists.get(position[1]).
getCharsData().get(position[2] + keyword.length() - 1));
break;
case 1:
mPageView.setLastSelectTxtChar(mCurPage.txtLists.get(position[1] + 1).
getCharsData().get(position[4]));
break;
case -1:
mPageView.setLastSelectTxtChar(mCurPage.txtLists.get(mCurPage.txtLists.size() - 1).
getCharsData().get(mCurPage.txtLists.get(mCurPage.txtLists.size() - 1).
getCharsData().size() - 1));
break;
}
mPageView.setSelectMode(PageView.SelectMode.SelectMoveForward);
mPageView.invalidate();
}
private int[] searchWordPositions(int countInChapter, String keyword) {
List<TxtPage> pages = mCurChapter.getTxtPageList();
// calculate search result's pageIndex
StringBuilder sb = new StringBuilder();
for (TxtPage page : pages) {
sb.append(page.getContent());
}
String content = sb.toString();
int count = 0;
int index = content.indexOf(keyword);
while (count != countInChapter) {
index = content.indexOf(keyword, index + 1);
count++;
}
int contentPosition = index;
int pageIndex = 0;
int length = mCurChapter.getPageLength(pageIndex);
while (length < contentPosition) {
pageIndex++;
if (pageIndex > pages.size()) {
pageIndex = pages.size();
break;
}
length = mCurChapter.getPageLength(pageIndex);
}
// calculate search word's lineIndex
TxtPage currentPage = pages.get(pageIndex);
int lineIndex = 0;
length = length - currentPage.getContent().length() + currentPage.lines.get(lineIndex).length();
while (length < contentPosition) {
lineIndex += 1;
if (lineIndex > currentPage.lines.size()) {
lineIndex = currentPage.lines.size();
break;
}
length += currentPage.lines.get(lineIndex).length();
}
// charIndex
String currentLine = currentPage.lines.get(lineIndex);
currentLine = StringUtils.trim(currentLine);
length -= currentLine.length();
int charIndex = contentPosition - length;
int addLine = 0;
int charIndex2 = 0;
// change line
if ((charIndex + keyword.length()) > currentLine.length()) {
addLine = 1;
charIndex2 = charIndex + keyword.length() - currentLine.length() - 1;
}
// changePage
if ((lineIndex + addLine + 1) > currentPage.lines.size()) {
addLine = -1;
charIndex2 = charIndex + keyword.length() - currentLine.length() - 1;
}
return new int[]{pageIndex, lineIndex, charIndex, addLine, charIndex2};
}
public boolean isPrev() {
return isPrev;

@ -8,7 +8,7 @@ import java.util.List;
* 书籍chapter
*/
public class TxtChapter{
public class TxtChapter {
private int position;
private List<TxtPage> txtPageList = new ArrayList<>();

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M602.3,603.46c9.09,-7.68 33.73,4.93 55.04,28.1l270.14,293.12c21.31,23.17 23.04,54.91 3.97,70.98 -19.2,16 -50.11,8.77 -69.25,-16.19L620.48,662.4c-19.07,-24.96 -27.2,-51.46 -18.18,-59.01z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M560,633.47a288,288 0,1 0,-288 -498.82,288 288,0 0,0 288,498.82zM592,688.9a352,352 0,1 1,-352 -609.66,352 352,0 0,1 352,609.66z"
android:fillColor="#FFFFFF"/>
</vector>

@ -27,9 +27,9 @@
android:layout_height="35dp"
android:layout_weight="2"
android:background="@drawable/search_et_backcolor"
android:hint="输入关键词"
android:hint="@string/input_keyword"
android:imeOptions="actionSearch"
android:paddingLeft="10dp"
android:paddingStart="10dp"
android:textColor="@color/textPrimary" />
@ -40,7 +40,7 @@
android:layout_weight="8"
android:background="@drawable/search_btn_backcolor"
android:gravity="center"
android:text="搜索"
android:text="@string/common_search"
android:textColor="@color/textPrimaryInverted"
android:textSize="18sp" />
</LinearLayout>
@ -235,7 +235,8 @@
android:visibility="gone"
app:elevation="5dp"
app:fabSize="mini"
app:layout_anchorGravity="right|bottom" />
app:layout_anchorGravity="right|bottom"
android:contentDescription="@string/stop" />
</RelativeLayout>
</LinearLayout>

@ -0,0 +1,77 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center"
android:orientation="horizontal"
android:paddingLeft="15dp"
android:paddingTop="10dp"
android:paddingRight="15dp"
android:paddingBottom="10dp">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/et_search_key"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_weight="2"
android:background="@drawable/search_et_backcolor"
android:hint="@string/search_word_tip"
android:imeOptions="actionSearch"
android:paddingStart="10dp"
android:textColor="@color/textPrimary" />
<TextView
android:id="@+id/tv_search_conform"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_weight="8"
android:background="@drawable/search_btn_backcolor"
android:gravity="center"
android:text="@string/common_search"
android:textColor="@color/textPrimaryInverted"
android:textSize="18sp" />
</LinearLayout>
<xyz.fycz.myreader.widget.RefreshProgressBar
android:id="@+id/rpb"
android:layout_width="match_parent"
android:layout_height="3dp"
android:visibility="visible" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<xyz.fycz.myreader.widget.scroller.FastScrollRecyclerView
android:id="@+id/rv_search_word1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="10dp"
tools:ignore="SpeakableTextPresentCheck" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabSearchStop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="30dp"
android:layout_marginBottom="30dp"
android:src="@drawable/ic_stop_black_24dp"
android:visibility="gone"
app:elevation="5dp"
app:fabSize="mini"
app:layout_anchorGravity="right|bottom"
android:contentDescription="@string/stop" />
</RelativeLayout>
</LinearLayout>

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:id="@+id/text_view"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/textPrimary"

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_chapter_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="5dp"
android:text="@string/app_name"
android:textColor="@color/textSecondary"
android:textSize="@dimen/text_normal_size" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_search_word2"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_search_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/textPrimary"
android:paddingVertical="10dp"
android:textSize="@dimen/text_normal_size"
android:text="@string/app_name"/>
<View
android:layout_width="fill_parent"
android:layout_height="0.5dp"
android:background="@color/sys_window_back" />
</LinearLayout>

@ -1,35 +1,36 @@
<?xml version ="1.0" encoding ="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<item
android:id="@+id/action_change_source"
android:icon="@drawable/ic_change"
android:title="@string/menu_change_source"
app:showAsAction="ifRoom"/>
android:id="@+id/action_change_source"
android:icon="@drawable/ic_change"
android:title="@string/menu_change_source"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_reload"
android:icon="@drawable/ic_refresh"
android:title="@string/menu_reload"
app:showAsAction="ifRoom"/>
android:id="@+id/action_reload"
android:icon="@drawable/ic_refresh"
android:title="@string/menu_reload"
app:showAsAction="ifRoom" />
<group android:id="@+id/action_load_finish">
<item
android:id="@+id/action_download"
android:icon="@drawable/ic_download"
android:title="@string/download_book"
app:showAsAction="always"/>
app:showAsAction="always" />
<item
android:id="@+id/action_add_bookmark"
android:icon="@drawable/ic_bookmark"
android:title="@string/menu_add_bookmark"
app:showAsAction="never"/>
android:id="@+id/action_add_bookmark"
android:icon="@drawable/ic_bookmark"
android:title="@string/menu_add_bookmark"
app:showAsAction="never" />
<item
android:id="@+id/action_replace_content"
android:icon="@drawable/ic_replace"
android:title="@string/content_replace"
app:showAsAction="never"/>
app:showAsAction="never" />
<item
android:id="@+id/action_re_seg"
@ -40,20 +41,28 @@
app:showAsAction="never" />
<item
android:id="@+id/action_copy_content"
android:icon="@drawable/ic_copy"
android:title="@string/menu_copy_content"
app:showAsAction="never"/>
android:id="@+id/action_copy_content"
android:icon="@drawable/ic_copy"
android:title="@string/menu_copy_content"
app:showAsAction="never" />
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search_word"
android:title="@string/search_word"
app:showAsAction="never" />
<item
android:id="@+id/action_open_link"
android:icon="@drawable/ic_link"
android:title="@string/menu_open_link"
app:showAsAction="never"/>
android:id="@+id/action_open_link"
android:icon="@drawable/ic_link"
android:title="@string/menu_open_link"
app:showAsAction="never" />
</group>
<item
android:id="@+id/action_edit_source"
android:icon="@drawable/ic_source"
android:title="@string/book_source"
app:showAsAction="ifRoom"/>
app:showAsAction="ifRoom" />
</menu>

@ -511,6 +511,9 @@
<string name="book_group">书籍分组</string>
<string name="book_group_tip">在书架内显示书籍分组</string>
<string name="book_word_count">计算字数(实验)</string>
<string name="input_keyword">输入关键词</string>
<string name="search_word">内容搜索</string>
<string name="search_word_tip">搜索书籍已缓存的内容</string>
<string-array name="reset_screen_time">

@ -1,3 +1,3 @@
#Fri Jun 18 21:45:31 CST 2021
VERSION_CODE=223
NEED_CREATE_RELEASE=true
VERSION_CODE=224
NEED_CREATE_RELEASE=false

Loading…
Cancel
Save