pull/32/head
Administrator 5 years ago
parent 6d98e6748c
commit 878f512c51
  1. 8
      app/src/main/java/io/legado/app/base/BaseViewModel.kt
  2. 26
      app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt
  3. 107
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  4. 5
      app/src/main/java/io/legado/app/model/WebBook.kt
  5. 1
      app/src/main/java/io/legado/app/model/webbook/SourceDebug.kt
  6. 7
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  7. 23
      app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt
  8. 1
      app/src/main/java/io/legado/app/ui/sourcedebug/SourceDebugModel.kt
  9. 77
      app/src/main/java/io/legado/app/ui/widget/PageView.kt
  10. 12
      app/src/main/java/io/legado/app/ui/widget/page/DataSource.kt
  11. 39
      app/src/main/java/io/legado/app/ui/widget/page/PageAnimDelegate.kt
  12. 11
      app/src/main/java/io/legado/app/ui/widget/page/PageFactory.kt
  13. 55
      app/src/main/java/io/legado/app/ui/widget/page/PageView.kt
  14. 3
      app/src/main/java/io/legado/app/ui/widget/page/TextChapter.kt
  15. 3
      app/src/main/java/io/legado/app/ui/widget/page/TextPage.kt
  16. 26
      app/src/main/java/io/legado/app/ui/widget/page/TextPageFactory.kt
  17. 2
      app/src/main/res/layout/activity_read.xml
  18. 42
      app/src/main/res/layout/page_view.xml

@ -12,12 +12,12 @@ import org.jetbrains.anko.AnkoLogger
open class BaseViewModel(application: Application) : AndroidViewModel(application), CoroutineScope by MainScope(),
AnkoLogger {
fun <T> execute(block: suspend CoroutineScope.() -> T): Coroutine<T> {
return Coroutine.async(this) { block() }
fun <T> execute(scope: CoroutineScope = this, block: suspend CoroutineScope.() -> T): Coroutine<T> {
return Coroutine.async(scope) { block() }
}
fun <T> submit(block: suspend CoroutineScope.() -> Deferred<T>): Coroutine<T> {
return Coroutine.async(this) { block().await() }
fun <R> submit(scope: CoroutineScope = this, block: suspend CoroutineScope.() -> Deferred<R>): Coroutine<R> {
return Coroutine.async(scope) { block().await() }
}
fun <T> plus(coroutine: Coroutine<T>): Coroutine<T> {

@ -7,7 +7,7 @@ class Coroutine<T>() {
companion object {
private val DEFAULT = MainScope()
val DEFAULT = MainScope()
fun <T> async(scope: CoroutineScope = DEFAULT, block: suspend CoroutineScope.() -> T): Coroutine<T> {
return Coroutine(scope, block)
@ -22,6 +22,7 @@ class Coroutine<T>() {
private var job: Job? = null
private var start: (suspend CoroutineScope.() -> Unit)? = null
private var execute: (suspend CoroutineScope.(T?) -> Unit)? = null
private var success: (suspend CoroutineScope.(T?) -> Unit)? = null
private var error: (suspend CoroutineScope.(Throwable) -> Unit)? = null
private var finally: (suspend CoroutineScope.() -> Unit)? = null
@ -80,6 +81,15 @@ class Coroutine<T>() {
return this@Coroutine
}
fun onExecute(execute: suspend CoroutineScope.(T?) -> Unit): Coroutine<T> {
if (this.interceptor != null) {
this.interceptor!!.execute = execute
} else {
this.execute = execute
}
return this@Coroutine
}
fun onSuccess(success: suspend CoroutineScope.(T?) -> Unit): Coroutine<T> {
if (this.interceptor != null) {
this.interceptor!!.success = success
@ -117,7 +127,7 @@ class Coroutine<T>() {
{
start?.invoke(this)
val result = executeBlock(block, timeMillis ?: 0L)
val result = executeBlockIO(block, timeMillis ?: 0L)
success?.invoke(this, result)
},
@ -136,11 +146,15 @@ class Coroutine<T>() {
})
}
private suspend fun executeBlock(block: suspend CoroutineScope.() -> T, timeMillis: Long): T? {
val asyncBlock = withContext(Dispatchers.IO) {
block()
private suspend fun executeBlockIO(block: suspend CoroutineScope.() -> T, timeMillis: Long): T? {
val result: T = withContext(Dispatchers.IO) {
val b = block()
execute?.invoke(this, b)
b
}
return if (timeMillis > 0L) withTimeout(timeMillis) { asyncBlock } else asyncBlock
return if (timeMillis > 0L) withTimeout(timeMillis) { result } else result
}
private suspend fun tryCatch(

@ -102,58 +102,61 @@ object Restore {
source.bookSourceUrl = jsonItem.readString("bookSourceUrl") ?: ""
if (source.bookSourceUrl.isBlank()) continue
if (source.bookSourceUrl in existingSources) continue
source.bookSourceName = jsonItem.readString("bookSourceName") ?: ""
source.bookSourceGroup = jsonItem.readString("bookSourceGroup") ?: ""
source.loginUrl = jsonItem.readString("loginUrl")
source.bookUrlPattern = jsonItem.readString("ruleBookUrlPattern")
source.customOrder = jsonItem.readInt("serialNumber") ?: 0
val searchRule = SearchRule(
searchUrl = OldRule.toNewUrl(jsonItem.readString("ruleSearchUrl")),
bookList = jsonItem.readString("ruleSearchList"),
name = jsonItem.readString("ruleSearchName"),
author = jsonItem.readString("ruleSearchAuthor"),
intro = jsonItem.readString("ruleSearchIntroduce"),
kind = jsonItem.readString("ruleSearchKind"),
bookUrl = jsonItem.readString("ruleSearchNoteUrl"),
coverUrl = jsonItem.readString("ruleSearchCoverUrl"),
lastChapter = jsonItem.readString("ruleSearchLastChapter")
)
source.ruleSearch = GSON.toJson(searchRule)
val exploreRule = ExploreRule(
exploreUrl = OldRule.toNewUrl(jsonItem.readString("ruleFindUrl")),
bookList = jsonItem.readString("ruleFindList"),
name = jsonItem.readString("ruleFindName"),
author = jsonItem.readString("ruleFindAuthor"),
intro = jsonItem.readString("ruleFindIntroduce"),
kind = jsonItem.readString("ruleFindKind"),
bookUrl = jsonItem.readString("ruleFindNoteUrl"),
coverUrl = jsonItem.readString("ruleFindCoverUrl"),
lastChapter = jsonItem.readString("ruleFindLastChapter")
)
source.ruleExplore = GSON.toJson(exploreRule)
val bookInfoRule = BookInfoRule(
init = jsonItem.readString("ruleBookInfoInit"),
name = jsonItem.readString("ruleBookName"),
author = jsonItem.readString("ruleBookAuthor"),
intro = jsonItem.readString("ruleIntroduce"),
kind = jsonItem.readString("ruleBookKind"),
coverUrl = jsonItem.readString("ruleCoverUrl"),
lastChapter = jsonItem.readString("ruleBookLastChapter"),
tocUrl = jsonItem.readString("ruleChapterUrl")
)
source.ruleBookInfo = GSON.toJson(bookInfoRule)
val chapterRule = TocRule(
chapterList = jsonItem.readString("ruleChapterList"),
chapterName = jsonItem.readString("ruleChapterName"),
chapterUrl = jsonItem.readString("ruleContentUrl"),
nextTocUrl = jsonItem.readString("ruleChapterUrlNext")
)
source.ruleToc = GSON.toJson(chapterRule)
val contentRule = ContentRule(
content = jsonItem.readString("ruleBookContent"),
nextContentUrl = jsonItem.readString("ruleContentUrlNext")
)
source.ruleContent = GSON.toJson(contentRule)
runCatching {
source.bookSourceName = jsonItem.readString("bookSourceName") ?: ""
source.bookSourceGroup = jsonItem.readString("bookSourceGroup") ?: ""
source.loginUrl = jsonItem.readString("loginUrl")
source.bookUrlPattern = jsonItem.readString("ruleBookUrlPattern")
source.customOrder = jsonItem.readInt("serialNumber") ?: 0
val searchRule = SearchRule(
searchUrl = OldRule.toNewUrl(jsonItem.readString("ruleSearchUrl")),
bookList = jsonItem.readString("ruleSearchList"),
name = jsonItem.readString("ruleSearchName"),
author = jsonItem.readString("ruleSearchAuthor"),
intro = jsonItem.readString("ruleSearchIntroduce"),
kind = jsonItem.readString("ruleSearchKind"),
bookUrl = jsonItem.readString("ruleSearchNoteUrl"),
coverUrl = jsonItem.readString("ruleSearchCoverUrl"),
lastChapter = jsonItem.readString("ruleSearchLastChapter")
)
source.ruleSearch = GSON.toJson(searchRule)
val exploreRule = ExploreRule(
exploreUrl = OldRule.toNewUrl(jsonItem.readString("ruleFindUrl")),
bookList = jsonItem.readString("ruleFindList"),
name = jsonItem.readString("ruleFindName"),
author = jsonItem.readString("ruleFindAuthor"),
intro = jsonItem.readString("ruleFindIntroduce"),
kind = jsonItem.readString("ruleFindKind"),
bookUrl = jsonItem.readString("ruleFindNoteUrl"),
coverUrl = jsonItem.readString("ruleFindCoverUrl"),
lastChapter = jsonItem.readString("ruleFindLastChapter")
)
source.ruleExplore = GSON.toJson(exploreRule)
val bookInfoRule = BookInfoRule(
init = jsonItem.readString("ruleBookInfoInit"),
name = jsonItem.readString("ruleBookName"),
author = jsonItem.readString("ruleBookAuthor"),
intro = jsonItem.readString("ruleIntroduce"),
kind = jsonItem.readString("ruleBookKind"),
coverUrl = jsonItem.readString("ruleCoverUrl"),
lastChapter = jsonItem.readString("ruleBookLastChapter"),
tocUrl = jsonItem.readString("ruleChapterUrl")
)
source.ruleBookInfo = GSON.toJson(bookInfoRule)
val chapterRule = TocRule(
chapterList = jsonItem.readString("ruleChapterList"),
chapterName = jsonItem.readString("ruleChapterName"),
chapterUrl = jsonItem.readString("ruleContentUrl"),
nextTocUrl = jsonItem.readString("ruleChapterUrlNext")
)
source.ruleToc = GSON.toJson(chapterRule)
val contentRule = ContentRule(
content = jsonItem.readString("ruleBookContent"),
nextContentUrl = jsonItem.readString("ruleContentUrlNext")
)
source.ruleContent = GSON.toJson(contentRule)
}
bookSources.add(source)
}
App.db.bookSourceDao().insert(*bookSources.toTypedArray())

@ -10,14 +10,15 @@ import io.legado.app.model.webbook.BookChapterList
import io.legado.app.model.webbook.BookContent
import io.legado.app.model.webbook.BookInfo
import io.legado.app.model.webbook.BookList
import kotlinx.coroutines.CoroutineScope
class WebBook(private val bookSource: BookSource) {
val sourceUrl: String
get() = bookSource.bookSourceUrl
fun searchBook(key: String, page: Int?, isSearch: Boolean = true): Coroutine<List<SearchBook>> {
return Coroutine.async {
fun searchBook(key: String, page: Int?, isSearch: Boolean = true, scope: CoroutineScope = Coroutine.DEFAULT): Coroutine<List<SearchBook>> {
return Coroutine.async(scope) {
bookSource.getSearchRule().searchUrl?.let { searchUrl ->
val analyzeUrl = AnalyzeUrl(searchUrl, key, page, baseUrl = sourceUrl)
val response = analyzeUrl.getResponseAsync().await()

@ -5,6 +5,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook
import io.legado.app.utils.htmlFormat
import io.legado.app.utils.isAbsUrl

@ -14,13 +14,14 @@ import io.legado.app.constant.Bus
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.Selector
import io.legado.app.lib.theme.ThemeStore
import io.legado.app.ui.main.bookshelf.BookshelfFragment
import io.legado.app.ui.main.findbook.FindBookFragment
import io.legado.app.ui.main.my.MyFragment
import io.legado.app.ui.main.rss.RssFragment
import io.legado.app.utils.*
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.getViewModel
import io.legado.app.utils.observeEvent
import io.legado.app.utils.putPrefInt
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch

@ -4,37 +4,32 @@ import android.app.Application
import io.legado.app.App
import io.legado.app.base.BaseViewModel
import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook
class SearchViewModel(application: Application) : BaseViewModel(application) {
private val tasks: CompositeCoroutine = CompositeCoroutine()
private var task: Coroutine<*>? = null
var searchPage = 0
fun search(key: String, start: ((startTime: Long) -> Unit)? = null, finally: (() -> Unit)? = null) {
if (key.isEmpty()) return
tasks.clear()
task?.cancel()
start?.invoke(System.currentTimeMillis())
execute {
task = execute {//onCleared时自动取消
val bookSourceList = App.db.bookSourceDao().allEnabled
for (item in bookSourceList) {
val search = WebBook(item).searchBook(key, searchPage)
.onSuccess { searchBookS ->
//task取消时自动取消 by (scope = this@execute)
WebBook(item).searchBook(key, searchPage, scope = this)
.onExecute { searchBookS ->
searchBookS?.let {
execute {
App.db.searchBookDao().insert(*it.toTypedArray())
}
App.db.searchBookDao().insert(*it.toTypedArray())
}
}
tasks.add(search)
}
}.onError {
it.printStackTrace()
}
}
override fun onCleared() {
super.onCleared()
tasks.clear()
}
}

@ -44,4 +44,5 @@ class SourceDebugModel(application: Application) : BaseViewModel(application), S
super.onCleared()
SourceDebug.cancelDebug(true)
}
}

@ -1,77 +0,0 @@
package io.legado.app.ui.widget
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import androidx.appcompat.widget.AppCompatTextView
import io.legado.app.utils.screenshot
import kotlin.math.abs
class PageView(context: Context, attrs: AttributeSet) : AppCompatTextView(context, attrs) {
private var bitmap: Bitmap? = null
private var downX: Float = 0.toFloat()
private var offset: Float = 0.toFloat()
private val srcRect: Rect = Rect()
private val destRect: Rect = Rect()
private val shadowDrawable: GradientDrawable
init {
val shadowColors = intArrayOf(0x66111111, 0x00000000)
shadowDrawable = GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT, shadowColors
)
shadowDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT
}
override fun onDraw(canvas: Canvas?) {
canvas?.save()
super.onDraw(canvas)
canvas?.restore()
bitmap?.let {
srcRect.set(0, 0, width, height)
destRect.set(-width + offset.toInt(), 0, offset.toInt(), height)
canvas?.drawBitmap(it, srcRect, destRect, null)
addShadow(offset.toInt(), canvas)
}
}
//添加阴影
private fun addShadow(left: Int, canvas: Canvas?) {
canvas?.let {
shadowDrawable.setBounds(left, 0, left + 30, height)
shadowDrawable.draw(it)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
bitmap = screenshot()
Log.e("TAG", "bitmap == null: " + (bitmap == null))
downX = event.x
offset = 0.toFloat()
invalidate()
}
MotionEvent.ACTION_MOVE -> {
offset = abs(event.x - downX)
invalidate()
}
MotionEvent.ACTION_UP -> {
bitmap = null
invalidate()
}
}
return true
}
}

@ -0,0 +1,12 @@
package io.legado.app.ui.widget.page
interface DataSource {
fun getChapterPosition()
fun getChapter(position: Int): TextChapter
fun getNextChapter(): TextChapter
fun getPreviousChapter(): TextChapter
}

@ -0,0 +1,39 @@
package io.legado.app.ui.widget.page
import android.widget.Scroller
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
abstract class PageAnimDelegate(protected val pageView: PageView) {
protected val scroller: Scroller = Scroller(pageView.context, FastOutLinearInInterpolator())
//起始点
protected var startX: Float = 0.toFloat()
protected var startY: Float = 0.toFloat()
//触碰点
protected var touchX: Float = 0.toFloat()
protected var touchY: Float = 0.toFloat()
//上一个触碰点
protected var lastX: Float = 0.toFloat()
protected var lastY: Float = 0.toFloat()
protected var isRunning = false
protected var isStarted = false
fun setStartPoint(x: Float, y: Float) {
startX = x
startY = y
lastX = startX
lastY = startY
}
fun setTouchPoint(x: Float, y: Float) {
lastX = touchX
lastY = touchY
touchX = x
touchY = y
}
}

@ -0,0 +1,11 @@
package io.legado.app.ui.widget.page
abstract class PageFactory<DATA>(protected val dataSource: DataSource) {
abstract fun pageAt(index: Int): DATA
abstract fun nextPage(): DATA
abstract fun previousPage(): DATA
}

@ -0,0 +1,55 @@
package io.legado.app.ui.widget.page
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import androidx.appcompat.widget.AppCompatTextView
import io.legado.app.R
import io.legado.app.utils.screenshot
import kotlin.math.abs
class PageView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) {
private var bitmap: Bitmap? = null
private var downX: Float = 0.toFloat()
private var offset: Float = 0.toFloat()
private val srcRect: Rect = Rect()
private val destRect: Rect = Rect()
private val shadowDrawable: GradientDrawable
init {
val shadowColors = intArrayOf(0x66111111, 0x00000000)
shadowDrawable = GradientDrawable(
GradientDrawable.Orientation.LEFT_RIGHT, shadowColors
)
shadowDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT
inflate(context, R.layout.page_view, this)
}
override fun onDraw(canvas: Canvas?) {
canvas?.save()
super.onDraw(canvas)
canvas?.restore()
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
return true
}
fun setPageFactory(factory: PageFactory<*>){
}
}

@ -0,0 +1,3 @@
package io.legado.app.ui.widget.page
data class TextChapter(val position: Int, val pages: List<TextPage>)

@ -0,0 +1,3 @@
package io.legado.app.ui.widget.page
data class TextPage(val index: Int, val text: String)

@ -0,0 +1,26 @@
package io.legado.app.ui.widget.page
class TextPageFactory private constructor(dataSource: DataSource) : PageFactory<TextPage>(dataSource) {
companion object{
fun create(dataSource: DataSource): TextPageFactory{
return TextPageFactory(dataSource)
}
}
private var index: Int = 0
override fun pageAt(index: Int): TextPage {
TODO("todo...")
}
override fun nextPage(): TextPage {
return TextPage(index.plus(1), "index:$index")
}
override fun previousPage(): TextPage {
return TextPage(index.minus(1), "index:$index")
}
}

@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<io.legado.app.ui.widget.PageView
<io.legado.app.ui.widget.page.PageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="30dp"

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/page_panel"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:showDividers="middle"
app:divider="@drawable/ic_divider">
<View
android:id="@+id/top_status_bar"
android:layout_width="match_parent"
android:layout_height="25dp"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</FrameLayout>
<View
android:id="@+id/bottom_status_bar"
android:layout_width="match_parent"
android:layout_height="25dp"/>
</androidx.appcompat.widget.LinearLayoutCompat>
Loading…
Cancel
Save