Merge pull request #22 from gedoor/master

up
pull/379/head
口口吕 5 years ago committed by GitHub
commit e3cd2ce3d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/build.gradle
  2. 8
      app/proguard-rules.pro
  3. 6
      app/src/main/java/io/legado/app/App.kt
  4. 2
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  5. 14
      app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt
  6. 13
      app/src/main/java/io/legado/app/help/BookHelp.kt
  7. 3
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  8. 5
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  9. 18
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  10. 3
      app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt
  11. 24
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  12. 6
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  13. 19
      app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt
  14. 4
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  15. 2
      app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt
  16. 4
      app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt
  17. 8
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  18. 10
      app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt
  19. 28
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  20. 25
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  21. 24
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt
  22. 3
      app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt
  23. 2
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  24. 2
      app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt
  25. 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt
  26. 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt
  27. 9
      app/src/main/java/io/legado/app/utils/RealPathUtil.kt
  28. 11
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  29. 2
      build.gradle
  30. 1
      gradle.properties

@ -55,7 +55,7 @@ android {
signingConfig signingConfigs.myConfig signingConfig signingConfigs.myConfig
} }
applicationIdSuffix '.release' applicationIdSuffix '.release'
minifyEnabled false minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
debug { debug {
@ -106,7 +106,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
//fireBase //fireBase
implementation 'com.google.firebase:firebase-core:17.2.2' implementation 'com.google.firebase:firebase-core:17.2.3'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
//androidX //androidX

@ -154,6 +154,8 @@
-keep class **.analyzeRule.**{*;} -keep class **.analyzeRule.**{*;}
# 保持web类 # 保持web类
-keep class **.web.**{*;} -keep class **.web.**{*;}
#数据类
-keep class **.data.**{*;}
-dontwarn rx.** -dontwarn rx.**
@ -166,6 +168,7 @@
-dontnote org.python.core.** -dontnote org.python.core.**
-dontwarn com.hwangjr.rxbus.** -dontwarn com.hwangjr.rxbus.**
-dontwarn okhttp3.** -dontwarn okhttp3.**
-dontwarn org.conscrypt.**
-keep class retrofit2.**{*;} -keep class retrofit2.**{*;}
-keep class okhttp3.**{*;} -keep class okhttp3.**{*;}
@ -206,8 +209,6 @@
-keep class javax.script.** { *; } -keep class javax.script.** { *; }
-keep class com.sun.script.javascript.** { *; } -keep class com.sun.script.javascript.** { *; }
-keep class org.mozilla.javascript.** { *; } -keep class org.mozilla.javascript.** { *; }
-dontwarn org.mozilla.javascript.**
-dontwarn sun.**
###EPUB ###EPUB
-dontwarn nl.siegmann.epublib.** -dontwarn nl.siegmann.epublib.**
@ -222,9 +223,6 @@
-keepclassmembers class * { -keepclassmembers class * {
public <init> (org.json.JSONObject); public <init> (org.json.JSONObject);
} }
-keep public class com.kunfei.bookshelf.R$*{
public static final int *;
}
-keepclassmembers enum * { -keepclassmembers enum * {
public static **[] values(); public static **[] values();
public static ** valueOf(java.lang.String); public static ** valueOf(java.lang.String);

@ -48,12 +48,8 @@ class App : Application() {
versionCode = it.versionCode versionCode = it.versionCode
versionName = it.versionName versionName = it.versionName
} }
if (!ThemeStore.isConfigured(this, versionCode)) applyTheme()
initNightMode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId()
applyDayNight()
LiveEventBus.get() LiveEventBus.get()
.config() .config()
.supportBroadcast(this) .supportBroadcast(this)

@ -32,7 +32,7 @@ abstract class AppDatabase : RoomDatabase() {
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.addCallback(object : Callback() { .addCallback(object : Callback() {
override fun onDestructiveMigration(db: SupportSQLiteDatabase) { override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
GlobalScope.launch { Restore.restore(Backup.backupPath) } GlobalScope.launch { Restore.restoreDatabase(Backup.backupPath) }
} }
}) })
.build() .build()

@ -33,14 +33,20 @@ interface ReplaceRuleDao {
fun findByIds(vararg ids: Long): List<ReplaceRule> fun findByIds(vararg ids: Long): List<ReplaceRule>
@Query( @Query(
"""SELECT * FROM replace_rules WHERE isEnabled = 1 """
AND (scope LIKE '%' || :scope || '%' or scope is null or scope = '')""" SELECT * FROM replace_rules WHERE isEnabled = 1
AND (scope LIKE '%' || :scope || '%' or scope is null or scope = '')
order by sortOrder
"""
) )
fun findEnabledByScope(scope: String): List<ReplaceRule> fun findEnabledByScope(scope: String): List<ReplaceRule>
@Query( @Query(
"""SELECT * FROM replace_rules WHERE isEnabled = 1 """
AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')""" SELECT * FROM replace_rules WHERE isEnabled = 1
AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')
order by sortOrder
"""
) )
fun findEnabledByScope(name: String, origin: String): List<ReplaceRule> fun findEnabledByScope(name: String, origin: String): List<ReplaceRule>

@ -11,7 +11,10 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import io.legado.app.model.localBook.AnalyzeTxtFile import io.legado.app.model.localBook.AnalyzeTxtFile
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.JaccardSimilarity import org.apache.commons.text.similarity.JaccardSimilarity
import org.jetbrains.anko.toast
import java.io.File import java.io.File
import kotlin.math.min import kotlin.math.min
@ -262,7 +265,7 @@ object BookHelp {
} }
} }
fun disposeContent( suspend fun disposeContent(
title: String, title: String,
name: String, name: String,
origin: String?, origin: String?,
@ -272,14 +275,20 @@ object BookHelp {
var c = content var c = content
if (enableReplace) { if (enableReplace) {
upReplaceRules(name, origin) upReplaceRules(name, origin)
for (item in replaceRules) { replaceRules.forEach { item ->
item.pattern.let { item.pattern.let {
if (it.isNotEmpty()) { if (it.isNotEmpty()) {
try {
c = if (item.isRegex) { c = if (item.isRegex) {
c.replace(it.toRegex(), item.replacement) c.replace(it.toRegex(), item.replacement)
} else { } else {
c.replace(it, item.replacement) c.replace(it, item.replacement)
} }
} catch (e: Exception) {
withContext(Main) {
App.INSTANCE.toast("${item.name}替换出错")
}
}
} }
} }
} }

@ -1,13 +1,14 @@
package io.legado.app.help package io.legado.app.help
import android.util.Base64 import android.util.Base64
import androidx.annotation.Keep
import io.legado.app.constant.AppConst.DATE_FORMAT import io.legado.app.constant.AppConst.DATE_FORMAT
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.EncoderUtils import io.legado.app.utils.EncoderUtils
import io.legado.app.utils.MD5Utils import io.legado.app.utils.MD5Utils
import java.util.* import java.util.*
@Keep
@Suppress("unused") @Suppress("unused")
interface JsExtensions { interface JsExtensions {

@ -4,6 +4,7 @@ import android.graphics.Color
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.annotation.Keep
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.constant.PreferKey
@ -15,6 +16,7 @@ import java.io.File
/** /**
* 阅读界面配置 * 阅读界面配置
*/ */
@Keep
object ReadBookConfig { object ReadBookConfig {
const val readConfigFileName = "readConfig.json" const val readConfigFileName = "readConfig.json"
private val configFilePath = private val configFilePath =
@ -236,6 +238,7 @@ object ReadBookConfig {
if (shareLayout) shareConfig.footerPaddingTop = value if (shareLayout) shareConfig.footerPaddingTop = value
else durConfig.footerPaddingTop = value else durConfig.footerPaddingTop = value
@Keep
class Config( class Config(
private var bgStr: String = "#EEEEEE",//白天背景 private var bgStr: String = "#EEEEEE",//白天背景
private var bgStrNight: String = "#000000",//夜间背景 private var bgStrNight: String = "#000000",//夜间背景
@ -247,7 +250,7 @@ object ReadBookConfig {
private var textColorNight: String = "#ADADAD",//夜间文字颜色 private var textColorNight: String = "#ADADAD",//夜间文字颜色
var textBold: Boolean = false,//是否粗体字 var textBold: Boolean = false,//是否粗体字
var textSize: Int = 20,//文字大小 var textSize: Int = 20,//文字大小
var letterSpacing: Float = 1f,//字间距 var letterSpacing: Float = 0.5f,//字间距
var lineSpacingExtra: Int = 12,//行间距 var lineSpacingExtra: Int = 12,//行间距
var paragraphSpacing: Int = 12,//段距 var paragraphSpacing: Int = 12,//段距
var titleCenter: Boolean = true,//标题居中 var titleCenter: Boolean = true,//标题居中

@ -8,6 +8,7 @@ import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.Option import com.jayway.jsonpath.Option
import com.jayway.jsonpath.ParseContext import com.jayway.jsonpath.ParseContext
import io.legado.app.App import io.legado.app.App
import io.legado.app.BuildConfig
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.* import io.legado.app.data.entities.*
import io.legado.app.help.LauncherIconHelp import io.legado.app.help.LauncherIconHelp
@ -16,6 +17,7 @@ import io.legado.app.service.help.ReadBook
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.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.anko.defaultSharedPreferences import org.jetbrains.anko.defaultSharedPreferences
import java.io.File import java.io.File
@ -60,10 +62,11 @@ object Restore {
} }
} }
} }
restore(Backup.backupPath) restoreDatabase()
restoreConfig()
} }
suspend fun restore(path: String) { suspend fun restoreDatabase(path: String = Backup.backupPath) {
withContext(IO) { withContext(IO) {
fileToListT<Book>(path, "bookshelf.json")?.let { fileToListT<Book>(path, "bookshelf.json")?.let {
App.db.bookDao().insert(*it.toTypedArray()) App.db.bookDao().insert(*it.toTypedArray())
@ -83,11 +86,16 @@ object Restore {
fileToListT<ReplaceRule>(path, "replaceRule.json")?.let { fileToListT<ReplaceRule>(path, "replaceRule.json")?.let {
App.db.replaceRuleDao().insert(*it.toTypedArray()) App.db.replaceRuleDao().insert(*it.toTypedArray())
} }
}
}
suspend fun restoreConfig(path: String = Backup.backupPath) {
withContext(IO) {
try { try {
val file = val file =
FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.readConfigFileName) FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.readConfigFileName)
val configFile = val configFile =
File(App.INSTANCE.filesDir.absolutePath + File.separator + ReadBookConfig.readConfigFileName) FileUtils.getFile(App.INSTANCE.filesDir, ReadBookConfig.readConfigFileName)
if (file.exists()) { if (file.exists()) {
file.copyTo(configFile, true) file.copyTo(configFile, true)
ReadBookConfig.upConfig() ReadBookConfig.upConfig()
@ -119,8 +127,12 @@ object Restore {
ChapterProvider.upStyle() ChapterProvider.upStyle()
ReadBook.loadContent() ReadBook.loadContent()
} }
withContext(Main) {
App.INSTANCE.applyDayNight()
if (!BuildConfig.DEBUG)
LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon)) LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon))
} }
}
private inline fun <reified T> fileToListT(path: String, fileName: String): List<T>? { private inline fun <reified T> fileToListT(path: String, fileName: String): List<T>? {
try { try {

@ -95,7 +95,8 @@ object WebDavHelp {
webDav.downloadTo(zipFilePath, true) webDav.downloadTo(zipFilePath, true)
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
ZipUtils.unzipFile(zipFilePath, Backup.backupPath) ZipUtils.unzipFile(zipFilePath, Backup.backupPath)
Restore.restore(Backup.backupPath) Restore.restoreDatabase()
Restore.restoreConfig()
} }
} }
}.onSuccess { }.onSuccess {

@ -69,11 +69,11 @@ object ReadBook {
nextTextChapter = null nextTextChapter = null
book?.let { book?.let {
if (curTextChapter == null) { if (curTextChapter == null) {
loadContent(durChapterIndex) loadContent(durChapterIndex, upContent)
} else if (upContent) { } else if (upContent) {
callBack?.upContent() callBack?.upContent()
} }
loadContent(durChapterIndex.plus(1)) loadContent(durChapterIndex.plus(1), upContent)
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
for (i in 2..10) { for (i in 2..10) {
delay(100) delay(100)
@ -99,11 +99,11 @@ object ReadBook {
prevTextChapter = null prevTextChapter = null
book?.let { book?.let {
if (curTextChapter == null) { if (curTextChapter == null) {
loadContent(durChapterIndex) loadContent(durChapterIndex, upContent)
} else if (upContent) { } else if (upContent) {
callBack?.upContent() callBack?.upContent()
} }
loadContent(durChapterIndex.minus(1)) loadContent(durChapterIndex.minus(1), upContent)
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
for (i in -5..-2) { for (i in -5..-2) {
delay(100) delay(100)
@ -190,13 +190,13 @@ object ReadBook {
loadContent(durChapterIndex - 1) loadContent(durChapterIndex - 1)
} }
fun loadContent(index: Int) { fun loadContent(index: Int, upContent: Boolean = true) {
book?.let { book -> book?.let { book ->
if (addLoading(index)) { if (addLoading(index)) {
Coroutine.async { Coroutine.async {
App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter -> App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter ->
BookHelp.getContent(book, chapter)?.let { BookHelp.getContent(book, chapter)?.let {
contentLoadFinish(chapter, it) contentLoadFinish(chapter, it, upContent)
removeLoading(chapter.index) removeLoading(chapter.index)
} ?: download(chapter) } ?: download(chapter)
} ?: removeLoading(index) } ?: removeLoading(index)
@ -262,7 +262,11 @@ object ReadBook {
/** /**
* 内容加载完成 * 内容加载完成
*/ */
private fun contentLoadFinish(chapter: BookChapter, content: String) { private fun contentLoadFinish(
chapter: BookChapter,
content: String,
upContent: Boolean = true
) {
Coroutine.async { Coroutine.async {
if (chapter.index in durChapterIndex - 1..durChapterIndex + 1) { if (chapter.index in durChapterIndex - 1..durChapterIndex + 1) {
val c = BookHelp.disposeContent( val c = BookHelp.disposeContent(
@ -275,18 +279,18 @@ object ReadBook {
when (chapter.index) { when (chapter.index) {
durChapterIndex -> { durChapterIndex -> {
curTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) curTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize)
callBack?.upContent() if (upContent) callBack?.upContent()
callBack?.upView() callBack?.upView()
curPageChanged() curPageChanged()
callBack?.contentLoadFinish() callBack?.contentLoadFinish()
} }
durChapterIndex - 1 -> { durChapterIndex - 1 -> {
prevTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) prevTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize)
callBack?.upContent(-1) if (upContent) callBack?.upContent(-1)
} }
durChapterIndex + 1 -> { durChapterIndex + 1 -> {
nextTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) nextTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize)
callBack?.upContent(1) if (upContent) callBack?.upContent(1)
} }
} }
} }

@ -23,6 +23,7 @@ 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.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.storage.SyncBookProgress
import io.legado.app.lib.dialogs.alert 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
@ -31,6 +32,8 @@ 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
import io.legado.app.ui.book.changesource.ChangeSourceDialog
import io.legado.app.ui.book.chapterlist.ChapterListActivity
import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.config.* import io.legado.app.ui.book.read.config.*
import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR
@ -40,8 +43,6 @@ import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.TextPageFactory import io.legado.app.ui.book.read.page.TextPageFactory
import io.legado.app.ui.book.read.page.delegate.PageDelegate import io.legado.app.ui.book.read.page.delegate.PageDelegate
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.changesource.ChangeSourceDialog
import io.legado.app.ui.book.chapterlist.ChapterListActivity
import io.legado.app.ui.login.SourceLogin import io.legado.app.ui.login.SourceLogin
import io.legado.app.ui.replacerule.ReplaceRuleActivity import io.legado.app.ui.replacerule.ReplaceRuleActivity
import io.legado.app.ui.replacerule.edit.ReplaceEditDialog import io.legado.app.ui.replacerule.edit.ReplaceEditDialog
@ -660,6 +661,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
mHandler.removeCallbacks(keepScreenRunnable) mHandler.removeCallbacks(keepScreenRunnable)
textActionMenu?.dismiss() textActionMenu?.dismiss()
page_view.onDestroy() page_view.onDestroy()
SyncBookProgress.uploadBookProgress()
} }
override fun observeLiveBus() { override fun observeLiveBus() {

@ -16,6 +16,7 @@ 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
import io.legado.app.utils.toStringArray
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -177,10 +178,10 @@ object ChapterProvider {
var x = 0f var x = 0f
val bodyIndent = ReadBookConfig.bodyIndent val bodyIndent = ReadBookConfig.bodyIndent
val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length
for (i in 0..bodyIndent.lastIndex) { bodyIndent.toStringArray().forEach {
val x1 = x + icw val x1 = x + icw
textLine.addTextChar( textLine.addTextChar(
charData = bodyIndent[i].toString(), charData = it,
start = paddingLeft + x, start = paddingLeft + x,
end = paddingLeft + x1 end = paddingLeft + x1
) )
@ -203,12 +204,11 @@ object ChapterProvider {
val gapCount: Int = words.length - 1 val gapCount: Int = words.length - 1
val d = (visibleWidth - desiredWidth) / gapCount val d = (visibleWidth - desiredWidth) / gapCount
var x = startX var x = startX
for (i in words.indices) { for ((i, char) in words.toStringArray().withIndex()) {
val char = words[i] val cw = StaticLayout.getDesiredWidth(char, textPaint)
val cw = StaticLayout.getDesiredWidth(char.toString(), textPaint)
val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw) val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw)
textLine.addTextChar( textLine.addTextChar(
charData = char.toString(), charData = char,
start = paddingLeft + x, start = paddingLeft + x,
end = paddingLeft + x1 end = paddingLeft + x1
) )
@ -230,12 +230,11 @@ object ChapterProvider {
stringBuilder.append("\n") stringBuilder.append("\n")
textLine.text = "$words\n" textLine.text = "$words\n"
var x = startX var x = startX
for (i in words.indices) { words.toStringArray().forEach {
val char = words[i].toString() val cw = StaticLayout.getDesiredWidth(it, textPaint)
val cw = StaticLayout.getDesiredWidth(char, textPaint)
val x1 = x + cw val x1 = x + cw
textLine.addTextChar( textLine.addTextChar(
charData = char, charData = it,
start = paddingLeft + x, start = paddingLeft + x,
end = paddingLeft + x1 end = paddingLeft + x1
) )

@ -170,13 +170,13 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} else if (!pageFactory.hasNext() && pageOffset < 0) { } else if (!pageFactory.hasNext() && pageOffset < 0) {
pageOffset = 0f pageOffset = 0f
} else if (pageOffset > 0) { } else if (pageOffset > 0) {
pageFactory.moveToPrev() pageFactory.moveToPrev(false)
textPage = pageFactory.currentPage textPage = pageFactory.currentPage
pageOffset -= textPage.height pageOffset -= textPage.height
upView?.invoke(textPage) upView?.invoke(textPage)
} else if (pageOffset < -textPage.height) { } else if (pageOffset < -textPage.height) {
pageOffset += textPage.height pageOffset += textPage.height
pageFactory.moveToNext() pageFactory.moveToNext(false)
textPage = pageFactory.currentPage textPage = pageFactory.currentPage
upView?.invoke(textPage) upView?.invoke(textPage)
} }

@ -16,4 +16,6 @@ interface DataSource {
fun hasNextChapter(): Boolean fun hasNextChapter(): Boolean
fun hasPrevChapter(): Boolean fun hasPrevChapter(): Boolean
fun upContent(relativePosition: Int = 0)
} }

@ -6,9 +6,9 @@ abstract class PageFactory<DATA>(protected val dataSource: DataSource) {
abstract fun moveToLast() abstract fun moveToLast()
abstract fun moveToNext():Boolean abstract fun moveToNext(upContent: Boolean): Boolean
abstract fun moveToPrev(): Boolean abstract fun moveToPrev(upContent: Boolean): Boolean
abstract val nextPage: DATA abstract val nextPage: DATA

@ -76,12 +76,10 @@ 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.moveToPrev() pageFactory.moveToPrev(true)
upContent()
} }
PageDelegate.Direction.NEXT -> { PageDelegate.Direction.NEXT -> {
pageFactory.moveToNext() pageFactory.moveToNext(true)
upContent()
} }
else -> Unit else -> Unit
} }
@ -100,7 +98,7 @@ class PageView(context: Context, attrs: AttributeSet) :
upContent() upContent()
} }
fun upContent(relativePosition: Int = 0) { override fun upContent(relativePosition: Int) {
if (ReadBookConfig.isScroll) { if (ReadBookConfig.isScroll) {
curPage.setContent(pageFactory.currentPage) curPage.setContent(pageFactory.currentPage)
} else { } else {

@ -31,25 +31,27 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
} ?: ReadBook.setPageIndex(0) } ?: ReadBook.setPageIndex(0)
} }
override fun moveToNext(): Boolean = with(dataSource) { override fun moveToNext(upContent: Boolean): Boolean = with(dataSource) {
return if (hasNext()) { return if (hasNext()) {
if (currentChapter?.isLastIndex(pageIndex) == true) { if (currentChapter?.isLastIndex(pageIndex) == true) {
ReadBook.moveToNextChapter(false) ReadBook.moveToNextChapter(upContent)
} else { } else {
ReadBook.setPageIndex(pageIndex.plus(1)) ReadBook.setPageIndex(pageIndex.plus(1))
} }
if (upContent) upContent()
true true
} else } else
false false
} }
override fun moveToPrev(): Boolean = with(dataSource) { override fun moveToPrev(upContent: Boolean): Boolean = with(dataSource) {
return if (hasPrev()) { return if (hasPrev()) {
if (pageIndex <= 0) { if (pageIndex <= 0) {
ReadBook.moveToPrevChapter(false) ReadBook.moveToPrevChapter(upContent)
} else { } else {
ReadBook.setPageIndex(pageIndex.minus(1)) ReadBook.setPageIndex(pageIndex.minus(1))
} }
if (upContent) upContent()
true true
} else } else
false false

@ -4,7 +4,6 @@ import android.graphics.Bitmap
import android.view.MotionEvent import android.view.MotionEvent
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import io.legado.app.utils.screenshot import io.legado.app.utils.screenshot
import kotlin.math.abs
abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageView) { abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageView) {
@ -52,12 +51,31 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
} }
private fun onScroll(event: MotionEvent) { private fun onScroll(event: MotionEvent) {
val action: Int = event.action
val pointerUp =
action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_POINTER_UP
val skipIndex = if (pointerUp) event.actionIndex else -1
// Determine focal point
var sumX = 0f
var sumY = 0f
val count: Int = event.pointerCount
for (i in 0 until count) {
if (skipIndex == i) continue
sumX += event.getX(i)
sumY += event.getY(i)
}
val div = if (pointerUp) count - 1 else count
val focusX = sumX / div
val focusY = sumY / div
//判断是否移动了 //判断是否移动了
if (!isMoved) { if (!isMoved) {
isMoved = abs(startX - event.x) > slop val deltaX = (focusX - startX).toInt()
|| abs(startX - event.x) > abs(startY - event.y) val deltaY = (focusY - startY).toInt()
val distance = deltaX * deltaX + deltaY * deltaY
isMoved = distance > slopSquare
if (isMoved) { if (isMoved) {
if (event.x - startX > 0) { if (sumX - startX > 0) {
//如果上一页不存在 //如果上一页不存在
if (!hasPrev()) { if (!hasPrev()) {
noNext = true noNext = true
@ -78,7 +96,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
isCancel = if (mDirection == Direction.NEXT) touchX > lastX else touchX < lastX isCancel = if (mDirection == Direction.NEXT) touchX > lastX else touchX < lastX
isRunning = true isRunning = true
//设置触摸点 //设置触摸点
setTouchPoint(event.x, event.y) setTouchPoint(sumX, sumY)
} }
} }

@ -23,7 +23,7 @@ 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
@ -41,18 +41,23 @@ abstract class PageDelegate(protected val pageView: PageView) :
protected var viewWidth: Int = pageView.width protected var viewWidth: Int = pageView.width
protected var viewHeight: Int = pageView.height protected var viewHeight: Int = pageView.height
private val snackBar: Snackbar by lazy {
Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT)
}
private val scroller: Scroller by lazy { private val scroller: Scroller by lazy {
Scroller(pageView.context, DecelerateInterpolator()) Scroller(pageView.context, DecelerateInterpolator())
} }
protected val slopSquare by lazy {
val scaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
scaledTouchSlop * scaledTouchSlop
}
private val detector: GestureDetector by lazy { private val detector: GestureDetector by lazy {
GestureDetector(pageView.context, this) GestureDetector(pageView.context, this)
} }
private val snackBar: Snackbar by lazy {
Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT)
}
var isMoved = false var isMoved = false
var noNext = true var noNext = true
@ -189,9 +194,12 @@ abstract class PageDelegate(protected val pageView: PageView) :
@CallSuper @CallSuper
open fun onTouch(event: MotionEvent) { open fun onTouch(event: MotionEvent) {
if (isStarted) return if (isStarted) return
detector.setIsLongpressEnabled(false)
if (!detector.onTouchEvent(event)) { if (!detector.onTouchEvent(event)) {
//GestureDetector.onFling小幅移动不会触发,所以要自己判断 //GestureDetector.onFling小幅移动不会触发,所以要自己判断
if (event.action == MotionEvent.ACTION_UP && isMoved) { when (event.action) {
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> if (isMoved) {
if (selectedOnDown) { if (selectedOnDown) {
selectedOnDown = false selectedOnDown = false
} }
@ -199,6 +207,7 @@ abstract class PageDelegate(protected val pageView: PageView) :
} }
} }
} }
}
/** /**
* 按下 * 按下
@ -232,6 +241,10 @@ abstract class PageDelegate(protected val pageView: PageView) :
selectedOnDown = false selectedOnDown = false
return true return true
} }
if (isMoved) {
if (!noNext) onAnimStart()
return true
}
val x = e.x val x = e.x
val y = e.y val y = e.y
if (centerRectF.contains(x, y)) { if (centerRectF.contains(x, y)) {

@ -4,7 +4,6 @@ import android.view.MotionEvent
import android.view.VelocityTracker import android.view.VelocityTracker
import io.legado.app.ui.book.read.page.ChapterProvider import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import kotlin.math.abs
class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
@ -46,9 +45,28 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
private fun onScroll(event: MotionEvent) { private fun onScroll(event: MotionEvent) {
mVelocity.addMovement(event) mVelocity.addMovement(event)
mVelocity.computeCurrentVelocity(velocityDuration) mVelocity.computeCurrentVelocity(velocityDuration)
setTouchPoint(event.x, event.y) val action: Int = event.action
val pointerUp =
action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_POINTER_UP
val skipIndex = if (pointerUp) event.actionIndex else -1
// Determine focal point
var sumX = 0f
var sumY = 0f
val count: Int = event.pointerCount
for (i in 0 until count) {
if (skipIndex == i) continue
sumX += event.getX(i)
sumY += event.getY(i)
}
val div = if (pointerUp) count - 1 else count
val focusX = sumX / div
val focusY = sumY / div
setTouchPoint(sumX, sumY)
if (!isMoved) { if (!isMoved) {
isMoved = abs(startX - event.x) > slop || abs(startY - event.y) > slop val deltaX = (focusX - startX).toInt()
val deltaY = (focusY - startY).toInt()
val distance = deltaX * deltaX + deltaY * deltaY
isMoved = distance > slopSquare
} }
if (isMoved) { if (isMoved) {
isRunning = true isRunning = true

@ -114,7 +114,8 @@ object BackupRestoreUi {
.onGranted { .onGranted {
Coroutine.async { Coroutine.async {
AppConfig.backupPath = path AppConfig.backupPath = path
Restore.restore(path) Restore.restoreDatabase(path)
Restore.restoreConfig(path)
}.onSuccess { }.onSuccess {
fragment.toast(R.string.restore_success) fragment.toast(R.string.restore_success)
} }

@ -16,7 +16,6 @@ import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.SyncBookProgress
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
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
@ -129,7 +128,6 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
override fun finish() { override fun finish() {
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
SyncBookProgress.uploadBookProgress()
Backup.autoBack(this) Backup.autoBack(this)
} }
super.finish() super.finish()

@ -23,7 +23,7 @@ class ReplaceEditViewModel(application: Application) : BaseViewModel(application
bundle.getString("pattern")?.let { pattern -> bundle.getString("pattern")?.let { pattern ->
val isRegex = bundle.getBoolean("isRegex") val isRegex = bundle.getBoolean("isRegex")
replaceRuleData.postValue( replaceRuleData.postValue(
ReplaceRule(pattern = pattern, isRegex = isRegex) ReplaceRule(name = pattern, pattern = pattern, isRegex = isRegex)
) )
} }
} }

@ -60,7 +60,7 @@ class RssArticlesActivity : VMBaseActivity<RssArticlesViewModel>(R.layout.activi
R.id.menu_clear -> { R.id.menu_clear -> {
viewModel.url?.let { viewModel.url?.let {
refresh_progress_bar.isAutoLoading = true refresh_progress_bar.isAutoLoading = true
viewModel.clear() viewModel.clearArticles()
} }
} }
} }

@ -106,7 +106,7 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application
} }
} }
fun clear() { fun clearArticles() {
execute { execute {
url?.let { url?.let {
App.db.rssArticleDao().delete(it) App.db.rssArticleDao().delete(it)

@ -102,8 +102,6 @@ object RealPathUtil {
val projection = arrayOf( val projection = arrayOf(
column column
) )
var input: FileInputStream? = null
var output: FileOutputStream? = null
try { try {
cursor = cursor =
context.contentResolver.query(uri!!, projection, selection, selectionArgs, null) context.contentResolver.query(uri!!, projection, selection, selectionArgs, null)
@ -115,6 +113,8 @@ object RealPathUtil {
e.printStackTrace() e.printStackTrace()
val file = File(context.cacheDir, "tmp") val file = File(context.cacheDir, "tmp")
val filePath = file.absolutePath val filePath = file.absolutePath
var input: FileInputStream? = null
var output: FileOutputStream? = null
try { try {
val pfd = val pfd =
context.contentResolver.openFileDescriptor(filePathUri!!, "r") context.contentResolver.openFileDescriptor(filePathUri!!, "r")
@ -127,11 +127,12 @@ object RealPathUtil {
while (input.read(bytes).also { read = it } != -1) { while (input.read(bytes).also { read = it } != -1) {
output.write(bytes, 0, read) output.write(bytes, 0, read)
} }
input.close()
output.close()
return File(filePath).absolutePath return File(filePath).absolutePath
} catch (ignored: IOException) { } catch (ignored: IOException) {
ignored.printStackTrace() ignored.printStackTrace()
} finally {
input?.close()
output?.close()
} }
} finally { } finally {
cursor?.close() cursor?.close()

@ -48,6 +48,17 @@ fun String.splitNotBlank(regex: Regex, limit: Int = 0): Array<String> = run {
this.split(regex, limit).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray() this.split(regex, limit).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray()
} }
fun String.toStringArray(): Array<String> {
var codePointIndex = 0
return Array(codePointCount(0, length)) {
substring(
codePointIndex,
offsetByCodePoints(codePointIndex, 1)
.apply { codePointIndex = this }
)
}
}
fun Char?.isHAN(): Boolean { fun Char?.isHAN(): Boolean {
this ?: return false this ?: return false
val ub: Character.UnicodeBlock = Character.UnicodeBlock.of(this) ?: return false val ub: Character.UnicodeBlock = Character.UnicodeBlock.of(this) ?: return false

@ -13,7 +13,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'de.timfreiheit.resourceplaceholders:placeholders:0.3' classpath 'de.timfreiheit.resourceplaceholders:placeholders:0.3'
classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.gms:google-services:4.3.3'
classpath 'io.fabric.tools:gradle:1.31.0' classpath 'io.fabric.tools:gradle:1.31.2'
} }
} }

@ -19,4 +19,3 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
android.enableR8=false
Loading…
Cancel
Save