Merge pull request #17 from gedoor/master

get update
pull/379/head
口口吕 5 years ago committed by GitHub
commit 98706adae6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/src/main/assets/updateLog.md
  2. 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  3. 14
      app/src/main/java/io/legado/app/help/BookHelp.kt
  4. 34
      app/src/main/java/io/legado/app/help/FirstTopListUpCallback.kt
  5. 152
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  6. 8
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  7. 9
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  8. 2
      app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt
  9. 6
      app/src/main/java/io/legado/app/receiver/TimeBatteryReceiver.kt
  10. 12
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  11. 4
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt
  12. 4
      app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt
  13. 4
      app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt
  14. 9
      app/src/main/java/io/legado/app/ui/book/read/Help.kt
  15. 44
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  16. 7
      app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
  17. 11
      app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt
  18. 6
      app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt
  19. 33
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  20. 7
      app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt
  21. 91
      app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt
  22. 219
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  23. 35
      app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt
  24. 9
      app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt
  25. 8
      app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt
  26. 56
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  27. 48
      app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt
  28. 36
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  29. 102
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  30. 77
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt
  31. 4
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt
  32. 1
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt
  33. 37
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt
  34. 6
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPoint.kt
  35. 3
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  36. 8
      app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt
  37. 4
      app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt
  38. 4
      app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt
  39. 4
      app/src/main/java/io/legado/app/ui/chapterlist/ChapterListFragment.kt
  40. 4
      app/src/main/java/io/legado/app/ui/explore/ExploreShowActivity.kt
  41. 8
      app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt
  42. 8
      app/src/main/java/io/legado/app/ui/replacerule/GroupManageDialog.kt
  43. 3
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt
  44. 4
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt
  45. 4
      app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt
  46. 8
      app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt
  47. 3
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  48. 2
      app/src/main/java/io/legado/app/ui/widget/LabelsBar.kt
  49. 8
      app/src/main/java/io/legado/app/ui/widget/TitleBar.kt
  50. 169
      app/src/main/java/io/legado/app/ui/widget/recycler/DividerNoLast.kt
  51. 9
      app/src/main/java/io/legado/app/ui/widget/recycler/VerticalDivider.kt
  52. 42
      app/src/main/java/io/legado/app/utils/ContextExtensions.kt
  53. 2
      app/src/main/res/drawable/recyclerview_divider_horizontal.xml
  54. 4
      app/src/main/res/layout/dialog_read_aloud.xml
  55. 1
      app/src/main/res/layout/dialog_read_padding.xml
  56. 1
      app/src/main/res/values/strings.xml

@ -2,6 +2,10 @@
* 旧版数据导入教程: * 旧版数据导入教程:
* 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/02/24**
* 滚动暂时可以滚了,先这样吧,头大
* 紧急修复朗读报错的bug
**2020/02/23** **2020/02/23**
* 修复BUG * 修复BUG
* 本地目录正则自定义完成 * 本地目录正则自定义完成

@ -37,4 +37,5 @@ object PreferKey {
const val lastBackup = "lastBackup" const val lastBackup = "lastBackup"
const val bodyIndent = "textIndent" const val bodyIndent = "textIndent"
const val shareLayout = "shareLayout" const val shareLayout = "shareLayout"
const val readStyleSelect = "readStyleSelect"
} }

@ -237,8 +237,14 @@ object BookHelp {
private var bookName: String? = null private var bookName: String? = null
private var bookOrigin: String? = null private var bookOrigin: String? = null
private var replaceRules: List<ReplaceRule> = arrayListOf() private var replaceRules: List<ReplaceRule> = arrayListOf()
val bodyIndent var bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2)
get() = " ".repeat(App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2)) set(value) {
field = value
App.INSTANCE.putPrefInt(PreferKey.bodyIndent, value)
bodyIndent = " ".repeat(value)
}
var bodyIndent = " ".repeat(bodyIndentCount)
fun disposeContent( fun disposeContent(
title: String, title: String,
@ -268,7 +274,9 @@ object BookHelp {
} }
} }
} }
c = "$title\n$c" if (!c.substringBefore("\n").contains(title)) {
c = "$title\n$c"
}
when (AppConfig.chineseConverterType) { when (AppConfig.chineseConverterType) {
1 -> c = ZhConvertBootstrap.newInstance().toSimple(c) 1 -> c = ZhConvertBootstrap.newInstance().toSimple(c)
2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c) 2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c)

@ -1,34 +0,0 @@
package io.legado.app.help
import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.base.adapter.ItemViewHolder
class FirstTopListUpCallback : ListUpdateCallback {
var firstInsert = -1
lateinit var adapter: RecyclerView.Adapter<ItemViewHolder>
override fun onChanged(position: Int, count: Int, payload: Any?) {
adapter.notifyItemRangeChanged(position, count, payload)
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
if (toPosition == 0) {
firstInsert = 0
}
adapter.notifyItemMoved(fromPosition, toPosition)
}
override fun onInserted(position: Int, count: Int) {
if (firstInsert == -1 || firstInsert > position) {
firstInsert = position
}
adapter.notifyItemRangeInserted(position, count)
}
override fun onRemoved(position: Int, count: Int) {
adapter.notifyItemRangeRemoved(position, count)
}
}

@ -6,6 +6,7 @@ import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.ui.book.read.page.ChapterProvider import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.utils.* import io.legado.app.utils.*
@ -23,12 +24,25 @@ object ReadBookConfig {
val json = String(App.INSTANCE.assets.open(readConfigFileName).readBytes()) val json = String(App.INSTANCE.assets.open(readConfigFileName).readBytes())
GSON.fromJsonArray<Config>(json)!! GSON.fromJsonArray<Config>(json)!!
} }
val durConfig val durConfig get() = getConfig(styleSelect)
get() = getConfig(styleSelect) private val shareConfig get() = getConfig(5)
var styleSelect = App.INSTANCE.getPrefInt(PreferKey.readStyleSelect)
var styleSelect set(value) {
get() = App.INSTANCE.getPrefInt("readStyleSelect") field = value
set(value) = App.INSTANCE.putPrefInt("readStyleSelect", value) App.INSTANCE.putPrefInt(PreferKey.readStyleSelect, value)
}
var shareLayout = App.INSTANCE.getPrefBoolean(PreferKey.shareLayout)
set(value) {
field = value
App.INSTANCE.putPrefBoolean(PreferKey.shareLayout)
}
var pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim)
set(value) {
field = value
isScroll = value == 3
App.INSTANCE.putPrefInt(PreferKey.pageAnim, value)
}
var isScroll = pageAnim == 3
var bg: Drawable? = null var bg: Drawable? = null
init { init {
@ -40,6 +54,9 @@ object ReadBookConfig {
if (configList.size < 5) { if (configList.size < 5) {
resetAll() resetAll()
} }
if (configList.size < 6) {
configList.add(Config())
}
return configList[index] return configList[index]
} }
@ -94,17 +111,118 @@ object ReadBookConfig {
} }
} }
data class Config( //配置写入读取
var bgStr: String = "#EEEEEE",//白天背景 var hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar)
var bgStrNight: String = "#000000",//夜间背景 var hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar)
var bgType: Int = 0,//白天背景类型 var textBold: Boolean
var bgTypeNight: Int = 0,//夜间背景类型 get() = if (shareLayout) shareConfig.textBold else durConfig.textBold
var darkStatusIcon: Boolean = true,//白天是否暗色状态栏 set(value) = if (shareLayout) shareConfig.textBold = value else durConfig.textBold = value
var darkStatusIconNight: Boolean = false,//晚上是否暗色状态栏
var textColor: String = "#3E3D3B",//白天文字颜色 var textSize: Int
var textColorNight: String = "#adadad",//夜间文字颜色 get() = if (shareLayout) shareConfig.textSize else durConfig.textSize
set(value) = if (shareLayout) shareConfig.textSize = value else durConfig.textSize = value
var letterSpacing: Float
get() = if (shareLayout) shareConfig.letterSpacing else durConfig.letterSpacing
set(value) =
if (shareLayout) shareConfig.letterSpacing = value else durConfig.letterSpacing = value
var lineSpacingExtra: Int
get() = if (shareLayout) shareConfig.lineSpacingExtra else durConfig.lineSpacingExtra
set(value) =
if (shareLayout) shareConfig.lineSpacingExtra = value
else durConfig.lineSpacingExtra = value
var paragraphSpacing: Int
get() = if (shareLayout) shareConfig.paragraphSpacing else durConfig.paragraphSpacing
set(value) =
if (shareLayout) shareConfig.paragraphSpacing = value
else durConfig.paragraphSpacing = value
var titleCenter: Boolean
get() = if (shareLayout) shareConfig.titleCenter else durConfig.titleCenter
set(value) =
if (shareLayout) shareConfig.titleCenter = value else durConfig.titleCenter = value
var paddingBottom: Int
get() = if (shareLayout) shareConfig.paddingBottom else durConfig.paddingBottom
set(value) =
if (shareLayout) shareConfig.paddingBottom = value else durConfig.paddingBottom = value
var paddingLeft: Int
get() = if (shareLayout) shareConfig.paddingLeft else durConfig.paddingLeft
set(value) =
if (shareLayout) shareConfig.paddingLeft = value else durConfig.paddingLeft = value
var paddingRight: Int
get() = if (shareLayout) shareConfig.paddingRight else durConfig.paddingRight
set(value) =
if (shareLayout) shareConfig.paddingRight = value else durConfig.paddingRight = value
var paddingTop: Int
get() = if (shareLayout) shareConfig.paddingTop else durConfig.paddingTop
set(value) =
if (shareLayout) shareConfig.paddingTop = value else durConfig.paddingTop = value
var headerPaddingBottom: Int
get() = if (shareLayout) shareConfig.headerPaddingBottom else durConfig.headerPaddingBottom
set(value) =
if (shareLayout) shareConfig.headerPaddingBottom = value
else durConfig.headerPaddingBottom = value
var headerPaddingLeft: Int
get() = if (shareLayout) shareConfig.headerPaddingLeft else durConfig.headerPaddingLeft
set(value) =
if (shareLayout) shareConfig.headerPaddingLeft = value
else durConfig.headerPaddingLeft = value
var headerPaddingRight: Int
get() = if (shareLayout) shareConfig.headerPaddingRight else durConfig.headerPaddingRight
set(value) =
if (shareLayout) shareConfig.headerPaddingRight = value
else durConfig.headerPaddingRight = value
var headerPaddingTop: Int
get() = if (shareLayout) shareConfig.headerPaddingTop else durConfig.headerPaddingTop
set(value) =
if (shareLayout) shareConfig.headerPaddingTop = value
else durConfig.headerPaddingTop = value
var footerPaddingBottom: Int
get() = if (shareLayout) shareConfig.footerPaddingBottom else durConfig.footerPaddingBottom
set(value) =
if (shareLayout) shareConfig.footerPaddingBottom = value
else durConfig.footerPaddingBottom = value
var footerPaddingLeft: Int
get() = if (shareLayout) shareConfig.footerPaddingLeft else durConfig.footerPaddingLeft
set(value) =
if (shareLayout) shareConfig.footerPaddingLeft = value
else durConfig.footerPaddingLeft = value
var footerPaddingRight: Int
get() = if (shareLayout) shareConfig.footerPaddingRight else durConfig.footerPaddingRight
set(value) =
if (shareLayout) shareConfig.footerPaddingRight = value
else durConfig.footerPaddingRight = value
var footerPaddingTop: Int
get() = if (shareLayout) shareConfig.footerPaddingTop else durConfig.footerPaddingTop
set(value) =
if (shareLayout) shareConfig.footerPaddingTop = value
else durConfig.footerPaddingTop = value
class Config(
private var bgStr: String = "#EEEEEE",//白天背景
private var bgStrNight: String = "#000000",//夜间背景
private var bgType: Int = 0,//白天背景类型
private var bgTypeNight: Int = 0,//夜间背景类型
private var darkStatusIcon: Boolean = true,//白天是否暗色状态栏
private var darkStatusIconNight: Boolean = false,//晚上是否暗色状态栏
private var textColor: String = "#3E3D3B",//白天文字颜色
private var textColorNight: String = "#ADADAD",//夜间文字颜色
var textBold: Boolean = false,//是否粗体字 var textBold: Boolean = false,//是否粗体字
var textSize: Int = 15,//文字大小 var textSize: Int = 20,//文字大小
var letterSpacing: Float = 1f,//字间距 var letterSpacing: Float = 1f,//字间距
var lineSpacingExtra: Int = 12,//行间距 var lineSpacingExtra: Int = 12,//行间距
var paragraphSpacing: Int = 12,//段距 var paragraphSpacing: Int = 12,//段距
@ -138,7 +256,7 @@ object ReadBookConfig {
} else { } else {
textColor = "#${color.hexString}" textColor = "#${color.hexString}"
} }
ChapterProvider.upStyle(this) ChapterProvider.upStyle()
} }
fun setStatusIconDark(isDark: Boolean) { fun setStatusIconDark(isDark: Boolean) {

@ -100,12 +100,16 @@ object Backup {
for (fileName in backupFileNames) { for (fileName in backupFileNames) {
val file = File(backupPath + File.separator + fileName) val file = File(backupPath + File.separator + fileName)
if (file.exists()) { if (file.exists()) {
val doc = treeDoc.findFile(fileName) ?: treeDoc.createFile("", fileName) var doc = treeDoc.findFile(fileName)
if (null != doc && doc.exists()) {
doc.delete()
}
doc = treeDoc.createFile("", fileName)
doc?.let { doc?.let {
DocumentUtils.writeText( DocumentUtils.writeText(
context, context,
file.readText(), file.readText(),
doc.uri it.uri
) )
} }
} }

@ -104,7 +104,14 @@ object Restore {
else -> Unit else -> Unit
} }
edit.putInt(PreferKey.versionCode, App.INSTANCE.versionCode) edit.putInt(PreferKey.versionCode, App.INSTANCE.versionCode)
edit.commit() edit.apply()
}
ReadBookConfig.apply {
styleSelect = App.INSTANCE.getPrefInt(PreferKey.readStyleSelect)
shareLayout = App.INSTANCE.getPrefBoolean(PreferKey.shareLayout)
pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim)
hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar)
hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar)
} }
} }
LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon)) LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon))

@ -45,7 +45,7 @@ class MediaButtonReceiver : BroadcastReceiver() {
} }
} }
} }
return false return true
} }
private fun readAloud(context: Context) { private fun readAloud(context: Context) {

@ -9,12 +9,12 @@ import io.legado.app.constant.EventBus
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
class TimeElectricityReceiver : BroadcastReceiver() { class TimeBatteryReceiver : BroadcastReceiver() {
companion object { companion object {
fun register(context: Context): TimeElectricityReceiver { fun register(context: Context): TimeBatteryReceiver {
val receiver = TimeElectricityReceiver() val receiver = TimeBatteryReceiver()
val filter = IntentFilter() val filter = IntentFilter()
filter.addAction(Intent.ACTION_TIME_TICK) filter.addAction(Intent.ACTION_TIME_TICK)
filter.addAction(Intent.ACTION_BATTERY_CHANGED) filter.addAction(Intent.ACTION_BATTERY_CHANGED)

@ -26,10 +26,10 @@ object ReadBook {
var book: Book? = null var book: Book? = null
var inBookshelf = false var inBookshelf = false
var chapterSize = 0 var chapterSize = 0
var callBack: CallBack? = null
var durChapterIndex = 0 var durChapterIndex = 0
var durPageIndex = 0 var durPageIndex = 0
var isLocalBook = true var isLocalBook = true
var callBack: CallBack? = null
var prevTextChapter: TextChapter? = null var prevTextChapter: TextChapter? = null
var curTextChapter: TextChapter? = null var curTextChapter: TextChapter? = null
var nextTextChapter: TextChapter? = null var nextTextChapter: TextChapter? = null
@ -127,7 +127,13 @@ object ReadBook {
saveRead() saveRead()
} }
fun curPageChanged() { fun setPageIndex(pageIndex: Int) {
durPageIndex = pageIndex
saveRead()
curPageChanged()
}
private fun curPageChanged() {
callBack?.upPageProgress() callBack?.upPageProgress()
if (BaseReadAloudService.isRun) { if (BaseReadAloudService.isRun) {
readAloud(!BaseReadAloudService.pause) readAloud(!BaseReadAloudService.pause)
@ -306,7 +312,7 @@ object ReadBook {
} }
interface CallBack { interface CallBack {
fun upContent(position: Int = 0) fun upContent(relativePosition: Int = 0)
fun upView() fun upView()
fun upPageProgress() fun upPageProgress()
fun contentLoadFinish() fun contentLoadFinish()

@ -19,8 +19,8 @@ import io.legado.app.lib.theme.ATH
import io.legado.app.ui.book.group.GroupManageDialog import io.legado.app.ui.book.group.GroupManageDialog
import io.legado.app.ui.book.group.GroupSelectDialog import io.legado.app.ui.book.group.GroupSelectDialog
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getVerticalDivider
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.activity_arrange_book.* import kotlinx.android.synthetic.main.activity_arrange_book.*
@ -60,7 +60,7 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
private fun initView() { private fun initView() {
ATH.applyEdgeEffectColor(recycler_view) ATH.applyEdgeEffectColor(recycler_view)
recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(this))
adapter = ArrangeBookAdapter(this, this) adapter = ArrangeBookAdapter(this, this)
recycler_view.adapter = adapter recycler_view.adapter = adapter
select_action_bar.setMainActionText(R.string.move_to_group) select_action_bar.setMainActionText(R.string.move_to_group)

@ -28,8 +28,8 @@ import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.dialogs.yesButton
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getVerticalDivider
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.requestInputMethod import io.legado.app.utils.requestInputMethod
import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_edit_text.view.*
@ -77,7 +77,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
.isChecked = AppConst.bookGroupAudioShow .isChecked = AppConst.bookGroupAudioShow
adapter = GroupAdapter(requireContext()) adapter = GroupAdapter(requireContext())
recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter recycler_view.adapter = adapter
App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, Observer { App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, Observer {
val diffResult = val diffResult =

@ -28,8 +28,8 @@ import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.dialogs.yesButton
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getVerticalDivider
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.requestInputMethod import io.legado.app.utils.requestInputMethod
import kotlinx.android.synthetic.main.dialog_book_group_picker.* import kotlinx.android.synthetic.main.dialog_book_group_picker.*
@ -98,7 +98,7 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
tool_bar.menu.findItem(R.id.menu_group_audio).isVisible = false tool_bar.menu.findItem(R.id.menu_group_audio).isVisible = false
adapter = GroupAdapter(requireContext()) adapter = GroupAdapter(requireContext())
recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter recycler_view.adapter = adapter
val itemTouchCallback = ItemTouchCallback() val itemTouchCallback = ItemTouchCallback()
itemTouchCallback.onItemTouchCallbackListener = adapter itemTouchCallback.onItemTouchCallbackListener = adapter

@ -10,7 +10,6 @@ import android.view.View.NO_ID
import android.widget.EditText import android.widget.EditText
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
@ -23,7 +22,6 @@ import io.legado.app.lib.theme.ThemeStore
import io.legado.app.service.help.Download import io.legado.app.service.help.Download
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.requestInputMethod import io.legado.app.utils.requestInputMethod
import kotlinx.android.synthetic.main.dialog_download_choice.view.* import kotlinx.android.synthetic.main.dialog_download_choice.view.*
import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_edit_text.view.*
@ -42,15 +40,14 @@ object Help {
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_IMMERSIVE or View.SYSTEM_UI_FLAG_IMMERSIVE
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
val hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) if (ReadBookConfig.hideNavigationBar) {
if (hideNavigationBar) {
flag = flag or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION flag = flag or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
} }
if (toolBarHide) { if (toolBarHide) {
if (App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar)) { if (ReadBookConfig.hideStatusBar) {
flag = flag or View.SYSTEM_UI_FLAG_FULLSCREEN flag = flag or View.SYSTEM_UI_FLAG_FULLSCREEN
} }
if (hideNavigationBar) { if (ReadBookConfig.hideNavigationBar) {
flag = flag or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION flag = flag or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
} }
} }

@ -7,6 +7,7 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.view.* import android.view.*
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.appcompat.view.menu.MenuItemImpl import androidx.appcompat.view.menu.MenuItemImpl
import androidx.core.view.get import androidx.core.view.get
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -25,7 +26,7 @@ import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.dialogs.okButton
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.receiver.TimeElectricityReceiver import io.legado.app.receiver.TimeBatteryReceiver
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
@ -80,7 +81,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
private val keepScreenRunnable: Runnable = Runnable { Help.keepScreenOn(window, false) } private val keepScreenRunnable: Runnable = Runnable { Help.keepScreenOn(window, false) }
private var screenTimeOut: Long = 0 private var screenTimeOut: Long = 0
private var timeElectricityReceiver: TimeElectricityReceiver? = null private var timeBatteryReceiver: TimeBatteryReceiver? = null
override val pageFactory: TextPageFactory get() = page_view.pageFactory override val pageFactory: TextPageFactory get() = page_view.pageFactory
override val headerHeight: Int get() = page_view.curPage.headerHeight override val headerHeight: Int get() = page_view.curPage.headerHeight
@ -105,15 +106,15 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
upSystemUiVisibility() upSystemUiVisibility()
timeElectricityReceiver = TimeElectricityReceiver.register(this) timeBatteryReceiver = TimeBatteryReceiver.register(this)
page_view.upTime() page_view.upTime()
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
timeElectricityReceiver?.let { timeBatteryReceiver?.let {
unregisterReceiver(it) unregisterReceiver(it)
timeElectricityReceiver = null timeBatteryReceiver = null
} }
upSystemUiVisibility() upSystemUiVisibility()
} }
@ -379,20 +380,17 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
textActionMenu ?: let { textActionMenu ?: let {
textActionMenu = TextActionMenu(this, this) textActionMenu = TextActionMenu(this, this)
} }
val x = cursor_left.x.toInt() + cursor_left.width
val y = if (cursor_left.y - statusBarHeight > ReadBookConfig.textSize.dp * 1.5 + 20.dp) {
(page_view.height - cursor_left.y + ReadBookConfig.textSize.dp * 1.5).toInt()
} else {
(page_view.height - cursor_left.y - cursor_left.height - 40.dp).toInt()
}
textActionMenu?.let { popup -> textActionMenu?.let { popup ->
if (!popup.isShowing) { if (!popup.isShowing) {
popup.showAtLocation( popup.showAtLocation(cursor_left, Gravity.BOTTOM or Gravity.START, x, y)
cursor_left,
Gravity.BOTTOM or Gravity.START,
cursor_left.x.toInt() + cursor_left.width,
page_view.height - cursor_left.y.toInt() + ReadBookConfig.durConfig.textSize.dp + popup.height
)
} else { } else {
popup.update( popup.update(x, y, WRAP_CONTENT, WRAP_CONTENT)
cursor_left.x.toInt() + cursor_left.width,
page_view.height - cursor_left.y.toInt() + ReadBookConfig.durConfig.textSize.dp + popup.height,
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
)
} }
} }
} }
@ -458,9 +456,9 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
/** /**
* 更新内容 * 更新内容
*/ */
override fun upContent(position: Int) { override fun upContent(relativePosition: Int) {
launch { launch {
page_view.upContent(position) page_view.upContent(relativePosition)
} }
} }
@ -507,12 +505,6 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
viewModel.changeTo(book) viewModel.changeTo(book)
} }
override fun setPageIndex(pageIndex: Int) {
ReadBook.durPageIndex = pageIndex
ReadBook.saveRead()
ReadBook.curPageChanged()
}
override fun clickCenter() { override fun clickCenter() {
if (BaseReadAloudService.isRun) { if (BaseReadAloudService.isRun) {
showReadAloudDialog() showReadAloudDialog()
@ -665,7 +657,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
upSystemUiVisibility() upSystemUiVisibility()
page_view.upBg() page_view.upBg()
page_view.upStyle() page_view.upStyle()
ChapterProvider.upStyle(ReadBookConfig.durConfig) ChapterProvider.upStyle()
if (it) { if (it) {
ReadBook.loadContent() ReadBook.loadContent()
} else { } else {
@ -721,7 +713,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
Help.keepScreenOn(window, true) Help.keepScreenOn(window, true)
return return
} }
val t = screenTimeOut - getScreenOffTime() val t = screenTimeOut - sysScreenOffTime
if (t > 0) { if (t > 0) {
mHandler.removeCallbacks(keepScreenRunnable) mHandler.removeCallbacks(keepScreenRunnable)
Help.keepScreenOn(window, true) Help.keepScreenOn(window, true)

@ -10,8 +10,8 @@ import android.widget.SeekBar
import androidx.core.view.isVisible import androidx.core.view.isVisible
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.buttonDisabledColor import io.legado.app.lib.theme.buttonDisabledColor
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
@ -199,9 +199,8 @@ class ReadMenu : FrameLayout {
vw_menu_bg.onClick { runMenuOut() } vw_menu_bg.onClick { runMenuOut() }
vwNavigationBar.layoutParams = vwNavigationBar.layoutParams.apply { vwNavigationBar.layoutParams = vwNavigationBar.layoutParams.apply {
height = height =
if (context.getPrefBoolean(PreferKey.hideNavigationBar) if (ReadBookConfig.hideNavigationBar && Help.isNavigationBarExist(activity))
&& Help.isNavigationBarExist(activity) context.navigationBarHeight
) context.getNavigationBarHeight()
else 0 else 0
} }
} }

@ -14,6 +14,7 @@ import androidx.preference.PreferenceFragmentCompat
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.ui.book.read.Help import io.legado.app.ui.book.read.Help
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
@ -92,8 +93,14 @@ class MoreConfigDialog : DialogFragment() {
key: String? key: String?
) { ) {
when (key) { when (key) {
PreferKey.hideStatusBar -> postEvent(EventBus.UP_CONFIG, true) PreferKey.hideStatusBar -> {
PreferKey.hideNavigationBar -> postEvent(EventBus.UP_CONFIG, true) ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar)
postEvent(EventBus.UP_CONFIG, true)
}
PreferKey.hideNavigationBar -> {
ReadBookConfig.hideNavigationBar = getPrefBoolean(PreferKey.hideNavigationBar)
postEvent(EventBus.UP_CONFIG, true)
}
PreferKey.keepLight -> postEvent(key, true) PreferKey.keepLight -> postEvent(key, true)
PreferKey.textSelectAble -> postEvent(key, getPrefBoolean(key)) PreferKey.textSelectAble -> postEvent(key, getPrefBoolean(key))
} }

@ -11,6 +11,7 @@ import io.legado.app.constant.EventBus
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.Help import io.legado.app.ui.book.read.Help
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.dialog_read_padding.* import kotlinx.android.synthetic.main.dialog_read_padding.*
class PaddingConfigDialog : DialogFragment() { class PaddingConfigDialog : DialogFragment() {
@ -49,13 +50,14 @@ class PaddingConfigDialog : DialogFragment() {
ReadBookConfig.save() ReadBookConfig.save()
} }
private fun initData() = with(ReadBookConfig.durConfig) { private fun initData() = with(ReadBookConfig) {
//正文 //正文
dsb_padding_top.progress = paddingTop dsb_padding_top.progress = paddingTop
dsb_padding_bottom.progress = paddingBottom dsb_padding_bottom.progress = paddingBottom
dsb_padding_left.progress = paddingLeft dsb_padding_left.progress = paddingLeft
dsb_padding_right.progress = paddingRight dsb_padding_right.progress = paddingRight
//页眉 //页眉
tv_header_padding.visible(hideStatusBar)
dsb_header_padding_top.progress = headerPaddingTop dsb_header_padding_top.progress = headerPaddingTop
dsb_header_padding_bottom.progress = headerPaddingBottom dsb_header_padding_bottom.progress = headerPaddingBottom
dsb_header_padding_left.progress = headerPaddingLeft dsb_header_padding_left.progress = headerPaddingLeft
@ -67,7 +69,7 @@ class PaddingConfigDialog : DialogFragment() {
dsb_footer_padding_right.progress = footerPaddingRight dsb_footer_padding_right.progress = footerPaddingRight
} }
private fun initView() = with(ReadBookConfig.durConfig) { private fun initView() = with(ReadBookConfig) {
//正文 //正文
dsb_padding_top.onChanged = { dsb_padding_top.onChanged = {
paddingTop = it paddingTop = it

@ -11,6 +11,7 @@ import androidx.fragment.app.DialogFragment
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.BookHelp
import io.legado.app.help.ImageLoader import io.legado.app.help.ImageLoader
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
@ -19,7 +20,9 @@ import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.read.Help import io.legado.app.ui.book.read.Help
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.widget.font.FontSelectDialog import io.legado.app.ui.widget.font.FontSelectDialog
import io.legado.app.utils.* import io.legado.app.utils.getPrefString
import io.legado.app.utils.postEvent
import io.legado.app.utils.putPrefString
import kotlinx.android.synthetic.main.activity_book_read.* import kotlinx.android.synthetic.main.activity_book_read.*
import kotlinx.android.synthetic.main.dialog_read_book_style.* import kotlinx.android.synthetic.main.dialog_read_book_style.*
import org.jetbrains.anko.sdk27.listeners.onCheckedChange import org.jetbrains.anko.sdk27.listeners.onCheckedChange
@ -76,8 +79,8 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
} }
private fun initData() { private fun initData() {
cb_share_layout.isChecked = getPrefBoolean(PreferKey.shareLayout) cb_share_layout.isChecked = ReadBookConfig.shareLayout
requireContext().getPrefInt(PreferKey.pageAnim).let { ReadBookConfig.pageAnim.let {
if (it >= 0 && it < rg_page_anim.childCount) { if (it >= 0 && it < rg_page_anim.childCount) {
rg_page_anim.check(rg_page_anim[it].id) rg_page_anim.check(rg_page_anim[it].id)
} }
@ -92,14 +95,14 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
tv_title_center.onClick { tv_title_center.onClick {
ReadBookConfig.durConfig.apply { ReadBookConfig.apply {
titleCenter = !titleCenter titleCenter = !titleCenter
tv_title_center.isSelected = titleCenter tv_title_center.isSelected = titleCenter
} }
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
tv_text_bold.onClick { tv_text_bold.onClick {
ReadBookConfig.durConfig.apply { ReadBookConfig.apply {
textBold = !textBold textBold = !textBold
tv_text_bold.isSelected = textBold tv_text_bold.isSelected = textBold
} }
@ -113,7 +116,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
title = getString(R.string.text_indent), title = getString(R.string.text_indent),
items = resources.getStringArray(R.array.indent).toList() items = resources.getStringArray(R.array.indent).toList()
) { _, index -> ) { _, index ->
putPrefInt(PreferKey.bodyIndent, index) BookHelp.bodyIndentCount = index
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
} }
@ -125,28 +128,28 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
} }
} }
dsb_text_size.onChanged = { dsb_text_size.onChanged = {
ReadBookConfig.durConfig.textSize = it + 5 ReadBookConfig.textSize = it + 5
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
dsb_text_letter_spacing.onChanged = { dsb_text_letter_spacing.onChanged = {
ReadBookConfig.durConfig.letterSpacing = (it - 50) / 100f ReadBookConfig.letterSpacing = (it - 50) / 100f
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
dsb_line_size.onChanged = { dsb_line_size.onChanged = {
ReadBookConfig.durConfig.lineSpacingExtra = it ReadBookConfig.lineSpacingExtra = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
dsb_paragraph_spacing.onChanged = { dsb_paragraph_spacing.onChanged = {
ReadBookConfig.durConfig.paragraphSpacing = it ReadBookConfig.paragraphSpacing = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
rg_page_anim.onCheckedChange { _, checkedId -> rg_page_anim.onCheckedChange { _, checkedId ->
for (i in 0 until rg_page_anim.childCount) { for (i in 0 until rg_page_anim.childCount) {
if (checkedId == rg_page_anim[i].id) { if (checkedId == rg_page_anim[i].id) {
requireContext().putPrefInt(PreferKey.pageAnim, i) ReadBookConfig.pageAnim = i
val activity = activity val activity = activity
if (activity is ReadBookActivity) { if (activity is ReadBookActivity) {
activity.page_view.upPageAnim(i) activity.page_view.upPageAnim()
} }
break break
} }
@ -154,7 +157,9 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
} }
cb_share_layout.onCheckedChangeListener = { checkBox, isChecked -> cb_share_layout.onCheckedChangeListener = { checkBox, isChecked ->
if (checkBox.isPressed) { if (checkBox.isPressed) {
putPrefBoolean(PreferKey.shareLayout, isChecked) ReadBookConfig.shareLayout = isChecked
upStyle()
postEvent(EventBus.UP_CONFIG, true)
} }
} }
bg0.onClick { changeBg(0) } bg0.onClick { changeBg(0) }
@ -190,7 +195,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
} }
private fun upStyle() { private fun upStyle() {
ReadBookConfig.durConfig.let { ReadBookConfig.let {
tv_title_center.isSelected = it.titleCenter tv_title_center.isSelected = it.titleCenter
tv_text_bold.isSelected = it.textBold tv_text_bold.isSelected = it.textBold
dsb_text_size.progress = it.textSize - 5 dsb_text_size.progress = it.textSize - 5

@ -28,10 +28,9 @@ import io.legado.app.lib.dialogs.cancelButton
import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.dialogs.okButton
import io.legado.app.model.localBook.AnalyzeTxtFile import io.legado.app.model.localBook.AnalyzeTxtFile
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getVerticalDivider
import kotlinx.android.synthetic.main.dialog_toc_regex.* import kotlinx.android.synthetic.main.dialog_toc_regex.*
import kotlinx.android.synthetic.main.dialog_toc_regex_edit.*
import kotlinx.android.synthetic.main.dialog_toc_regex_edit.view.* import kotlinx.android.synthetic.main.dialog_toc_regex_edit.view.*
import kotlinx.android.synthetic.main.item_toc_regex.view.* import kotlinx.android.synthetic.main.item_toc_regex.view.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -76,7 +75,7 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
private fun initView() { private fun initView() {
adapter = TocRegexAdapter(requireContext()) adapter = TocRegexAdapter(requireContext())
recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter recycler_view.adapter = adapter
val itemTouchCallback = ItemTouchCallback() val itemTouchCallback = ItemTouchCallback()
itemTouchCallback.onItemTouchCallbackListener = adapter itemTouchCallback.onItemTouchCallbackListener = adapter
@ -150,7 +149,7 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
} }
} }
okButton { okButton {
rootView?.let { rootView?.apply {
tocRule.name = tv_rule_name.text.toString() tocRule.name = tv_rule_name.text.toString()
tocRule.rule = tv_rule_regex.text.toString() tocRule.rule = tv_rule_regex.text.toString()
saveRule(tocRule, rule) saveRule(tocRule, rule)

@ -10,7 +10,10 @@ import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.entities.* import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.entities.TextChar
import io.legado.app.ui.book.read.page.entities.TextLine
import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.dp import io.legado.app.utils.dp
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import io.legado.app.utils.removePref import io.legado.app.utils.removePref
@ -20,10 +23,10 @@ import io.legado.app.utils.removePref
object ChapterProvider { object ChapterProvider {
var viewWidth = 0 var viewWidth = 0
var viewHeight = 0 var viewHeight = 0
private var visibleWidth = 0 var visibleWidth = 0
private var visibleHeight = 0 var visibleHeight = 0
private var paddingLeft = 0 var paddingLeft = 0
private var paddingTop = 0 var paddingTop = 0
private var lineSpacingExtra = 0f private var lineSpacingExtra = 0f
private var paragraphSpacing = 0 private var paragraphSpacing = 0
var typeface: Typeface = Typeface.SANS_SERIF var typeface: Typeface = Typeface.SANS_SERIF
@ -32,7 +35,7 @@ object ChapterProvider {
private var bodyIndent = BookHelp.bodyIndent private var bodyIndent = BookHelp.bodyIndent
init { init {
upStyle(ReadBookConfig.durConfig) upStyle()
} }
/** /**
@ -116,12 +119,12 @@ object ChapterProvider {
Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true
) )
for (lineIndex in 0 until layout.lineCount) { for (lineIndex in 0 until layout.lineCount) {
textPages.last().height = durY
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
val textLine = TextLine(isTitle = true) val textLine = TextLine(isTitle = true)
if (durY < visibleHeight) { if (durY < visibleHeight) {
textPages.last().textLines.add(textLine) textPages.last().textLines.add(textLine)
} else { } else {
textPages.last().height = durY
textPages.last().text = stringBuilder.toString() textPages.last().text = stringBuilder.toString()
stringBuilder.clear() stringBuilder.clear()
pageLines.add(textPages.last().textLines.size) pageLines.add(textPages.last().textLines.size)
@ -131,10 +134,11 @@ object ChapterProvider {
textPages.add(TextPage()) textPages.add(TextPage())
textPages.last().textLines.add(textLine) textPages.last().textLines.add(textLine)
} }
textLine.lineBottom = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat()
textLine.lineTop = (paddingTop + durY - textLine.lineTop = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat() (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat()
textLine.lineBase = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat()
textLine.lineBottom = textLine.lineBase + titlePaint.fontMetrics.descent
val words = val words =
title.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) title.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex))
stringBuilder.append(words) stringBuilder.append(words)
@ -150,8 +154,8 @@ object ChapterProvider {
val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw) val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw)
val textChar = TextChar( val textChar = TextChar(
charData = char, charData = char,
leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), start = paddingLeft + x,
rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) end = paddingLeft + x1
) )
textLine.textChars.add(textChar) textLine.textChars.add(textChar)
x = x1 x = x1
@ -160,7 +164,7 @@ object ChapterProvider {
//最后一行 //最后一行
textLine.text = "$words\n" textLine.text = "$words\n"
stringBuilder.append("\n") stringBuilder.append("\n")
var x = if (ReadBookConfig.durConfig.titleCenter) var x = if (ReadBookConfig.titleCenter)
(visibleWidth - layout.getLineMax(lineIndex)) / 2 (visibleWidth - layout.getLineMax(lineIndex)) / 2
else 0f else 0f
for (i in words.indices) { for (i in words.indices) {
@ -169,14 +173,13 @@ object ChapterProvider {
val x1 = x + cw val x1 = x + cw
val textChar = TextChar( val textChar = TextChar(
charData = char, charData = char,
leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), start = paddingLeft + x,
rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) end = paddingLeft + x1
) )
textLine.textChars.add(textChar) textLine.textChars.add(textChar)
x = x1 x = x1
} }
} }
textLine.lineBottom = textLine.lineBottom + titlePaint.fontMetrics.descent
} }
durY += paragraphSpacing durY += paragraphSpacing
return durY return durY
@ -199,12 +202,12 @@ object ChapterProvider {
Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true
) )
for (lineIndex in 0 until layout.lineCount) { for (lineIndex in 0 until layout.lineCount) {
val textLine = TextLine(isTitle = false) textPages.last().height = durY
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
val textLine = TextLine()
if (durY < visibleHeight) { if (durY < visibleHeight) {
textPages.last().textLines.add(textLine) textPages.last().textLines.add(textLine)
} else { } else {
textPages.last().height = durY
textPages.last().text = stringBuilder.toString() textPages.last().text = stringBuilder.toString()
stringBuilder.clear() stringBuilder.clear()
pageLines.add(textPages.last().textLines.size) pageLines.add(textPages.last().textLines.size)
@ -214,10 +217,11 @@ object ChapterProvider {
textPages.add(TextPage()) textPages.add(TextPage())
textPages.last().textLines.add(textLine) textPages.last().textLines.add(textLine)
} }
textLine.lineBottom = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat()
textLine.lineTop = (paddingTop + durY - textLine.lineTop = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat() (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat()
textLine.lineBase = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat()
textLine.lineBottom = textLine.lineBase + contentPaint.fontMetrics.descent
var words = var words =
text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex))
stringBuilder.append(words) stringBuilder.append(words)
@ -231,8 +235,8 @@ object ChapterProvider {
val x1 = x + icw val x1 = x + icw
val textChar = TextChar( val textChar = TextChar(
charData = bodyIndent[i].toString(), charData = bodyIndent[i].toString(),
leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), start = paddingLeft + x,
rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) end = paddingLeft + x1
) )
textLine.textChars.add(textChar) textLine.textChars.add(textChar)
x = x1 x = x1
@ -246,8 +250,8 @@ object ChapterProvider {
val x1 = if (i != words.lastIndex) x + cw + d else x + cw val x1 = if (i != words.lastIndex) x + cw + d else x + cw
val textChar1 = TextChar( val textChar1 = TextChar(
charData = char, charData = char,
leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), start = paddingLeft + x,
rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) end = paddingLeft + x1
) )
textLine.textChars.add(textChar1) textLine.textChars.add(textChar1)
x = x1 x = x1
@ -263,8 +267,8 @@ object ChapterProvider {
val x1 = x + cw val x1 = x + cw
val textChar = TextChar( val textChar = TextChar(
charData = char, charData = char,
leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), start = paddingLeft + x,
rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) end = paddingLeft + x1
) )
textLine.textChars.add(textChar) textLine.textChars.add(textChar)
x = x1 x = x1
@ -280,14 +284,13 @@ object ChapterProvider {
val x1 = if (i != words.lastIndex) x + cw + d else x + cw val x1 = if (i != words.lastIndex) x + cw + d else x + cw
val textChar = TextChar( val textChar = TextChar(
charData = char, charData = char,
leftBottomPosition = TextPoint(paddingLeft + x, textLine.lineBottom), start = paddingLeft + x,
rightTopPosition = TextPoint(paddingLeft + x1, textLine.lineTop) end = paddingLeft + x1
) )
textLine.textChars.add(textChar) textLine.textChars.add(textChar)
x = x1 x = x1
} }
} }
textLine.lineBottom = textLine.lineBottom + contentPaint.fontMetrics.descent
} }
durY += paragraphSpacing durY += paragraphSpacing
return durY return durY
@ -297,7 +300,7 @@ object ChapterProvider {
/** /**
* 更新样式 * 更新样式
*/ */
fun upStyle(config: ReadBookConfig.Config) { fun upStyle() {
typeface = try { typeface = try {
val fontPath = App.INSTANCE.getPrefString(PreferKey.readBookFont) val fontPath = App.INSTANCE.getPrefString(PreferKey.readBookFont)
if (!TextUtils.isEmpty(fontPath)) { if (!TextUtils.isEmpty(fontPath)) {
@ -311,34 +314,34 @@ object ChapterProvider {
} }
//标题 //标题
titlePaint.isAntiAlias = true titlePaint.isAntiAlias = true
titlePaint.color = config.textColor() titlePaint.color = ReadBookConfig.durConfig.textColor()
titlePaint.letterSpacing = config.letterSpacing titlePaint.letterSpacing = ReadBookConfig.letterSpacing
titlePaint.typeface = Typeface.create(typeface, Typeface.BOLD) titlePaint.typeface = Typeface.create(typeface, Typeface.BOLD)
//正文 //正文
contentPaint.isAntiAlias = true contentPaint.isAntiAlias = true
contentPaint.color = config.textColor() contentPaint.color = ReadBookConfig.durConfig.textColor()
contentPaint.letterSpacing = config.letterSpacing contentPaint.letterSpacing = ReadBookConfig.letterSpacing
val bold = if (config.textBold) Typeface.BOLD else Typeface.NORMAL val bold = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL
contentPaint.typeface = Typeface.create(typeface, bold) contentPaint.typeface = Typeface.create(typeface, bold)
//间距 //间距
lineSpacingExtra = config.lineSpacingExtra.dp.toFloat() lineSpacingExtra = ReadBookConfig.lineSpacingExtra.dp.toFloat()
paragraphSpacing = config.paragraphSpacing.dp paragraphSpacing = ReadBookConfig.paragraphSpacing.dp
titlePaint.textSize = (config.textSize + 2).dp.toFloat() titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat()
contentPaint.textSize = config.textSize.dp.toFloat() contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat()
bodyIndent = BookHelp.bodyIndent bodyIndent = BookHelp.bodyIndent
upSize(config) upSize()
} }
/** /**
* 更新View尺寸 * 更新View尺寸
*/ */
fun upSize(config: ReadBookConfig.Config) { fun upSize() {
paddingLeft = config.paddingLeft.dp paddingLeft = ReadBookConfig.paddingLeft.dp
paddingTop = config.paddingTop.dp paddingTop = ReadBookConfig.paddingTop.dp
visibleWidth = viewWidth - paddingLeft - config.paddingRight.dp visibleWidth = viewWidth - paddingLeft - ReadBookConfig.paddingRight.dp
visibleHeight = viewHeight - paddingTop - config.paddingBottom.dp visibleHeight = viewHeight - paddingTop - ReadBookConfig.paddingBottom.dp
} }
} }

@ -3,14 +3,13 @@ package io.legado.app.ui.book.read.page
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.text.Layout
import android.text.StaticLayout
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.book.read.page.entities.TextChar
import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.activity import io.legado.app.utils.activity
import io.legado.app.utils.getCompatColor import io.legado.app.utils.getCompatColor
@ -18,6 +17,8 @@ import io.legado.app.utils.getPrefBoolean
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble)
var upView: ((TextPage) -> Unit)? = null
private val selectedPaint by lazy { private val selectedPaint by lazy {
Paint().apply { Paint().apply {
color = context.getCompatColor(R.color.btn_bg_press_2) color = context.getCompatColor(R.color.btn_bg_press_2)
@ -25,7 +26,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
} }
private var callBack: CallBack private var callBack: CallBack
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble)
private var selectLineStart = 0 private var selectLineStart = 0
private var selectCharStart = 0 private var selectCharStart = 0
private var selectLineEnd = 0 private var selectLineEnd = 0
@ -36,7 +36,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
private val maxScrollOffset = 100f private val maxScrollOffset = 100f
private var pageOffset = 0f private var pageOffset = 0f
private var linePos = 0 private var linePos = 0
private var isLastPage = false
init { init {
callBack = activity as CallBack callBack = activity as CallBack
@ -51,70 +50,105 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh) super.onSizeChanged(w, h, oldw, oldh)
ReadBookConfig.durConfig.let { ChapterProvider.viewWidth = w
ChapterProvider.viewWidth = w ChapterProvider.viewHeight = h
ChapterProvider.viewHeight = h ChapterProvider.upSize()
ChapterProvider.upSize(ReadBookConfig.durConfig) textPage.format()
}
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
super.onDraw(canvas) super.onDraw(canvas)
if (textPage.textLines.isEmpty()) { if (ReadBookConfig.isScroll) {
drawMsg(canvas, textPage.text) drawScrollPage(canvas)
} else { } else {
drawHorizontalPage(canvas) drawHorizontalPage(canvas)
} }
} }
@Suppress("DEPRECATION") private fun drawHorizontalPage(canvas: Canvas) {
private fun drawMsg(canvas: Canvas, msg: String) { textPage.textLines.forEach { textLine ->
val layout = StaticLayout( drawChars(
msg, ChapterProvider.contentPaint, width, canvas,
Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false textLine.textChars,
) textLine.lineTop,
val y = (height - layout.height) / 2f textLine.lineBase,
for (lineIndex in 0 until layout.lineCount) { textLine.lineBottom,
val x = (width - layout.getLineMax(lineIndex)) / 2 textLine.isTitle,
val words = textLine.isReadAloud
msg.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) )
canvas.drawText(words, x, y, ChapterProvider.contentPaint)
} }
} }
private fun drawHorizontalPage(canvas: Canvas) { private fun drawScrollPage(canvas: Canvas) {
val mPageOffset = pageOffset
textPage.textLines.forEach { textLine -> textPage.textLines.forEach { textLine ->
val textPaint = if (textLine.isTitle) { val lineTop = textLine.lineTop + mPageOffset
ChapterProvider.titlePaint val lineBase = textLine.lineBase + mPageOffset
} else { val lineBottom = textLine.lineBottom + mPageOffset
ChapterProvider.contentPaint drawChars(
} canvas,
textPaint.color = if (textLine.isReadAloud) { textLine.textChars,
context.accentColor lineTop,
} else { lineBase,
ReadBookConfig.durConfig.textColor() lineBottom,
} textLine.isTitle,
textLine.textChars.forEach { textLine.isReadAloud
canvas.drawText( )
it.charData, }
it.leftBottomPosition.x, pageFactory.nextPage?.textLines?.forEach { textLine ->
it.leftBottomPosition.y, val yPy = mPageOffset + textPage.height - ChapterProvider.paddingTop
textPaint val lineTop = textLine.lineTop + yPy
) val lineBase = textLine.lineBase + yPy
if (it.selected) { val lineBottom = textLine.lineBottom + yPy
canvas.drawRect( drawChars(
it.leftBottomPosition.x, canvas,
textLine.lineTop, textLine.textChars,
it.rightTopPosition.x, lineTop,
textLine.lineBottom, lineBase,
selectedPaint lineBottom,
) textLine.isTitle,
} textLine.isReadAloud
)
}
pageFactory.prevPage?.textLines?.forEach { textLine ->
val yPy = mPageOffset + ChapterProvider.paddingTop
val lineTop = -textLine.lineTop + yPy
val lineBase = -textLine.lineBase + yPy
val lineBottom = -textLine.lineBottom + yPy
drawChars(
canvas,
textLine.textChars,
lineTop,
lineBase,
lineBottom,
textLine.isTitle,
textLine.isReadAloud
)
}
}
private fun drawChars(
canvas: Canvas,
textChars: List<TextChar>,
lineTop: Float,
lineBase: Float,
lineBottom: Float,
isTitle: Boolean,
isReadAloud: Boolean
) {
val textPaint = if (isTitle) ChapterProvider.titlePaint else ChapterProvider.contentPaint
textPaint.color =
if (isReadAloud) context.accentColor else ReadBookConfig.durConfig.textColor()
textChars.forEach {
canvas.drawText(it.charData, it.start, lineBase, textPaint)
if (it.selected) {
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint)
} }
} }
} }
fun onScroll(mOffset: Float) { fun onScroll(mOffset: Float) {
if (mOffset == 0f) return
var offset = mOffset var offset = mOffset
if (offset > maxScrollOffset) { if (offset > maxScrollOffset) {
offset = maxScrollOffset offset = maxScrollOffset
@ -122,78 +156,56 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
offset = -maxScrollOffset offset = -maxScrollOffset
} }
if (!isLastPage || offset < 0) { pageOffset += offset
pageOffset += offset if (pageOffset > 0) {
isLastPage = false pageFactory.moveToPrev()
} textPage = pageFactory.currentPage ?: TextPage().format()
// 首页 pageOffset -= textPage.height
if (pageOffset < 0 && !pageFactory.hasPrev()) { upView?.invoke(textPage)
pageOffset = 0f } else if (pageOffset < -textPage.height) {
} pageOffset += textPage.height
pageFactory.moveToNext()
val cHeight = if (textPage.height > 0) textPage.height else height textPage = pageFactory.currentPage ?: TextPage().format()
if (offset > 0 && pageOffset > cHeight) { upView?.invoke(textPage)
} }
invalidate()
} }
fun resetPageOffset() { fun resetPageOffset() {
pageOffset = 0f pageOffset = 0f
linePos = 0 linePos = 0
isLastPage = false
} }
private fun switchToPageOffset(offset: Int) { fun selectText(x: Float, y: Float, select: (lineIndex: Int, charIndex: Int) -> Unit) {
when (offset) {
1 -> {
}
-1 -> {
}
}
}
fun selectText(x: Float, y: Float): Boolean {
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { for ((lineIndex, textLine) in textPage.textLines.withIndex()) {
if (y > textLine.lineTop && y < textLine.lineBottom) { if (y > textLine.lineTop && y < textLine.lineBottom) {
for ((charIndex, textChar) in textLine.textChars.withIndex()) { for ((charIndex, textChar) in textLine.textChars.withIndex()) {
if (x > textChar.leftBottomPosition.x && x < textChar.rightTopPosition.x) { if (x > textChar.start && x < textChar.end) {
textChar.selected = true textChar.selected = true
invalidate() invalidate()
selectLineStart = lineIndex selectLineStart = lineIndex
selectCharStart = charIndex selectCharStart = charIndex
selectLineEnd = lineIndex selectLineEnd = lineIndex
selectCharEnd = charIndex selectCharEnd = charIndex
upSelectedStart( upSelectedStart(textChar.start, textLine.lineBottom)
textChar.leftBottomPosition.x, upSelectedEnd(textChar.end, textLine.lineBottom)
textChar.leftBottomPosition.y select(lineIndex, charIndex)
)
upSelectedEnd(
textChar.rightTopPosition.x,
textChar.leftBottomPosition.y
)
return true
} }
} }
break break
} }
} }
return false
} }
fun selectStartMove(x: Float, y: Float) { fun selectStartMove(x: Float, y: Float) {
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { for ((lineIndex, textLine) in textPage.textLines.withIndex()) {
if (y > textLine.lineTop && y < textLine.lineBottom) { if (y > textLine.lineTop && y < textLine.lineBottom) {
for ((charIndex, textChar) in textLine.textChars.withIndex()) { for ((charIndex, textChar) in textLine.textChars.withIndex()) {
if (x > textChar.leftBottomPosition.x && x < textChar.rightTopPosition.x) { if (x > textChar.start && x < textChar.end) {
if (selectLineStart != lineIndex || selectCharStart != charIndex) { if (selectLineStart != lineIndex || selectCharStart != charIndex) {
selectLineStart = lineIndex selectLineStart = lineIndex
selectCharStart = charIndex selectCharStart = charIndex
upSelectedStart( upSelectedStart(textChar.start, textLine.lineBottom)
textChar.leftBottomPosition.x,
textChar.leftBottomPosition.y
)
upSelectChars(textPage) upSelectChars(textPage)
} }
break break
@ -204,18 +216,24 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
} }
fun selectStartMoveIndex(lineIndex: Int, charIndex: Int) {
selectLineStart = lineIndex
selectCharStart = charIndex
val textLine = textPage.textLines[lineIndex]
val textChar = textLine.textChars[charIndex]
upSelectedStart(textChar.start, textLine.lineBottom)
upSelectChars(textPage)
}
fun selectEndMove(x: Float, y: Float) { fun selectEndMove(x: Float, y: Float) {
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { for ((lineIndex, textLine) in textPage.textLines.withIndex()) {
if (y > textLine.lineTop && y < textLine.lineBottom) { if (y > textLine.lineTop && y < textLine.lineBottom) {
for ((charIndex, textChar) in textLine.textChars.withIndex()) { for ((charIndex, textChar) in textLine.textChars.withIndex()) {
if (x > textChar.leftBottomPosition.x && x < textChar.rightTopPosition.x) { if (x > textChar.start && x < textChar.end) {
if (selectLineEnd != lineIndex || selectCharEnd != charIndex) { if (selectLineEnd != lineIndex || selectCharEnd != charIndex) {
selectLineEnd = lineIndex selectLineEnd = lineIndex
selectCharEnd = charIndex selectCharEnd = charIndex
upSelectedEnd( upSelectedEnd(textChar.end, textLine.lineBottom)
textChar.rightTopPosition.x,
textChar.leftBottomPosition.y
)
upSelectChars(textPage) upSelectChars(textPage)
} }
break break
@ -226,6 +244,15 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
} }
fun selectEndMoveIndex(lineIndex: Int, charIndex: Int) {
selectLineEnd = lineIndex
selectCharEnd = charIndex
val textLine = textPage.textLines[lineIndex]
val textChar = textLine.textChars[charIndex]
upSelectedEnd(textChar.end, textLine.lineBottom)
upSelectChars(textPage)
}
private fun upSelectChars(textPage: TextPage) { private fun upSelectChars(textPage: TextPage) {
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { for ((lineIndex, textLine) in textPage.textLines.withIndex()) {
for ((charIndex, textChar) in textLine.textChars.withIndex()) { for ((charIndex, textChar) in textLine.textChars.withIndex()) {

@ -7,7 +7,6 @@ import android.view.MotionEvent
import android.widget.FrameLayout import android.widget.FrameLayout
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppConst.TIME_FORMAT import io.legado.app.constant.AppConst.TIME_FORMAT
import io.legado.app.constant.PreferKey
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.* import io.legado.app.utils.*
@ -25,18 +24,23 @@ class ContentView(context: Context) : FrameLayout(context) {
upStyle() upStyle()
upTime() upTime()
content_text_view.upView = {
tv_bottom_left.text = it.title
pageSize = it.pageSize
setPageIndex(it.index)
}
} }
fun upStyle() { fun upStyle() {
ReadBookConfig.durConfig.apply { ReadBookConfig.apply {
tv_top_left.typeface = ChapterProvider.typeface tv_top_left.typeface = ChapterProvider.typeface
tv_top_right.typeface = ChapterProvider.typeface tv_top_right.typeface = ChapterProvider.typeface
tv_bottom_left.typeface = ChapterProvider.typeface tv_bottom_left.typeface = ChapterProvider.typeface
tv_bottom_right.typeface = ChapterProvider.typeface tv_bottom_right.typeface = ChapterProvider.typeface
//显示状态栏时隐藏header //显示状态栏时隐藏header
if (context.getPrefBoolean(PreferKey.hideStatusBar, false)) { if (hideStatusBar) {
ll_header.layoutParams = ll_header.layoutParams =
ll_header.layoutParams.apply { height = context.getStatusBarHeight() } ll_header.layoutParams.apply { height = context.statusBarHeight }
ll_header.setPadding( ll_header.setPadding(
headerPaddingLeft.dp, headerPaddingLeft.dp,
headerPaddingTop.dp, headerPaddingTop.dp,
@ -47,7 +51,7 @@ class ContentView(context: Context) : FrameLayout(context) {
page_panel.setPadding(0, 0, 0, 0) page_panel.setPadding(0, 0, 0, 0)
} else { } else {
ll_header.gone() ll_header.gone()
page_panel.setPadding(0, context.getStatusBarHeight(), 0, 0) page_panel.setPadding(0, context.statusBarHeight, 0, 0)
} }
content_text_view.setPadding( content_text_view.setPadding(
paddingLeft.dp, paddingLeft.dp,
@ -61,7 +65,7 @@ class ContentView(context: Context) : FrameLayout(context) {
footerPaddingRight.dp, footerPaddingRight.dp,
footerPaddingBottom.dp footerPaddingBottom.dp
) )
textColor().let { durConfig.textColor().let {
tv_top_left.setTextColor(it) tv_top_left.setTextColor(it)
tv_top_right.setTextColor(it) tv_top_right.setTextColor(it)
tv_bottom_left.setTextColor(it) tv_bottom_left.setTextColor(it)
@ -72,10 +76,10 @@ class ContentView(context: Context) : FrameLayout(context) {
val headerHeight: Int val headerHeight: Int
get() { get() {
return if (context.getPrefBoolean(PreferKey.hideStatusBar, false)) { return if (ReadBookConfig.hideStatusBar) {
ll_header.height ll_header.height
} else { } else {
context.getStatusBarHeight() context.statusBarHeight
} }
} }
@ -93,10 +97,11 @@ class ContentView(context: Context) : FrameLayout(context) {
fun setContent(textPage: TextPage?) { fun setContent(textPage: TextPage?) {
if (textPage != null) { if (textPage != null) {
content_text_view.setContent(textPage)
tv_bottom_left.text = textPage.title tv_bottom_left.text = textPage.title
pageSize = textPage.pageSize pageSize = textPage.pageSize
setPageIndex(textPage.index) setPageIndex(textPage.index)
content_text_view.resetPageOffset()
content_text_view.setContent(textPage)
} }
} }
@ -115,19 +120,27 @@ class ContentView(context: Context) : FrameLayout(context) {
content_text_view.selectAble = selectAble content_text_view.selectAble = selectAble
} }
fun selectText(e: MotionEvent): Boolean { fun selectText(e: MotionEvent, select: (lineIndex: Int, charIndex: Int) -> Unit) {
val y = e.y - headerHeight val y = e.y - headerHeight
return content_text_view.selectText(e.x, y) return content_text_view.selectText(e.x, y, select)
} }
fun selectStartMove(x: Float, y: Float) { fun selectStartMove(x: Float, y: Float) {
content_text_view.selectStartMove(x, y - headerHeight) content_text_view.selectStartMove(x, y - headerHeight)
} }
fun selectStartMoveIndex(lineIndex: Int, charIndex: Int) {
content_text_view.selectStartMoveIndex(lineIndex, charIndex)
}
fun selectEndMove(x: Float, y: Float) { fun selectEndMove(x: Float, y: Float) {
content_text_view.selectEndMove(x, y - headerHeight) content_text_view.selectEndMove(x, y - headerHeight)
} }
fun selectEndMoveIndex(lineIndex: Int, charIndex: Int) {
content_text_view.selectEndMoveIndex(lineIndex, charIndex)
}
fun cancelSelect() { fun cancelSelect() {
content_text_view.cancelSelect() content_text_view.cancelSelect()
} }

@ -1,15 +1,11 @@
package io.legado.app.ui.book.read.page package io.legado.app.ui.book.read.page
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
interface DataSource { interface DataSource {
val isScrollDelegate: Boolean
val pageIndex: Int val pageIndex: Int get() = ReadBook.durChapterPos()
fun setPageIndex(pageIndex: Int)
fun getChapterPosition(): Int
fun getCurrentChapter(): TextChapter? fun getCurrentChapter(): TextChapter?
@ -20,5 +16,4 @@ interface DataSource {
fun hasNextChapter(): Boolean fun hasNextChapter(): Boolean
fun hasPrevChapter(): Boolean fun hasPrevChapter(): Boolean
} }

@ -8,13 +8,13 @@ abstract class PageFactory<DATA>(protected val dataSource: DataSource) {
abstract fun moveToNext():Boolean abstract fun moveToNext():Boolean
abstract fun moveToPrevious(): Boolean abstract fun moveToPrev(): Boolean
abstract fun nextPage(): DATA? abstract val nextPage: DATA?
abstract fun previousPage(): DATA? abstract val prevPage: DATA?
abstract fun currentPage(): DATA? abstract val currentPage: DATA?
abstract fun hasNext(): Boolean abstract fun hasNext(): Boolean

@ -6,13 +6,11 @@ import android.graphics.Canvas
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import android.widget.FrameLayout import android.widget.FrameLayout
import io.legado.app.constant.PreferKey
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.delegate.* import io.legado.app.ui.book.read.page.delegate.*
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.utils.activity import io.legado.app.utils.activity
import io.legado.app.utils.getPrefInt
class PageView(context: Context, attrs: AttributeSet) : class PageView(context: Context, attrs: AttributeSet) :
FrameLayout(context, attrs), FrameLayout(context, attrs),
@ -37,7 +35,7 @@ class PageView(context: Context, attrs: AttributeSet) :
upBg() upBg()
setWillNotDraw(false) setWillNotDraw(false)
pageFactory = TextPageFactory(this) pageFactory = TextPageFactory(this)
upPageAnim(context.getPrefInt(PreferKey.pageAnim)) upPageAnim()
} }
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
@ -64,8 +62,9 @@ class PageView(context: Context, attrs: AttributeSet) :
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean { override fun onTouchEvent(event: MotionEvent): Boolean {
pageDelegate?.onTouch(event)
callBack.screenOffTimerStart() callBack.screenOffTimerStart()
return pageDelegate?.onTouch(event) ?: super.onTouchEvent(event) return true
} }
fun onDestroy() { fun onDestroy() {
@ -76,7 +75,7 @@ class PageView(context: Context, attrs: AttributeSet) :
fun fillPage(direction: PageDelegate.Direction) { fun fillPage(direction: PageDelegate.Direction) {
when (direction) { when (direction) {
PageDelegate.Direction.PREV -> { PageDelegate.Direction.PREV -> {
pageFactory.moveToPrevious() pageFactory.moveToPrev()
upContent() upContent()
} }
PageDelegate.Direction.NEXT -> { PageDelegate.Direction.NEXT -> {
@ -87,10 +86,10 @@ class PageView(context: Context, attrs: AttributeSet) :
} }
} }
fun upPageAnim(pageAnim: Int) { fun upPageAnim() {
pageDelegate?.onDestroy() pageDelegate?.onDestroy()
pageDelegate = null pageDelegate = null
pageDelegate = when (pageAnim) { pageDelegate = when (ReadBookConfig.pageAnim) {
0 -> CoverPageDelegate(this) 0 -> CoverPageDelegate(this)
1 -> SlidePageDelegate(this) 1 -> SlidePageDelegate(this)
2 -> SimulationPageDelegate(this) 2 -> SimulationPageDelegate(this)
@ -100,15 +99,17 @@ class PageView(context: Context, attrs: AttributeSet) :
upContent() upContent()
} }
fun upContent(position: Int = 0) { fun upContent(relativePosition: Int = 0) {
pageFactory.let { if (ReadBookConfig.isScroll) {
when (position) { curPage.setContent(pageFactory.currentPage)
-1 -> prevPage.setContent(it.previousPage()) } else {
1 -> nextPage.setContent(it.nextPage()) when (relativePosition) {
-1 -> prevPage.setContent(pageFactory.prevPage)
1 -> nextPage.setContent(pageFactory.nextPage)
else -> { else -> {
curPage.setContent(it.currentPage()) curPage.setContent(pageFactory.currentPage)
nextPage.setContent(it.nextPage()) nextPage.setContent(pageFactory.nextPage)
prevPage.setContent(it.previousPage()) prevPage.setContent(pageFactory.prevPage)
} }
} }
} }
@ -163,20 +164,6 @@ class PageView(context: Context, attrs: AttributeSet) :
nextPage.upBattery(battery) nextPage.upBattery(battery)
} }
override val isScrollDelegate: Boolean
get() = pageDelegate is ScrollPageDelegate
override val pageIndex: Int
get() = ReadBook.durChapterPos()
override fun setPageIndex(pageIndex: Int) {
callBack.setPageIndex(pageIndex)
}
override fun getChapterPosition(): Int {
return ReadBook.durChapterIndex
}
override fun getCurrentChapter(): TextChapter? { override fun getCurrentChapter(): TextChapter? {
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
} }
@ -198,19 +185,8 @@ class PageView(context: Context, attrs: AttributeSet) :
} }
interface CallBack { interface CallBack {
val isInitFinish: Boolean val isInitFinish: Boolean
/**
* 保存页数
*/
fun setPageIndex(pageIndex: Int)
/**
* 点击屏幕中间
*/
fun clickCenter() fun clickCenter()
fun screenOffTimerStart() fun screenOffTimerStart()
} }
} }

@ -14,63 +14,65 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
} }
override fun moveToFirst() { override fun moveToFirst() {
dataSource.setPageIndex(0) ReadBook.setPageIndex(0)
} }
override fun moveToLast() = with(dataSource) { override fun moveToLast() = with(dataSource) {
getCurrentChapter()?.let { getCurrentChapter()?.let {
if (it.pageSize() == 0) { if (it.pageSize() == 0) {
setPageIndex(0) ReadBook.setPageIndex(0)
} else { } else {
setPageIndex(it.pageSize().minus(1)) ReadBook.setPageIndex(it.pageSize().minus(1))
} }
} ?: setPageIndex(0) } ?: ReadBook.setPageIndex(0)
} }
override fun moveToNext(): Boolean = with(dataSource) { override fun moveToNext(): Boolean = with(dataSource) {
return if (hasNext()) { return if (hasNext()) {
if (getCurrentChapter()?.isLastIndex(pageIndex) == true if (getCurrentChapter()?.isLastIndex(pageIndex) == true) {
) {
ReadBook.moveToNextChapter(false) ReadBook.moveToNextChapter(false)
} else { } else {
setPageIndex(pageIndex.plus(1)) ReadBook.setPageIndex(pageIndex.plus(1))
} }
true true
} else } else
false false
} }
override fun moveToPrevious(): Boolean = with(dataSource) { override fun moveToPrev(): Boolean = with(dataSource) {
return if (hasPrev()) { return if (hasPrev()) {
if (pageIndex <= 0) { if (pageIndex <= 0) {
ReadBook.moveToPrevChapter(false) ReadBook.moveToPrevChapter(false)
} else { } else {
setPageIndex(pageIndex.minus(1)) ReadBook.setPageIndex(pageIndex.minus(1))
} }
true true
} else } else
false false
} }
override fun currentPage(): TextPage? = with(dataSource) { override val currentPage: TextPage?
return getCurrentChapter()?.page(pageIndex) get() = with(dataSource) {
} return getCurrentChapter()?.page(pageIndex)
}
override fun nextPage(): TextPage? = with(dataSource) { override val nextPage: TextPage?
getCurrentChapter()?.let { get() = with(dataSource) {
if (pageIndex < it.pageSize() - 1) { getCurrentChapter()?.let {
return getCurrentChapter()?.page(pageIndex + 1)?.removePageAloudSpan() if (pageIndex < it.pageSize() - 1) {
return getCurrentChapter()?.page(pageIndex + 1)?.removePageAloudSpan()
}
} }
return getNextChapter()?.page(0)?.removePageAloudSpan()
} }
return getNextChapter()?.page(0)?.removePageAloudSpan()
}
override fun previousPage(): TextPage? = with(dataSource) { override val prevPage: TextPage?
if (pageIndex > 0) { get() = with(dataSource) {
return getCurrentChapter()?.page(pageIndex - 1)?.removePageAloudSpan() if (pageIndex > 0) {
return getCurrentChapter()?.page(pageIndex - 1)?.removePageAloudSpan()
}
return getPreviousChapter()?.lastPage()?.removePageAloudSpan()
} }
return getPreviousChapter()?.lastPage()?.removePageAloudSpan()
}
} }

@ -6,19 +6,29 @@ import kotlin.math.abs
abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageView) { abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageView) {
override fun onScroll( override fun onTouch(event: MotionEvent) {
e1: MotionEvent, when (event.action) {
e2: MotionEvent, MotionEvent.ACTION_MOVE -> {
distanceX: Float, if (isTextSelected) {
distanceY: Float selectText(event)
): Boolean { } else {
onScroll(event)
}
}
}
super.onTouch(event)
}
private fun onScroll(event: MotionEvent) {
//判断是否移动了
if (!isMoved) { if (!isMoved) {
if (abs(distanceX) > abs(distanceY)) { isMoved = abs(startX - event.x) > slop || abs(startY - event.y) > slop
if (distanceX < 0) { if (isMoved) {
if (event.x - startX > 0) {
//如果上一页不存在 //如果上一页不存在
if (!hasPrev()) { if (!hasPrev()) {
noNext = true noNext = true
return true return
} }
setDirection(Direction.PREV) setDirection(Direction.PREV)
setBitmap() setBitmap()
@ -26,21 +36,19 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
//如果不存在表示没有下一页了 //如果不存在表示没有下一页了
if (!hasNext()) { if (!hasNext()) {
noNext = true noNext = true
return true return
} }
setDirection(Direction.NEXT) setDirection(Direction.NEXT)
setBitmap() setBitmap()
} }
isMoved = true
} }
} }
if (isMoved) { if (isMoved) {
isCancel = if (mDirection == Direction.NEXT) distanceX < 0 else distanceX > 0 isCancel = if (mDirection == Direction.NEXT) touchX > lastX else touchX < lastX
isRunning = true isRunning = true
//设置触摸点 //设置触摸点
setTouchPoint(e2.x, e2.y) setTouchPoint(event.x, event.y)
} }
return isMoved
} }
} }

@ -6,15 +6,14 @@ import android.graphics.Canvas
import android.graphics.RectF import android.graphics.RectF
import android.view.GestureDetector import android.view.GestureDetector
import android.view.MotionEvent import android.view.MotionEvent
import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator
import android.widget.Scroller import android.widget.Scroller
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.ui.book.read.page.ContentView import io.legado.app.ui.book.read.page.ContentView
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.screenshot import io.legado.app.utils.screenshot
import kotlin.math.abs import kotlin.math.abs
@ -25,44 +24,36 @@ abstract class PageDelegate(protected val pageView: PageView) :
pageView.width * 0.66f, pageView.height * 0.66f pageView.width * 0.66f, pageView.height * 0.66f
) )
protected val context: Context = pageView.context protected val context: Context = pageView.context
protected val slop = ViewConfiguration.get(context).scaledTouchSlop
//起始点 //起始点
protected var startX: Float = 0f protected var startX: Float = 0f
protected var startY: Float = 0f protected var startY: Float = 0f
//上一个触碰点 //上一个触碰点
protected var lastX: Float = 0f
protected var lastY: Float = 0f protected var lastY: Float = 0f
//触碰点 //触碰点
protected var touchX: Float = 0f protected var touchX: Float = 0f
protected var touchY: Float = 0f protected var touchY: Float = 0f
protected val nextPage: ContentView protected val nextPage: ContentView get() = pageView.nextPage
get() = pageView.nextPage protected val curPage: ContentView get() = pageView.curPage
protected val prevPage: ContentView get() = pageView.prevPage
protected val curPage: ContentView
get() = pageView.curPage
protected val prevPage: ContentView
get() = pageView.prevPage
protected var bitmap: Bitmap? = null protected var bitmap: Bitmap? = null
protected var viewWidth: Int = pageView.width protected var viewWidth: Int = pageView.width
protected var viewHeight: Int = pageView.height protected var viewHeight: Int = pageView.height
//textView在顶端或低端
protected var atTop: Boolean = false
protected var atBottom: Boolean = false
private val snackBar: Snackbar by lazy { private val snackBar: Snackbar by lazy {
Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT) Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT)
} }
private val scroller: Scroller by lazy { private val scroller: Scroller by lazy {
Scroller(pageView.context, FastOutLinearInInterpolator()) Scroller(pageView.context, DecelerateInterpolator())
} }
private val detector: GestureDetector by lazy { private val detector: GestureDetector by lazy {
GestureDetector(pageView.context, this).apply { GestureDetector(pageView.context, this)
setIsLongpressEnabled(context.getPrefBoolean(PreferKey.textSelectAble))
}
} }
var isMoved = false var isMoved = false
@ -74,26 +65,32 @@ abstract class PageDelegate(protected val pageView: PageView) :
var isRunning = false var isRunning = false
var isStarted = false var isStarted = false
var isTextSelected = false var isTextSelected = false
var selectedOnDown = false
var firstLineIndex: Int = 0
var firstCharIndex: Int = 0
open fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) { open fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) {
startX = x startX = x
startY = y startY = y
lastX = x
lastY = y lastY = y
touchX = x touchX = x
touchY = y touchY = y
if (invalidate) { if (invalidate) {
invalidate() pageView.invalidate()
} }
} }
open fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) { open fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) {
lastX = touchX
lastY = touchY lastY = touchY
touchX = x touchX = x
touchY = y touchY = y
if (invalidate) { if (invalidate) {
invalidate() pageView.invalidate()
} }
onScroll() onScroll()
@ -103,10 +100,6 @@ abstract class PageDelegate(protected val pageView: PageView) :
detector.setIsLongpressEnabled(selectAble) detector.setIsLongpressEnabled(selectAble)
} }
protected fun invalidate() {
pageView.invalidate()
}
open fun fling( open fun fling(
startX: Int, startY: Int, velocityX: Int, velocityY: Int, startX: Int, startY: Int, velocityX: Int, velocityY: Int,
minX: Int, maxX: Int, minY: Int, maxY: Int minX: Int, maxX: Int, minY: Int, maxY: Int
@ -114,7 +107,7 @@ abstract class PageDelegate(protected val pageView: PageView) :
scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY) scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY)
isRunning = true isRunning = true
isStarted = true isStarted = true
invalidate() pageView.invalidate()
} }
protected fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int) { protected fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int) {
@ -127,13 +120,13 @@ abstract class PageDelegate(protected val pageView: PageView) :
) )
isRunning = true isRunning = true
isStarted = true isStarted = true
invalidate() pageView.invalidate()
} }
private fun stopScroll() { private fun stopScroll() {
isRunning = false isRunning = false
isStarted = false isStarted = false
invalidate() pageView.invalidate()
bitmap?.recycle() bitmap?.recycle()
bitmap = null bitmap = null
} }
@ -141,7 +134,7 @@ abstract class PageDelegate(protected val pageView: PageView) :
fun setViewSize(width: Int, height: Int) { fun setViewSize(width: Int, height: Int) {
viewWidth = width viewWidth = width
viewHeight = height viewHeight = height
invalidate() pageView.invalidate()
centerRectF.set( centerRectF.set(
width * 0.33f, height * 0.33f, width * 0.33f, height * 0.33f,
width * 0.66f, height * 0.66f width * 0.66f, height * 0.66f
@ -152,7 +145,6 @@ abstract class PageDelegate(protected val pageView: PageView) :
if (scroller.computeScrollOffset()) { if (scroller.computeScrollOffset()) {
setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat()) setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat())
} else if (isStarted) { } else if (isStarted) {
setTouchPoint(scroller.finalX.toFloat(), scroller.finalY.toFloat(), false)
onAnimStop() onAnimStop()
stopScroll() stopScroll()
} }
@ -192,14 +184,13 @@ abstract class PageDelegate(protected val pageView: PageView) :
onAnimStart() onAnimStart()
} }
abstract fun onAnimStart()//scroller start open fun onAnimStart() {}//scroller start
abstract fun onDraw(canvas: Canvas)//绘制 open fun onDraw(canvas: Canvas) {}//绘制
abstract fun onAnimStop()//scroller finish open fun onAnimStop() {}//scroller finish
open fun onScroll() {//移动contentView, slidePage open fun onScroll() {}//移动contentView, slidePage
}
@CallSuper @CallSuper
open fun setDirection(direction: Direction) { open fun setDirection(direction: Direction) {
@ -218,18 +209,17 @@ abstract class PageDelegate(protected val pageView: PageView) :
* 触摸事件处理 * 触摸事件处理
*/ */
@CallSuper @CallSuper
open fun onTouch(event: MotionEvent): Boolean { open fun onTouch(event: MotionEvent) {
if (isStarted) return false if (isStarted) return
if (!detector.onTouchEvent(event)) { if (!detector.onTouchEvent(event)) {
//GestureDetector.onFling小幅移动不会触发,所以要自己判断 //GestureDetector.onFling小幅移动不会触发,所以要自己判断
if (event.action == MotionEvent.ACTION_UP && isMoved) { if (event.action == MotionEvent.ACTION_UP && isMoved) {
if (isTextSelected) { if (selectedOnDown) {
isTextSelected = false selectedOnDown = false
} }
if (!noNext) onAnimStart() if (!noNext) onAnimStart()
} }
} }
return true
} }
/** /**
@ -238,6 +228,8 @@ abstract class PageDelegate(protected val pageView: PageView) :
override fun onDown(e: MotionEvent): Boolean { override fun onDown(e: MotionEvent): Boolean {
if (isTextSelected) { if (isTextSelected) {
curPage.cancelSelect() curPage.cancelSelect()
isTextSelected = false
selectedOnDown = true
} }
//是否移动 //是否移动
isMoved = false isMoved = false
@ -258,8 +250,8 @@ abstract class PageDelegate(protected val pageView: PageView) :
* 单击 * 单击
*/ */
override fun onSingleTapUp(e: MotionEvent): Boolean { override fun onSingleTapUp(e: MotionEvent): Boolean {
if (isTextSelected) { if (selectedOnDown) {
isTextSelected = false selectedOnDown = false
return true return true
} }
val x = e.x val x = e.x
@ -272,15 +264,11 @@ abstract class PageDelegate(protected val pageView: PageView) :
AppConfig.clickAllNext AppConfig.clickAllNext
) { ) {
//设置动画方向 //设置动画方向
if (!hasNext()) { if (!hasNext()) return true
return true
}
setDirection(Direction.NEXT) setDirection(Direction.NEXT)
setBitmap() setBitmap()
} else { } else {
if (!hasPrev()) { if (!hasPrev()) return true
return true
}
setDirection(Direction.PREV) setDirection(Direction.PREV)
setBitmap() setBitmap()
} }
@ -294,7 +282,25 @@ abstract class PageDelegate(protected val pageView: PageView) :
* 长按选择 * 长按选择
*/ */
override fun onLongPress(e: MotionEvent) { override fun onLongPress(e: MotionEvent) {
isTextSelected = curPage.selectText(e) curPage.selectText(e) { lineIndex, charIndex ->
isTextSelected = true
firstLineIndex = lineIndex
firstCharIndex = charIndex
}
}
protected fun selectText(event: MotionEvent) {
curPage.selectText(event) { lineIndex, charIndex ->
if (lineIndex > firstLineIndex
|| (lineIndex == firstLineIndex && charIndex > firstCharIndex)
) {
curPage.selectStartMoveIndex(firstLineIndex, firstCharIndex)
curPage.selectEndMoveIndex(lineIndex, charIndex)
} else {
curPage.selectEndMoveIndex(firstLineIndex, firstCharIndex)
curPage.selectStartMoveIndex(lineIndex, charIndex)
}
}
} }
/** /**

@ -1,6 +1,5 @@
package io.legado.app.ui.book.read.page.delegate package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas
import android.view.MotionEvent import android.view.MotionEvent
import android.view.VelocityTracker import android.view.VelocityTracker
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
@ -21,70 +20,38 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
) )
} }
override fun onDraw(canvas: Canvas) { override fun onScroll() {
curPage.onScroll(lastY - touchY) curPage.onScroll(touchY - lastY)
} }
override fun onAnimStop() { override fun onTouch(event: MotionEvent) {
if (!isCancel) { when (event.action) {
pageView.fillPage(mDirection) MotionEvent.ACTION_DOWN -> {
setStartPoint(event.x, event.y)
abort()
mVelocity.clear()
}
MotionEvent.ACTION_MOVE -> {
if (isTextSelected) {
selectText(event)
} else {
onScroll(event)
}
}
} }
super.onTouch(event)
} }
override fun onDown(e: MotionEvent): Boolean { private fun onScroll(event: MotionEvent) {
abort() mVelocity.addMovement(event)
mVelocity.clear()
mVelocity.addMovement(e)
return super.onDown(e)
}
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
mVelocity.addMovement(e2)
mVelocity.computeCurrentVelocity(velocityDuration) mVelocity.computeCurrentVelocity(velocityDuration)
setTouchPoint(event.x, event.y)
if (!isMoved && abs(distanceX) < abs(distanceY)) { if (!isMoved) {
if (distanceY < 0) { isMoved = abs(startX - event.x) > slop || abs(startY - event.y) > slop
if (atTop) {
//如果上一页不存在
if (!hasPrev()) {
noNext = true
return true
}
setDirection(Direction.PREV)
}
} else {
if (atBottom) {
//如果不存在表示没有下一页了
if (!hasNext()) {
noNext = true
return true
}
setDirection(Direction.NEXT)
}
}
isMoved = true
} }
if (isMoved) { if (isMoved) {
isRunning = true isRunning = true
//设置触摸点
setTouchPoint(e2.x, e2.y)
} }
return isMoved
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
mVelocity.addMovement(e2)
return super.onFling(e1, e2, velocityX, velocityY)
} }
override fun onDestroy() { override fun onDestroy() {

@ -3,6 +3,6 @@ package io.legado.app.ui.book.read.page.entities
data class TextChar( data class TextChar(
val charData: String, val charData: String,
var selected: Boolean = false, var selected: Boolean = false,
val leftBottomPosition: TextPoint, val start: Float,
val rightTopPosition: TextPoint val end: Float
) )

@ -4,6 +4,7 @@ data class TextLine(
var text: String = "", var text: String = "",
val textChars: ArrayList<TextChar> = arrayListOf(), val textChars: ArrayList<TextChar> = arrayListOf(),
var lineTop: Float = 0f, var lineTop: Float = 0f,
var lineBase: Float = 0f,
var lineBottom: Float = 0f, var lineBottom: Float = 0f,
val isTitle: Boolean = false, val isTitle: Boolean = false,
var isReadAloud: Boolean = false var isReadAloud: Boolean = false

@ -1,7 +1,10 @@
package io.legado.app.ui.book.read.page.entities package io.legado.app.ui.book.read.page.entities
import android.text.Layout
import android.text.StaticLayout
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.ui.book.read.page.ChapterProvider
data class TextPage( data class TextPage(
var index: Int = 0, var index: Int = 0,
@ -14,6 +17,40 @@ data class TextPage(
var height: Int = 0 var height: Int = 0
) { ) {
@Suppress("DEPRECATION")
fun format(): TextPage {
if (textLines.isEmpty() && ChapterProvider.visibleWidth > 0) {
val layout = StaticLayout(
text, ChapterProvider.contentPaint, ChapterProvider.visibleWidth,
Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false
)
var y = (ChapterProvider.visibleHeight - layout.height) / 2f
if (y < 0) y = 0f
for (lineIndex in 0 until layout.lineCount) {
val textLine = TextLine()
textLine.lineTop = (ChapterProvider.paddingTop + y -
(layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)))
textLine.lineBase = (ChapterProvider.paddingTop + y -
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex)))
textLine.lineBottom =
textLine.lineBase + ChapterProvider.contentPaint.fontMetrics.descent
var x = (ChapterProvider.visibleWidth - layout.getLineMax(lineIndex)) / 2
textLine.text =
text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex))
for (i in textLine.text.indices) {
val char = textLine.text[i].toString()
val cw = StaticLayout.getDesiredWidth(char, ChapterProvider.contentPaint)
val x1 = x + cw
textLine.textChars.add(TextChar(charData = char, start = x, end = x1))
x = x1
}
textLines.add(textLine)
}
height = ChapterProvider.visibleHeight
}
return this
}
fun removePageAloudSpan(): TextPage { fun removePageAloudSpan(): TextPage {
textLines.forEach { textLine -> textLines.forEach { textLine ->
textLine.isReadAloud = false textLine.isReadAloud = false

@ -1,6 +0,0 @@
package io.legado.app.ui.book.read.page.entities
data class TextPoint(
val x: Float,
val y: Float
)

@ -30,6 +30,7 @@ import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_book_source.* import kotlinx.android.synthetic.main.activity_book_source.*
@ -109,7 +110,7 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
private fun initRecyclerView() { private fun initRecyclerView() {
ATH.applyEdgeEffectColor(recycler_view) ATH.applyEdgeEffectColor(recycler_view)
recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(this))
adapter = BookSourceAdapter(this, this) adapter = BookSourceAdapter(this, this)
recycler_view.adapter = adapter recycler_view.adapter = adapter
val itemTouchCallback = ItemTouchCallback() val itemTouchCallback = ItemTouchCallback()

@ -22,7 +22,11 @@ import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.dialogs.yesButton
import io.legado.app.utils.* import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint
import io.legado.app.utils.getViewModelOfActivity
import io.legado.app.utils.requestInputMethod
import io.legado.app.utils.splitNotBlank
import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_edit_text.view.*
import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.dialog_recycler_view.*
import kotlinx.android.synthetic.main.item_group_manage.view.* import kotlinx.android.synthetic.main.item_group_manage.view.*
@ -60,7 +64,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
adapter = GroupAdapter(requireContext()) adapter = GroupAdapter(requireContext())
recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter recycler_view.adapter = adapter
App.db.bookSourceDao().liveGroup().observe(viewLifecycleOwner, Observer { App.db.bookSourceDao().liveGroup().observe(viewLifecycleOwner, Observer {
val groups = linkedSetOf<String>() val groups = linkedSetOf<String>()

@ -18,8 +18,8 @@ import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getVerticalDivider
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.putPrefBoolean import io.legado.app.utils.putPrefBoolean
import kotlinx.android.synthetic.main.dialog_change_source.* import kotlinx.android.synthetic.main.dialog_change_source.*
@ -92,7 +92,7 @@ class ChangeSourceDialog : DialogFragment(),
private fun initRecyclerView() { private fun initRecyclerView() {
adapter = ChangeSourceAdapter(requireContext(), this) adapter = ChangeSourceAdapter(requireContext(), this)
recycler_view.layoutManager = LinearLayoutManager(context) recycler_view.layoutManager = LinearLayoutManager(context)
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter recycler_view.adapter = adapter
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {

@ -14,7 +14,7 @@ import io.legado.app.R
import io.legado.app.base.VMBaseFragment import io.legado.app.base.VMBaseFragment
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.utils.getVerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.getViewModelOfActivity
import kotlinx.android.synthetic.main.fragment_bookmark.* import kotlinx.android.synthetic.main.fragment_bookmark.*
@ -38,7 +38,7 @@ class BookmarkFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragment_
ATH.applyEdgeEffectColor(recycler_view) ATH.applyEdgeEffectColor(recycler_view)
adapter = BookmarkAdapter(this) adapter = BookmarkAdapter(this)
recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter recycler_view.adapter = adapter
} }

@ -15,7 +15,7 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.ui.widget.recycler.UpLinearLayoutManager import io.legado.app.ui.widget.recycler.UpLinearLayoutManager
import io.legado.app.utils.getVerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.getViewModelOfActivity
import io.legado.app.utils.observeEvent import io.legado.app.utils.observeEvent
import kotlinx.android.synthetic.main.fragment_chapter_list.* import kotlinx.android.synthetic.main.fragment_chapter_list.*
@ -48,7 +48,7 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
adapter = ChapterListAdapter(requireContext(), this) adapter = ChapterListAdapter(requireContext(), this)
mLayoutManager = UpLinearLayoutManager(requireContext()) mLayoutManager = UpLinearLayoutManager(requireContext())
recycler_view.layoutManager = mLayoutManager recycler_view.layoutManager = mLayoutManager
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter recycler_view.adapter = adapter
} }

@ -10,7 +10,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.widget.recycler.LoadMoreView import io.legado.app.ui.widget.recycler.LoadMoreView
import io.legado.app.utils.getVerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.activity_explore_show.* import kotlinx.android.synthetic.main.activity_explore_show.*
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
@ -34,7 +34,7 @@ class ExploreShowActivity : VMBaseActivity<ExploreShowViewModel>(R.layout.activi
private fun initRecyclerView() { private fun initRecyclerView() {
adapter = ExploreShowAdapter(this, this) adapter = ExploreShowAdapter(this, this)
recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(this))
recycler_view.adapter = adapter recycler_view.adapter = adapter
loadMoreView = LoadMoreView(this) loadMoreView = LoadMoreView(this)
adapter.addFooterView(loadMoreView) adapter.addFooterView(loadMoreView)

@ -15,7 +15,11 @@ import io.legado.app.R
import io.legado.app.constant.Theme import io.legado.app.constant.Theme
import io.legado.app.ui.filechooser.adapter.FileAdapter import io.legado.app.ui.filechooser.adapter.FileAdapter
import io.legado.app.ui.filechooser.adapter.PathAdapter import io.legado.app.ui.filechooser.adapter.PathAdapter
import io.legado.app.utils.* import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.FileUtils
import io.legado.app.utils.applyTint
import io.legado.app.utils.gone
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.dialog_file_chooser.* import kotlinx.android.synthetic.main.dialog_file_chooser.*
@ -133,7 +137,7 @@ class FileChooserDialog : DialogFragment(),
fileAdapter = FileAdapter(requireContext(), this) fileAdapter = FileAdapter(requireContext(), this)
pathAdapter = PathAdapter(requireContext(), this) pathAdapter = PathAdapter(requireContext(), this)
rv_file.addItemDecoration(rv_file.getVerticalDivider()) rv_file.addItemDecoration(VerticalDivider(requireContext()))
rv_file.layoutManager = LinearLayoutManager(activity) rv_file.layoutManager = LinearLayoutManager(activity)
rv_file.adapter = fileAdapter rv_file.adapter = fileAdapter

@ -22,7 +22,11 @@ import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.dialogs.yesButton
import io.legado.app.utils.* import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint
import io.legado.app.utils.getViewModelOfActivity
import io.legado.app.utils.requestInputMethod
import io.legado.app.utils.splitNotBlank
import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_edit_text.view.*
import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.dialog_recycler_view.*
import kotlinx.android.synthetic.main.item_group_manage.view.* import kotlinx.android.synthetic.main.item_group_manage.view.*
@ -60,7 +64,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
adapter = GroupAdapter(requireContext()) adapter = GroupAdapter(requireContext())
recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter recycler_view.adapter = adapter
App.db.replaceRuleDao().liveGroup().observe(viewLifecycleOwner, Observer { App.db.replaceRuleDao().liveGroup().observe(viewLifecycleOwner, Observer {
val groups = linkedSetOf<String>() val groups = linkedSetOf<String>()

@ -28,6 +28,7 @@ import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.replacerule.edit.ReplaceEditDialog import io.legado.app.ui.replacerule.edit.ReplaceEditDialog
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_replace_rule.* import kotlinx.android.synthetic.main.activity_replace_rule.*
@ -77,7 +78,7 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.layoutManager = LinearLayoutManager(this)
adapter = ReplaceRuleAdapter(this, this) adapter = ReplaceRuleAdapter(this, this)
recycler_view.adapter = adapter recycler_view.adapter = adapter
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(this))
val itemTouchCallback = ItemTouchCallback() val itemTouchCallback = ItemTouchCallback()
itemTouchCallback.onItemTouchCallbackListener = adapter itemTouchCallback.onItemTouchCallbackListener = adapter
itemTouchCallback.isCanDrag = true itemTouchCallback.isCanDrag = true

@ -15,7 +15,7 @@ import io.legado.app.lib.theme.ATH
import io.legado.app.ui.rss.read.ReadRssActivity import io.legado.app.ui.rss.read.ReadRssActivity
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.ui.widget.recycler.LoadMoreView import io.legado.app.ui.widget.recycler.LoadMoreView
import io.legado.app.utils.getVerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.activity_rss_artivles.* import kotlinx.android.synthetic.main.activity_rss_artivles.*
import kotlinx.android.synthetic.main.view_load_more.view.* import kotlinx.android.synthetic.main.view_load_more.view.*
@ -70,7 +70,7 @@ class RssArticlesActivity : VMBaseActivity<RssArticlesViewModel>(R.layout.activi
private fun initView() { private fun initView() {
ATH.applyEdgeEffectColor(recycler_view) ATH.applyEdgeEffectColor(recycler_view)
recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(this))
adapter = RssArticlesAdapter(this, this) adapter = RssArticlesAdapter(this, this)
recycler_view.adapter = adapter recycler_view.adapter = adapter
loadMoreView = LoadMoreView(this) loadMoreView = LoadMoreView(this)

@ -10,7 +10,7 @@ import io.legado.app.base.BaseActivity
import io.legado.app.data.entities.RssStar import io.legado.app.data.entities.RssStar
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.ui.rss.read.ReadRssActivity import io.legado.app.ui.rss.read.ReadRssActivity
import io.legado.app.utils.getVerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import kotlinx.android.synthetic.main.view_refresh_recycler.* import kotlinx.android.synthetic.main.view_refresh_recycler.*
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
@ -29,7 +29,7 @@ class RssFavoritesActivity : BaseActivity(R.layout.activity_rss_favorites),
private fun initView() { private fun initView() {
ATH.applyEdgeEffectColor(recycler_view) ATH.applyEdgeEffectColor(recycler_view)
recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(this))
adapter = RssFavoritesAdapter(this, this) adapter = RssFavoritesAdapter(this, this)
recycler_view.adapter = adapter recycler_view.adapter = adapter
} }

@ -22,7 +22,11 @@ import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.dialogs.yesButton
import io.legado.app.utils.* import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint
import io.legado.app.utils.getViewModelOfActivity
import io.legado.app.utils.requestInputMethod
import io.legado.app.utils.splitNotBlank
import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_edit_text.view.*
import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.dialog_recycler_view.*
import kotlinx.android.synthetic.main.item_group_manage.view.* import kotlinx.android.synthetic.main.item_group_manage.view.*
@ -60,7 +64,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
adapter = GroupAdapter(requireContext()) adapter = GroupAdapter(requireContext())
recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter recycler_view.adapter = adapter
App.db.rssSourceDao().liveGroup().observe(viewLifecycleOwner, Observer { App.db.rssSourceDao().liveGroup().observe(viewLifecycleOwner, Observer {
val groups = linkedSetOf<String>() val groups = linkedSetOf<String>()

@ -29,6 +29,7 @@ import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_rss_source.* import kotlinx.android.synthetic.main.activity_rss_source.*
@ -104,7 +105,7 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
private fun initRecyclerView() { private fun initRecyclerView() {
ATH.applyEdgeEffectColor(recycler_view) ATH.applyEdgeEffectColor(recycler_view)
recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.addItemDecoration(recycler_view.getVerticalDivider()) recycler_view.addItemDecoration(VerticalDivider(this))
adapter = RssSourceAdapter(this, this) adapter = RssSourceAdapter(this, this)
recycler_view.adapter = adapter recycler_view.adapter = adapter
val itemTouchCallback = ItemTouchCallback() val itemTouchCallback = ItemTouchCallback()

@ -12,6 +12,7 @@ class LabelsBar(context: Context, attrs: AttributeSet?) : LinearLayout(context,
private val unUsedViews = arrayListOf<TextView>() private val unUsedViews = arrayListOf<TextView>()
private val usedViews = arrayListOf<TextView>() private val usedViews = arrayListOf<TextView>()
var textSize = 12f
fun setLabels(labels: Array<String>) { fun setLabels(labels: Array<String>) {
clear() clear()
@ -51,6 +52,7 @@ class LabelsBar(context: Context, attrs: AttributeSet?) : LinearLayout(context,
unUsedViews.removeAt(unUsedViews.lastIndex) unUsedViews.removeAt(unUsedViews.lastIndex)
} }
} }
tv.textSize = textSize
tv.text = label tv.text = label
addView(tv) addView(tv)
} }

@ -12,8 +12,8 @@ import com.google.android.material.appbar.AppBarLayout
import io.legado.app.R import io.legado.app.R
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.utils.activity import io.legado.app.utils.activity
import io.legado.app.utils.getNavigationBarHeight import io.legado.app.utils.navigationBarHeight
import io.legado.app.utils.getStatusBarHeight import io.legado.app.utils.statusBarHeight
import org.jetbrains.anko.backgroundColor import org.jetbrains.anko.backgroundColor
import org.jetbrains.anko.bottomPadding import org.jetbrains.anko.bottomPadding
import org.jetbrains.anko.topPadding import org.jetbrains.anko.topPadding
@ -132,11 +132,11 @@ class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, a
} }
if (a.getBoolean(R.styleable.TitleBar_fitStatusBar, true)) { if (a.getBoolean(R.styleable.TitleBar_fitStatusBar, true)) {
topPadding = context.getStatusBarHeight() topPadding = context.statusBarHeight
} }
if (a.getBoolean(R.styleable.TitleBar_fitNavigationBar, false)) { if (a.getBoolean(R.styleable.TitleBar_fitNavigationBar, false)) {
bottomPadding = context.getNavigationBarHeight() bottomPadding = context.navigationBarHeight
} }
backgroundColor = context.primaryColor backgroundColor = context.primaryColor

@ -0,0 +1,169 @@
package io.legado.app.ui.widget.recycler
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.View
import android.widget.LinearLayout
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.roundToInt
/**
* 不画最后一条分隔线
*/
class DividerNoLast(context: Context, orientation: Int) :
RecyclerView.ItemDecoration() {
companion object {
const val HORIZONTAL = LinearLayout.HORIZONTAL
const val VERTICAL = LinearLayout.VERTICAL
}
private val tag = "DividerItem"
private val attrs = intArrayOf(android.R.attr.listDivider)
private var mDivider: Drawable? = null
/**
* Current orientation. Either [.HORIZONTAL] or [.VERTICAL].
*/
private var mOrientation = 0
private val mBounds = Rect()
init {
val a = context.obtainStyledAttributes(attrs)
mDivider = a.getDrawable(0)
if (mDivider == null) {
Log.w(
tag, "@android:attr/listDivider was not set in the theme used for this "
+ "DividerItemDecoration. Please set that attribute all call setDrawable()"
)
}
a.recycle()
setOrientation(orientation)
}
/**
* Sets the orientation for this divider. This should be called if
* [RecyclerView.LayoutManager] changes orientation.
*
* @param orientation [.HORIZONTAL] or [.VERTICAL]
*/
fun setOrientation(orientation: Int) {
require(!(orientation != HORIZONTAL && orientation != VERTICAL)) { "Invalid orientation. It should be either HORIZONTAL or VERTICAL" }
mOrientation = orientation
}
/**
* Sets the [Drawable] for this divider.
*
* @param drawable Drawable that should be used as a divider.
*/
fun setDrawable(drawable: Drawable) {
requireNotNull(drawable) { "Drawable cannot be null." }
mDivider = drawable
}
/**
* @return the [Drawable] for this divider.
*/
fun getDrawable(): Drawable? {
return mDivider
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
if (parent.layoutManager == null || mDivider == null) {
return
}
if (mOrientation == VERTICAL) {
drawVertical(c, parent)
} else {
drawHorizontal(c, parent)
}
}
private fun drawVertical(
canvas: Canvas,
parent: RecyclerView
) {
canvas.save()
val left: Int
val right: Int
if (parent.clipToPadding) {
left = parent.paddingLeft
right = parent.width - parent.paddingRight
canvas.clipRect(
left, parent.paddingTop, right,
parent.height - parent.paddingBottom
)
} else {
left = 0
right = parent.width
}
val childCount = parent.childCount
for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i)
parent.getDecoratedBoundsWithMargins(child, mBounds)
val bottom = mBounds.bottom + child.translationY.roundToInt()
val top = bottom - mDivider!!.intrinsicHeight
mDivider!!.setBounds(left, top, right, bottom)
mDivider!!.draw(canvas)
}
canvas.restore()
}
private fun drawHorizontal(canvas: Canvas, parent: RecyclerView) {
canvas.save()
val top: Int
val bottom: Int
if (parent.clipToPadding) {
top = parent.paddingTop
bottom = parent.height - parent.paddingBottom
canvas.clipRect(
parent.paddingLeft, top,
parent.width - parent.paddingRight, bottom
)
} else {
top = 0
bottom = parent.height
}
val childCount = parent.childCount
for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i)
parent.layoutManager!!.getDecoratedBoundsWithMargins(child, mBounds)
val right = mBounds.right + child.translationX.roundToInt()
val left = right - mDivider!!.intrinsicWidth
mDivider!!.setBounds(left, top, right, bottom)
mDivider!!.draw(canvas)
}
canvas.restore()
}
override fun getItemOffsets(
outRect: Rect, view: View, parent: RecyclerView,
state: RecyclerView.State
) {
if (mDivider == null) {
outRect[0, 0, 0] = 0
return
}
if (mOrientation == VERTICAL) {
outRect[0, 0, 0] = mDivider!!.intrinsicHeight
} else {
val childAdapterPosition = parent.getChildAdapterPosition(view)
val lastCount = parent.adapter!!.itemCount - 1
//如果不是最后一条 正常赋值 如果是最后一条 赋值为0
if (childAdapterPosition != lastCount) {
outRect[0, 0, mDivider!!.intrinsicWidth] = 0
} else {
outRect[0, 0, 0] = 0
}
}
}
}

@ -1,15 +1,16 @@
package io.legado.app.utils package io.legado.app.ui.widget.recycler
import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
class VerticalDivider(context: Context) : DividerItemDecoration(context, VERTICAL) {
fun RecyclerView.getVerticalDivider(): DividerItemDecoration { init {
return DividerItemDecoration(context, DividerItemDecoration.VERTICAL).apply {
ContextCompat.getDrawable(context, R.drawable.ic_divider)?.let { ContextCompat.getDrawable(context, R.drawable.ic_divider)?.let {
this.setDrawable(it) this.setDrawable(it)
} }
} }
} }

@ -1,3 +1,4 @@
@file:Suppress("unused")
package io.legado.app.utils package io.legado.app.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -30,7 +31,6 @@ fun Context.getPrefBoolean(key: String, defValue: Boolean = false) =
fun Context.putPrefBoolean(key: String, value: Boolean = false) = fun Context.putPrefBoolean(key: String, value: Boolean = false) =
defaultSharedPreferences.edit { putBoolean(key, value) } defaultSharedPreferences.edit { putBoolean(key, value) }
fun Context.getPrefInt(key: String, defValue: Int = 0) = fun Context.getPrefInt(key: String, defValue: Int = 0) =
defaultSharedPreferences.getInt(key, defValue) defaultSharedPreferences.getInt(key, defValue)
@ -69,24 +69,27 @@ fun Context.getCompatColorStateList(@ColorRes id: Int): ColorStateList? =
/** /**
* 系统息屏时间 * 系统息屏时间
*/ */
fun Context.getScreenOffTime(): Int { val Context.sysScreenOffTime: Int
var screenOffTime = 0 get() {
try { var screenOffTime = 0
screenOffTime = Settings.System.getInt(contentResolver, Settings.System.SCREEN_OFF_TIMEOUT) try {
} catch (e: Exception) { screenOffTime = Settings.System.getInt(contentResolver, Settings.System.SCREEN_OFF_TIMEOUT)
e.printStackTrace() } catch (e: Exception) {
} e.printStackTrace()
return screenOffTime }
return screenOffTime
} }
fun Context.getStatusBarHeight(): Int { val Context.statusBarHeight: Int
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") get() {
return resources.getDimensionPixelSize(resourceId) val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
return resources.getDimensionPixelSize(resourceId)
} }
fun Context.getNavigationBarHeight(): Int { val Context.navigationBarHeight: Int
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android") get() {
return resources.getDimensionPixelSize(resourceId) val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
return resources.getDimensionPixelSize(resourceId)
} }
fun Context.shareText(title: String, text: String) { fun Context.shareText(title: String, text: String) {
@ -149,10 +152,11 @@ fun Context.sysIsDarkMode(): Boolean {
/** /**
* 获取电量 * 获取电量
*/ */
fun Context.getBettery(): Int { val Context.sysBattery: Int
val iFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) get() {
val batteryStatus = registerReceiver(null, iFilter) val iFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
return batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1 val batteryStatus = registerReceiver(null, iFilter)
return batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
} }
fun Context.openUrl(url: String) { fun Context.openUrl(url: String) {

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"> <inset xmlns:android="http://schemas.android.com/apk/res/android">
<shape> <shape>
<size android:width="1dp" /> <size android:width="0.5dp" />
<!--分割线的颜色--> <!--分割线的颜色-->
<solid android:color="@color/bg_divider_line" /> <solid android:color="@color/bg_divider_line" />
</shape> </shape>

@ -154,8 +154,8 @@
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:tooltipText="@string/other_setting" android:tooltipText="@string/other_aloud_setting"
android:contentDescription="@string/other_setting" android:contentDescription="@string/other_aloud_setting"
android:src="@drawable/ic_settings" android:src="@drawable/ic_settings"
android:tint="@color/tv_text_default" android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" /> tools:ignore="UnusedAttribute" />

@ -8,6 +8,7 @@
android:orientation="vertical"> android:orientation="vertical">
<io.legado.app.ui.widget.text.AccentTextView <io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/tv_header_padding"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="10dp" android:paddingBottom="10dp"

@ -626,4 +626,5 @@
<string name="more_menu">更多菜单</string> <string name="more_menu">更多菜单</string>
<string name="reduce"></string> <string name="reduce"></string>
<string name="plus"></string> <string name="plus"></string>
<string name="other_aloud_setting">其它朗读设置</string>
</resources> </resources>

Loading…
Cancel
Save