pull/47/head
Celeter 5 years ago
commit 3c5051282e
  1. 5
      app/build.gradle
  2. 69
      app/src/debug/google-services .json
  3. 0
      app/src/debug/google-services.json
  4. 28
      app/src/main/AndroidManifest.xml
  5. 2
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  6. 12
      app/src/main/java/io/legado/app/help/BlurTransformation.kt
  7. 255
      app/src/main/java/io/legado/app/help/ImageLoader.kt
  8. 24
      app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt
  9. 8
      app/src/main/java/io/legado/app/service/AudioPlayService.kt
  10. 2
      app/src/main/java/io/legado/app/service/help/AudioPlay.kt
  11. 108
      app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt
  12. 83
      app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt
  13. 2
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  14. 42
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  15. 86
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  16. 2
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  17. 2
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  18. 6
      app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt
  19. 3
      app/src/main/java/io/legado/app/ui/chapterlist/ChapterListActivity.kt
  20. 25
      app/src/main/java/io/legado/app/ui/chapterlist/ChapterListFragment.kt
  21. 12
      app/src/main/java/io/legado/app/ui/chapterlist/ChapterListViewModel.kt
  22. 2
      app/src/main/java/io/legado/app/ui/explore/ExploreShowAdapter.kt
  23. 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/BooksAdapter.kt
  24. 2
      app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt
  25. 2
      app/src/main/java/io/legado/app/ui/main/rss/RssAdapter.kt
  26. 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter.kt
  27. 22
      app/src/main/res/layout/activity_audio_play.xml
  28. 4
      app/src/main/res/layout/item_text.xml
  29. 1
      app/src/main/res/values/strings.xml
  30. 2
      build.gradle

@ -37,6 +37,11 @@ android {
versionName version versionName version
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
project.ext.set("archivesBaseName", name + "_" + version) project.ext.set("archivesBaseName", name + "_" + version)
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.incremental":"true"]
}
}
} }
buildTypes { buildTypes {
release { release {

@ -1,69 +0,0 @@
{
"project_info": {
"project_number": "453392274790",
"firebase_url": "https://legado-fca69.firebaseio.com",
"project_id": "legado-fca69",
"storage_bucket": "legado-fca69.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:453392274790:android:1d2b1eefbe0e78cff624a7",
"android_client_info": {
"package_name": "io.legado.app"
}
},
"oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:453392274790:android:c4eac14b1410eec5f624a7",
"android_client_info": {
"package_name": "io.legado.app.debug"
}
},
"oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

@ -56,12 +56,24 @@
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout" android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
android:launchMode="singleTask" android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize|stateHidden" /> android:windowSoftInputMode="adjustResize|stateHidden" />
<activity android:name=".ui.config.ConfigActivity" /> <activity
<activity android:name=".ui.replacerule.ReplaceRuleActivity" /> android:name=".ui.config.ConfigActivity"
<activity android:name="io.legado.app.ui.book.search.SearchActivity" /> android:launchMode="singleTask" />
<activity android:name=".ui.about.AboutActivity" /> <activity
<activity android:name=".ui.qrcode.QrCodeActivity" /> android:name=".ui.replacerule.ReplaceRuleActivity"
<activity android:name=".ui.about.DonateActivity" /> android:launchMode="singleTask" />
<activity
android:name="io.legado.app.ui.book.search.SearchActivity"
android:launchMode="singleTask" />
<activity
android:name=".ui.about.AboutActivity"
android:launchMode="singleTask" />
<activity
android:name=".ui.qrcode.QrCodeActivity"
android:launchMode="singleTask" />
<activity
android:name=".ui.about.DonateActivity"
android:launchMode="singleTask" />
<activity android:name=".ui.book.info.BookInfoActivity" /> <activity android:name=".ui.book.info.BookInfoActivity" />
<activity android:name="io.legado.app.ui.book.info.edit.BookInfoEditActivity" /> <activity android:name="io.legado.app.ui.book.info.edit.BookInfoEditActivity" />
<activity android:name=".ui.book.source.debug.BookSourceDebugActivity" /> <activity android:name=".ui.book.source.debug.BookSourceDebugActivity" />
@ -79,7 +91,9 @@
</activity> </activity>
<activity android:name=".ui.chapterlist.ChapterListActivity" /> <activity android:name=".ui.chapterlist.ChapterListActivity" />
<activity android:name=".ui.rss.read.ReadRssActivity" /> <activity android:name=".ui.rss.read.ReadRssActivity" />
<activity android:name=".ui.audio.AudioPlayActivity" /> <activity
android:name=".ui.audio.AudioPlayActivity"
android:launchMode="singleTask" />
<activity android:name=".ui.explore.ExploreShowActivity" /> <activity android:name=".ui.explore.ExploreShowActivity" />
<activity android:name=".ui.rss.source.manage.RssSourceActivity" /> <activity android:name=".ui.rss.source.manage.RssSourceActivity" />
<activity android:name=".ui.rss.source.debug.RssSourceDebugActivity" /> <activity android:name=".ui.rss.source.debug.RssSourceDebugActivity" />

@ -53,4 +53,6 @@ interface BookDao {
@Query("delete from books where bookUrl = :bookUrl") @Query("delete from books where bookUrl = :bookUrl")
fun delete(bookUrl: String) fun delete(bookUrl: String)
@Query("update books set durChapterPos = :pos where bookUrl = :bookUrl")
fun upProgress(bookUrl: String, pos: Int)
} }

@ -13,17 +13,23 @@ import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import java.security.MessageDigest import java.security.MessageDigest
import kotlin.math.min
import kotlin.math.roundToInt
/** /**
* 模糊 * 模糊
* @radius: 0..25
*/ */
class BlurTransformation(context: Context, private val radius: Int) : BitmapTransformation() { class BlurTransformation(context: Context, private val radius: Int) : BitmapTransformation() {
private val rs: RenderScript = RenderScript.create(context) private val rs: RenderScript = RenderScript.create(context)
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap { override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
val blurredBitmap = toTransform.copy(Bitmap.Config.ARGB_8888, true) //图片缩小1/2
val width = (min(outWidth, toTransform.width) / 2f).roundToInt()
val height = (min(outHeight, toTransform.height) / 2f).roundToInt()
val blurredBitmap = Bitmap.createScaledBitmap(toTransform, width, height, false);
// Allocate memory for Renderscript to work with // Allocate memory for Renderscript to work with
//分配用于渲染脚本的内存 //分配用于渲染脚本的内存
val input = Allocation.createFromBitmap( val input = Allocation.createFromBitmap(
@ -40,7 +46,7 @@ class BlurTransformation(context: Context, private val radius: Int) : BitmapTran
script.setInput(input) script.setInput(input)
// Set the blur radius // Set the blur radius
//设置模糊半径 //设置模糊半径0..25
script.setRadius(radius.toFloat()) script.setRadius(radius.toFloat())
// Start the ScriptIntrinsicBlur // Start the ScriptIntrinsicBlur

@ -4,264 +4,45 @@ import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.widget.ImageView
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.Transformation
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.BitmapTransitionOptions
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.load.resource.gif.GifDrawable
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.transition.Transition
import java.io.File import java.io.File
object ImageLoader { object ImageLoader {
fun load(context: Context, url: String?): ImageLoadBuilder<String> { fun load(context: Context, path: String?): RequestBuilder<Drawable> {
return ImageLoadBuilder(context, url) if (path?.startsWith("http", true) == true) {
return Glide.with(context).load(path)
} }
kotlin.runCatching {
fun load(context: Context, @DrawableRes resId: Int?): ImageLoadBuilder<Int> { return Glide.with(context).load(File(path))
return ImageLoadBuilder(context, resId)
}
fun load(context: Context, file: File?): ImageLoadBuilder<File> {
return ImageLoadBuilder(context, file)
}
fun load(context: Context, uri: Uri?): ImageLoadBuilder<Uri> {
return ImageLoadBuilder(context, uri)
}
fun load(context: Context, drawable: Drawable?): ImageLoadBuilder<Drawable> {
return ImageLoadBuilder(context, drawable)
}
fun load(context: Context, bitmap: Bitmap?): ImageLoadBuilder<Bitmap> {
return ImageLoadBuilder(context, bitmap)
}
fun load(context: Context, bytes: ByteArray?): ImageLoadBuilder<ByteArray> {
return ImageLoadBuilder(context, bytes)
}
fun with(context: Context): ImageLoadBuilder<Any> {
return ImageLoadBuilder(context)
}
fun clear(imageView: ImageView) {
with(imageView.context).clear(imageView)
}
class ImageLoadBuilder<S> constructor(context: Context, private var source: S? = null) {
private val manager: RequestManager = Glide.with(context)
private var requestOptions: RequestOptions = RequestOptions()
private var crossFade: Boolean = false
private var noCache: Boolean = false
fun load(source: S): ImageLoadBuilder<S> {
this.source = source
return this
}
fun bitmapTransform(transformation: Transformation<Bitmap>): ImageLoadBuilder<S> {
requestOptions = requestOptions.apply(RequestOptions.bitmapTransform(transformation))
return this
}
fun toRound(corner: Int): ImageLoadBuilder<S> {
requestOptions = requestOptions.transform(RoundedCorners(corner))
return this
}
fun toCropRound(corner: Int): ImageLoadBuilder<S> {
requestOptions = requestOptions.transform(CenterCrop(), RoundedCorners(corner))
return this
}
fun toCircle(): ImageLoadBuilder<S> {
requestOptions = requestOptions.circleCrop()
return this
}
fun centerInside(): ImageLoadBuilder<S> {
requestOptions = requestOptions.centerInside()
return this
}
fun fitCenter(): ImageLoadBuilder<S> {
requestOptions = requestOptions.fitCenter()
return this
}
fun centerCrop(): ImageLoadBuilder<S> {
requestOptions = requestOptions.centerCrop()
return this
}
fun crossFade(): ImageLoadBuilder<S> {
crossFade = true
return this
}
fun noCache(): ImageLoadBuilder<S> {
noCache = true
return this
}
fun placeholder(placeholder: Drawable): ImageLoadBuilder<S> {
requestOptions = requestOptions.placeholder(placeholder)
return this
}
fun placeholder(@DrawableRes resId: Int): ImageLoadBuilder<S> {
requestOptions = requestOptions.placeholder(resId)
return this
}
fun error(drawable: Drawable): ImageLoadBuilder<S> {
requestOptions = requestOptions.error(drawable)
return this
}
fun error(@DrawableRes resId: Int): ImageLoadBuilder<S> {
requestOptions = requestOptions.error(resId)
return this
}
fun override(width: Int, height: Int): ImageLoadBuilder<S> {
requestOptions = requestOptions.override(width, height)
return this
}
fun override(size: Int): ImageLoadBuilder<S> {
requestOptions = requestOptions.override(size)
return this
}
fun clear(imageView: ImageView) {
manager.clear(imageView)
} }
return Glide.with(context).load(path)
fun downloadOnly(target: ImageViewTarget<File>) {
manager.downloadOnly().load(source).into(Target(target))
} }
fun setAsDrawable(imageView: ImageView) { fun load(context: Context, @DrawableRes resId: Int?): RequestBuilder<Drawable> {
asDrawable().into(imageView) return Glide.with(context).load(resId)
} }
fun setAsDrawable(target: ImageViewTarget<Drawable>) { fun load(context: Context, file: File?): RequestBuilder<Drawable> {
asDrawable().into(Target(target)) return Glide.with(context).load(file)
} }
fun setAsBitmap(imageView: ImageView) { fun load(context: Context, uri: Uri?): RequestBuilder<Drawable> {
asBitmap().into(imageView) return Glide.with(context).load(uri)
} }
fun setAsBitmap(target: ImageViewTarget<Bitmap>) { fun load(context: Context, drawable: Drawable?): RequestBuilder<Drawable> {
asBitmap().into(Target(target)) return Glide.with(context).load(drawable)
}
fun setAsGif(imageView: ImageView) {
asGif().into(imageView)
}
fun setAsGif(target: ImageViewTarget<GifDrawable>) {
asGif().into(Target(target))
}
fun setAsFile(imageView: ImageView) {
asFile().into(imageView)
}
fun setAsFile(target: ImageViewTarget<File>) {
asFile().into(Target(target))
}
private fun asDrawable(): RequestBuilder<Drawable> {
var builder: RequestBuilder<Drawable> = ensureOptions(manager.asDrawable().load(source))
if (crossFade) {
builder = builder.transition(DrawableTransitionOptions.withCrossFade())
}
return builder
}
private fun asBitmap(): RequestBuilder<Bitmap> {
var builder: RequestBuilder<Bitmap> = ensureOptions(manager.asBitmap().load(source))
if (crossFade) {
builder = builder.transition(BitmapTransitionOptions.withCrossFade())
}
return builder
}
private fun asGif(): RequestBuilder<GifDrawable> {
var builder: RequestBuilder<GifDrawable> = ensureOptions(manager.asGif().load(source))
if (crossFade) {
builder = builder.transition(DrawableTransitionOptions.withCrossFade())
} }
return builder fun load(context: Context, bitmap: Bitmap?): RequestBuilder<Drawable> {
return Glide.with(context).load(bitmap)
} }
private fun asFile(): RequestBuilder<File> { fun load(context: Context, bytes: ByteArray?): RequestBuilder<Drawable> {
return manager.asFile().load(source) return Glide.with(context).load(bytes)
} }
private fun <ResourceType> ensureOptions(builder: RequestBuilder<ResourceType>): RequestBuilder<ResourceType> {
return builder.apply(requestOptions.diskCacheStrategy(if (noCache) DiskCacheStrategy.NONE else DiskCacheStrategy.AUTOMATIC))
}
private inner class Target<R> constructor(private val target: ImageViewTarget<R>) :
com.bumptech.glide.request.target.ImageViewTarget<R>(target.view) {
init {
if (this.target.waitForLayout) {
waitForLayout()
}
}
override fun onResourceReady(resource: R, transition: Transition<in R>?) {
if (!target.onResourceReady(resource)) {
super.onResourceReady(resource, transition)
}
}
override fun setResource(resource: R?) {
target.setResource(resource)
}
}
}
abstract class ImageViewTarget<R>(val view: ImageView) {
internal var waitForLayout: Boolean = false
fun waitForLayout(): ImageViewTarget<R> {
waitForLayout = true
return this
}
fun setResource(resource: R?) {
}
fun onResourceReady(resource: R?): Boolean {
return false
}
}
} }

@ -4,11 +4,18 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.view.KeyEvent import android.view.KeyEvent
import io.legado.app.App
import io.legado.app.constant.Bus import io.legado.app.constant.Bus
import io.legado.app.data.entities.Book
import io.legado.app.help.ActivityHelp import io.legado.app.help.ActivityHelp
import io.legado.app.ui.audio.AudioPlayActivity import io.legado.app.ui.audio.AudioPlayActivity
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/** /**
@ -33,15 +40,24 @@ class MediaButtonReceiver : BroadcastReceiver() {
} }
private fun readAloud(context: Context) { private fun readAloud(context: Context) {
if (ActivityHelp.isExist(AudioPlayActivity::class.java)) { when {
ActivityHelp.isExist(AudioPlayActivity::class.java) ->
postEvent(Bus.AUDIO_PLAY_BUTTON, true) postEvent(Bus.AUDIO_PLAY_BUTTON, true)
} else if (!ActivityHelp.isExist(ReadBookActivity::class.java)) { ActivityHelp.isExist(ReadBookActivity::class.java) ->
postEvent(Bus.READ_ALOUD_BUTTON, true)
else -> {
GlobalScope.launch(Main) {
val lastBook: Book? = withContext(IO) {
App.db.bookDao().lastReadBook
}
lastBook?.let {
val intent = Intent(context, ReadBookActivity::class.java) val intent = Intent(context, ReadBookActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtra("readAloud", true) intent.putExtra("readAloud", true)
context.startActivity(intent) context.startActivity(intent)
} else { }
postEvent(Bus.READ_ALOUD_BUTTON, true) }
}
} }
} }
} }

@ -24,6 +24,7 @@ import io.legado.app.help.IntentHelp
import io.legado.app.help.MediaHelp import io.legado.app.help.MediaHelp
import io.legado.app.receiver.MediaButtonReceiver import io.legado.app.receiver.MediaButtonReceiver
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.utils.LogUtils
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
@ -73,7 +74,7 @@ class AudioPlayService : BaseService(),
Action.play -> { Action.play -> {
title = intent.getStringExtra("title") ?: "" title = intent.getStringExtra("title") ?: ""
subtitle = intent.getStringExtra("subtitle") ?: "" subtitle = intent.getStringExtra("subtitle") ?: ""
position = intent.getIntExtra("pageIndex", 0) position = intent.getIntExtra("position", 0)
play(intent.getStringExtra("url")) play(intent.getStringExtra("url"))
} }
Action.pause -> pause(true) Action.pause -> pause(true)
@ -107,8 +108,10 @@ class AudioPlayService : BaseService(),
mediaPlayer.setDataSource(url) mediaPlayer.setDataSource(url)
mediaPlayer.prepareAsync() mediaPlayer.prepareAsync()
} catch (e: Exception) { } catch (e: Exception) {
LogUtils.d("AudioPlayService", e.localizedMessage + " " + url)
launch { launch {
toast(e.localizedMessage + " " + url) toast(e.localizedMessage + " " + url)
stopSelf()
} }
} }
} }
@ -153,6 +156,9 @@ class AudioPlayService : BaseService(),
} }
override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean { override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean {
launch {
toast("error: $what $extra")
}
return true return true
} }

@ -13,7 +13,7 @@ object AudioPlay {
intent.putExtra("title", title) intent.putExtra("title", title)
intent.putExtra("subtitle", subtitle) intent.putExtra("subtitle", subtitle)
intent.putExtra("url", url) intent.putExtra("url", url)
intent.putExtra("dataKey", position) intent.putExtra("position", position)
context.startService(intent) context.startService(intent)
} }

@ -1,23 +1,38 @@
package io.legado.app.ui.audio package io.legado.app.ui.audio
import android.app.Activity
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.widget.SeekBar import android.widget.SeekBar
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.request.RequestOptions
import io.legado.app.BuildConfig
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.Bus import io.legado.app.constant.Bus
import io.legado.app.constant.Status import io.legado.app.constant.Status
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BlurTransformation import io.legado.app.help.BlurTransformation
import io.legado.app.help.ImageLoader import io.legado.app.help.ImageLoader
import io.legado.app.help.storage.Backup
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton
import io.legado.app.service.help.AudioPlay import io.legado.app.service.help.AudioPlay
import io.legado.app.ui.chapterlist.ChapterListActivity
import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.applyTint
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.observeEvent import io.legado.app.utils.observeEvent
import kotlinx.android.synthetic.main.activity_audio_play.* import kotlinx.android.synthetic.main.activity_audio_play.*
import kotlinx.android.synthetic.main.view_title_bar.* import kotlinx.android.synthetic.main.view_title_bar.*
import org.apache.commons.lang3.time.DateFormatUtils
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
import org.jetbrains.anko.sdk27.listeners.onLongClick import org.jetbrains.anko.sdk27.listeners.onLongClick
import org.jetbrains.anko.startActivity
import org.jetbrains.anko.startActivityForResult
class AudioPlayActivity : VMBaseActivity<AudioPlayViewModel>(R.layout.activity_audio_play), class AudioPlayActivity : VMBaseActivity<AudioPlayViewModel>(R.layout.activity_audio_play),
AudioPlayViewModel.CallBack { AudioPlayViewModel.CallBack {
@ -25,13 +40,15 @@ class AudioPlayActivity : VMBaseActivity<AudioPlayViewModel>(R.layout.activity_a
override val viewModel: AudioPlayViewModel override val viewModel: AudioPlayViewModel
get() = getViewModel(AudioPlayViewModel::class.java) get() = getViewModel(AudioPlayViewModel::class.java)
private var requestCodeChapter = 8461
private var adjustProgress = false private var adjustProgress = false
private var status = Status.STOP private var status = Status.STOP
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
viewModel.callBack = this viewModel.callBack = this
viewModel.bookData.observe(this, Observer { upView(it) }) viewModel.titleData.observe(this, Observer { title_bar.title = it })
viewModel.coverData.observe(this, Observer { upCover(it) })
viewModel.initData(intent) viewModel.initData(intent)
initView() initView()
} }
@ -45,14 +62,14 @@ class AudioPlayActivity : VMBaseActivity<AudioPlayViewModel>(R.layout.activity_a
true true
} }
iv_skip_next.onClick { iv_skip_next.onClick {
viewModel.moveToNext()
} }
iv_skip_previous.onClick { iv_skip_previous.onClick {
viewModel.moveToPrev()
} }
player_progress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { player_progress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
tv_dur_time.text = DateFormatUtils.format(progress.toLong(), "mm:ss")
} }
override fun onStartTrackingTouch(seekBar: SeekBar?) { override fun onStartTrackingTouch(seekBar: SeekBar?) {
@ -64,43 +81,89 @@ class AudioPlayActivity : VMBaseActivity<AudioPlayViewModel>(R.layout.activity_a
AudioPlay.adjustProgress(this@AudioPlayActivity, player_progress.progress) AudioPlay.adjustProgress(this@AudioPlayActivity, player_progress.progress)
} }
}) })
iv_chapter.onClick {
viewModel.book?.let {
startActivityForResult<ChapterListActivity>(
requestCodeChapter,
Pair("bookUrl", it.bookUrl)
)
}
}
} }
private fun upView(book: Book) { private fun upCover(path: String) {
actionBar?.title = book.name ImageLoader.load(this, path)
ImageLoader.load(this, book.getDisplayCover())
.placeholder(R.drawable.image_cover_default) .placeholder(R.drawable.image_cover_default)
.error(R.drawable.image_cover_default) .error(R.drawable.image_cover_default)
.centerCrop() .centerCrop()
.setAsDrawable(iv_cover) .into(iv_cover)
ImageLoader.load(this, book.getDisplayCover()) ImageLoader.load(this, path)
.placeholder(R.drawable.image_cover_default) .thumbnail(defaultCover())
.error(R.drawable.image_cover_default)
.centerCrop() .centerCrop()
.bitmapTransform(BlurTransformation(this, 25)) .apply(RequestOptions.bitmapTransform(BlurTransformation(this, 25)))
.setAsDrawable(iv_bg) .into(iv_bg)
}
private fun defaultCover(): RequestBuilder<Drawable> {
return ImageLoader.load(this, R.drawable.image_cover_default)
.apply(RequestOptions.bitmapTransform(BlurTransformation(this, 25)))
} }
private fun playButton() { private fun playButton() {
when (status) { when (status) {
Status.PLAY -> AudioPlay.pause(this) Status.PLAY -> AudioPlay.pause(this)
Status.PAUSE -> AudioPlay.resume(this) Status.PAUSE -> AudioPlay.resume(this)
else -> viewModel.bookData.value?.let { else -> viewModel.loadContent(viewModel.durChapterIndex)
viewModel.loadContent(it, viewModel.durChapterIndex)
}
} }
} }
override fun contentLoadFinish(bookChapter: BookChapter, content: String) { override fun contentLoadFinish(bookChapter: BookChapter, content: String) {
AudioPlay.play( AudioPlay.play(
this, this,
viewModel.bookData.value?.name, viewModel.book?.name,
bookChapter.title, bookChapter.title,
content, content,
viewModel.durPageIndex viewModel.durPageIndex
) )
viewModel.bookData.value?.let { viewModel.loadContent(viewModel.durChapterIndex + 1)
viewModel.loadContent(it, viewModel.durChapterIndex + 1) }
override fun finish() {
viewModel.book?.let {
if (!viewModel.inBookshelf) {
this.alert(title = getString(R.string.add_to_shelf)) {
message = getString(R.string.check_add_bookshelf, it.name)
okButton { viewModel.inBookshelf = true }
noButton { viewModel.removeFromBookshelf { super.finish() } }
}.show().applyTint()
} else {
if (status == Status.PLAY) {
startActivity<MainActivity>()
} else {
super.finish()
}
}
} ?: super.finish()
}
override fun onDestroy() {
super.onDestroy()
AudioPlay.stop(this)
if (!BuildConfig.DEBUG) {
Backup.autoBackup()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
requestCodeChapter -> data?.getIntExtra("index", viewModel.durChapterIndex)?.let {
if (it != viewModel.durChapterIndex) {
viewModel.loadContent(it)
}
}
}
} }
} }
@ -120,10 +183,15 @@ class AudioPlayActivity : VMBaseActivity<AudioPlayViewModel>(R.layout.activity_a
} }
} }
observeEvent<Int>(Bus.AUDIO_PROGRESS) { observeEvent<Int>(Bus.AUDIO_PROGRESS) {
viewModel.durPageIndex = it
if (!adjustProgress) player_progress.progress = it if (!adjustProgress) player_progress.progress = it
tv_dur_time.text = DateFormatUtils.format(it.toLong(), "mm:ss")
viewModel.saveProgress()
} }
observeEvent<Int>(Bus.AUDIO_SIZE) { observeEvent<Int>(Bus.AUDIO_SIZE) {
player_progress.max = it player_progress.max = it
tv_all_time.text = DateFormatUtils.format(it.toLong(), "mm:ss")
} }
} }
} }

@ -6,7 +6,6 @@ import androidx.lifecycle.MutableLiveData
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
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
@ -16,13 +15,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class AudioPlayViewModel(application: Application) : BaseViewModel(application) { class AudioPlayViewModel(application: Application) : BaseViewModel(application) {
var titleData = MutableLiveData<String>()
var coverData = MutableLiveData<String>()
var book: Book? = null
var inBookshelf = false var inBookshelf = false
var bookData = MutableLiveData<Book>() private var chapterSize = 0
var chapterSize = 0
var callBack: CallBack? = null var callBack: CallBack? = null
var durChapterIndex = 0 var durChapterIndex = 0
var durPageIndex = 0 var durPageIndex = 0
var isLocalBook = true
var webBook: WebBook? = null var webBook: WebBook? = null
private val loadingChapters = arrayListOf<Int>() private val loadingChapters = arrayListOf<Int>()
@ -30,16 +30,16 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
execute { execute {
inBookshelf = intent.getBooleanExtra("inBookshelf", true) inBookshelf = intent.getBooleanExtra("inBookshelf", true)
val bookUrl = intent.getStringExtra("bookUrl") val bookUrl = intent.getStringExtra("bookUrl")
val book = if (!bookUrl.isNullOrEmpty()) { book = if (!bookUrl.isNullOrEmpty()) {
App.db.bookDao().getBook(bookUrl) App.db.bookDao().getBook(bookUrl)
} else { } else {
App.db.bookDao().lastReadBook App.db.bookDao().lastReadBook
} }
book?.let { book?.let { book ->
titleData.postValue(book.name)
coverData.postValue(book.getDisplayCover())
durChapterIndex = book.durChapterIndex durChapterIndex = book.durChapterIndex
durPageIndex = book.durChapterPos durPageIndex = book.durChapterPos
isLocalBook = book.origin == BookType.local
bookData.postValue(book)
App.db.bookSourceDao().getBookSource(book.origin)?.let { App.db.bookSourceDao().getBookSource(book.origin)?.let {
webBook = WebBook(it) webBook = WebBook(it)
} }
@ -57,7 +57,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
chapterSize = count chapterSize = count
} }
} }
saveRead(book) saveRead()
} }
} }
@ -96,22 +96,25 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun loadContent(book: Book, index: Int) { fun loadContent(index: Int) {
book?.let { book ->
if (addLoading(index)) { if (addLoading(index)) {
execute { execute {
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)
removeLoading(chapter.index) removeLoading(chapter.index)
} ?: download(book, chapter) } ?: download(chapter)
} ?: removeLoading(index) } ?: removeLoading(index)
}.onError { }.onError {
removeLoading(index) removeLoading(index)
} }
} }
} }
}
private fun download(book: Book, chapter: BookChapter) { private fun download(chapter: BookChapter) {
book?.let { book ->
webBook?.getContent(book, chapter, scope = this) webBook?.getContent(book, chapter, scope = this)
?.onSuccess(Dispatchers.IO) { content -> ?.onSuccess(Dispatchers.IO) { content ->
if (content.isNullOrEmpty()) { if (content.isNullOrEmpty()) {
@ -127,6 +130,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
removeLoading(chapter.index) removeLoading(chapter.index)
} }
} }
}
private fun addLoading(index: Int): Boolean { private fun addLoading(index: Int): Boolean {
synchronized(this) { synchronized(this) {
@ -148,23 +152,23 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun changeTo(book: Book) { fun changeTo(book1: Book) {
execute { execute {
bookData.value?.let { book?.let {
App.db.bookDao().delete(it.bookUrl) App.db.bookDao().delete(it.bookUrl)
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
} }
App.db.bookDao().insert(book) App.db.bookDao().insert(book1)
bookData.postValue(book) book = book1
App.db.bookSourceDao().getBookSource(book.origin)?.let { App.db.bookSourceDao().getBookSource(book1.origin)?.let {
webBook = WebBook(it) webBook = WebBook(it)
} }
if (book.tocUrl.isEmpty()) { if (book1.tocUrl.isEmpty()) {
loadBookInfo(book) { upChangeDurChapterIndex(book, it) } loadBookInfo(book1) { upChangeDurChapterIndex(book1, it) }
} else { } else {
loadChapterList(book) { upChangeDurChapterIndex(book, it) } loadChapterList(book1) { upChangeDurChapterIndex(book1, it) }
} }
} }
} }
@ -184,20 +188,29 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun moveToPrev() {
if (durChapterIndex > 0) {
durChapterIndex--
durPageIndex = 0
book?.durChapterIndex = durChapterIndex
saveRead()
loadContent(durChapterIndex)
}
}
fun moveToNext() { fun moveToNext() {
if (durChapterIndex < chapterSize - 1) { if (durChapterIndex < chapterSize - 1) {
durChapterIndex++ durChapterIndex++
bookData.value?.let { durPageIndex = 0
it.durChapterIndex = durChapterIndex book?.durChapterIndex = durChapterIndex
saveRead(it) saveRead()
loadContent(it, durChapterIndex) loadContent(durChapterIndex)
}
} else { } else {
AudioPlay.stop(context) AudioPlay.stop(context)
} }
} }
fun saveRead(book: Book? = bookData.value) { private fun saveRead() {
execute { execute {
book?.let { book -> book?.let { book ->
book.lastCheckCount = 0 book.lastCheckCount = 0
@ -209,6 +222,24 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun saveProgress() {
execute {
book?.let {
App.db.bookDao().upProgress(it.bookUrl, durPageIndex)
}
}
}
fun removeFromBookshelf(success: (() -> Unit)?) {
execute {
book?.let {
App.db.bookDao().delete(it.bookUrl)
}
}.onSuccess {
success?.invoke()
}
}
interface CallBack { interface CallBack {
fun contentLoadFinish(bookChapter: BookChapter, content: String) fun contentLoadFinish(bookChapter: BookChapter, content: String)
} }

@ -89,7 +89,7 @@ class BookInfoActivity : VMBaseActivity<BookInfoViewModel>(R.layout.activity_boo
.placeholder(R.drawable.image_cover_default) .placeholder(R.drawable.image_cover_default)
.error(R.drawable.image_cover_default) .error(R.drawable.image_cover_default)
.centerCrop() .centerCrop()
.setAsDrawable(iv_cover) .into(iv_cover)
} }
val kinds = book.getKindList() val kinds = book.getKindList()
if (kinds.isEmpty()) { if (kinds.isEmpty()) {

@ -64,6 +64,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
override val viewModel: ReadBookViewModel override val viewModel: ReadBookViewModel
get() = getViewModel(ReadBookViewModel::class.java) get() = getViewModel(ReadBookViewModel::class.java)
private val requestCodeChapterList = 568
private val requestCodeEditSource = 111 private val requestCodeEditSource = 111
private var timeElectricityReceiver: TimeElectricityReceiver? = null private var timeElectricityReceiver: TimeElectricityReceiver? = null
override var readAloudStatus = Status.STOP override var readAloudStatus = Status.STOP
@ -73,7 +74,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
initView() initView()
viewModel.callBack = this viewModel.callBack = this
viewModel.bookData.observe(this, Observer { title_bar.title = it.name }) viewModel.titleDate.observe(this, Observer { title_bar.title = it })
viewModel.initData(intent) viewModel.initData(intent)
} }
@ -140,12 +141,12 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
when (item.itemId) { when (item.itemId) {
R.id.menu_change_source -> { R.id.menu_change_source -> {
read_menu.runMenuOut() read_menu.runMenuOut()
viewModel.bookData.value?.let { viewModel.book?.let {
ChangeSourceDialog.show(supportFragmentManager, it.name, it.author) ChangeSourceDialog.show(supportFragmentManager, it.name, it.author)
} }
} }
R.id.menu_refresh -> { R.id.menu_refresh -> {
viewModel.bookData.value?.let { viewModel.book?.let {
viewModel.curTextChapter = null viewModel.curTextChapter = null
page_view.upContent() page_view.upContent()
viewModel.refreshContent(it) viewModel.refreshContent(it)
@ -198,7 +199,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_BACK -> { KeyEvent.KEYCODE_BACK -> {
page_view.snackbar("转到后台", "确定") { page_view.snackbar(R.string.to_backstage, R.string.ok) {
startActivity<MainActivity>() startActivity<MainActivity>()
} }
return true return true
@ -254,20 +255,16 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
* 加载章节内容 * 加载章节内容
*/ */
override fun loadContent() { override fun loadContent() {
viewModel.bookData.value?.let { viewModel.loadContent(viewModel.durChapterIndex)
viewModel.loadContent(it, viewModel.durChapterIndex) viewModel.loadContent(viewModel.durChapterIndex + 1)
viewModel.loadContent(it, viewModel.durChapterIndex + 1) viewModel.loadContent(viewModel.durChapterIndex - 1)
viewModel.loadContent(it, viewModel.durChapterIndex - 1)
}
} }
/** /**
* 加载章节内容, index章节序号 * 加载章节内容, index章节序号
*/ */
override fun loadContent(index: Int) { override fun loadContent(index: Int) {
viewModel.bookData.value?.let { viewModel.loadContent(index)
viewModel.loadContent(it, index)
}
} }
/** /**
@ -336,10 +333,10 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
} }
override val curOrigin: String? override val curOrigin: String?
get() = viewModel.bookData.value?.origin get() = viewModel.book?.origin
override val oldBook: Book? override val oldBook: Book?
get() = viewModel.bookData.value get() = viewModel.book
override fun changeTo(book: Book) { override fun changeTo(book: Book) {
viewModel.changeTo(book) viewModel.changeTo(book)
@ -435,8 +432,11 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
} }
override fun openChapterList() { override fun openChapterList() {
viewModel.bookData.value?.let { viewModel.book?.let {
startActivity<ChapterListActivity>(Pair("bookUrl", it.bookUrl)) startActivityForResult<ChapterListActivity>(
requestCodeChapterList,
Pair("bookUrl", it.bookUrl)
)
} }
} }
@ -471,7 +471,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
* 朗读 * 朗读
*/ */
private fun readAloud(play: Boolean = true) { private fun readAloud(play: Boolean = true) {
val book = viewModel.bookData.value val book = viewModel.book
val textChapter = viewModel.curTextChapter val textChapter = viewModel.curTextChapter
if (book != null && textChapter != null) { if (book != null && textChapter != null) {
val key = IntentDataHelp.putData(textChapter) val key = IntentDataHelp.putData(textChapter)
@ -510,12 +510,16 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
when (requestCode) { when (requestCode) {
requestCodeEditSource -> viewModel.upBookSource() requestCodeEditSource -> viewModel.upBookSource()
requestCodeChapterList ->
data?.getIntExtra("index", viewModel.durChapterIndex)?.let {
viewModel.openChapter(it)
}
} }
} }
} }
override fun finish() { override fun finish() {
viewModel.bookData.value?.let { viewModel.book?.let {
if (!viewModel.inBookshelf) { if (!viewModel.inBookshelf) {
this.alert(title = getString(R.string.add_to_shelf)) { this.alert(title = getString(R.string.add_to_shelf)) {
message = getString(R.string.check_add_bookshelf, it.name) message = getString(R.string.check_add_bookshelf, it.name)
@ -552,7 +556,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
observeEvent<String>(Bus.TIME_CHANGED) { page_view.upTime() } observeEvent<String>(Bus.TIME_CHANGED) { page_view.upTime() }
observeEvent<Int>(Bus.BATTERY_CHANGED) { page_view.upBattery(it) } observeEvent<Int>(Bus.BATTERY_CHANGED) { page_view.upBattery(it) }
observeEvent<BookChapter>(Bus.OPEN_CHAPTER) { observeEvent<BookChapter>(Bus.OPEN_CHAPTER) {
viewModel.openChapter(it) viewModel.openChapter(it.index)
page_view.upContent() page_view.upContent()
} }
observeEvent<Boolean>(Bus.READ_ALOUD_BUTTON) { observeEvent<Boolean>(Bus.READ_ALOUD_BUTTON) {

@ -21,8 +21,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class ReadBookViewModel(application: Application) : BaseViewModel(application) { class ReadBookViewModel(application: Application) : BaseViewModel(application) {
var titleDate = MutableLiveData<String>()
var book: Book? = null
var inBookshelf = false var inBookshelf = false
var bookData = MutableLiveData<Book>()
var chapterSize = 0 var chapterSize = 0
var callBack: CallBack? = null var callBack: CallBack? = null
var durChapterIndex = 0 var durChapterIndex = 0
@ -38,22 +39,23 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
execute { execute {
inBookshelf = intent.getBooleanExtra("inBookshelf", true) inBookshelf = intent.getBooleanExtra("inBookshelf", true)
IntentDataHelp.getData<Book>(intent.getStringExtra("key"))?.let { IntentDataHelp.getData<Book>(intent.getStringExtra("key"))?.let {
setBook(it) initBook(it)
} ?: intent.getStringExtra("bookUrl")?.let { } ?: intent.getStringExtra("bookUrl")?.let {
App.db.bookDao().getBook(it)?.let { book -> App.db.bookDao().getBook(it)?.let { book ->
setBook(book) initBook(book)
} }
} ?: App.db.bookDao().lastReadBook?.let { } ?: App.db.bookDao().lastReadBook?.let {
setBook(it) initBook(it)
} }
} }
} }
private fun setBook(book: Book) { private fun initBook(book: Book) {
this.book = book
titleDate.postValue(book.name)
durChapterIndex = book.durChapterIndex durChapterIndex = book.durChapterIndex
durPageIndex = book.durChapterPos durPageIndex = book.durChapterPos
isLocalBook = book.origin == BookType.local isLocalBook = book.origin == BookType.local
bookData.postValue(book)
App.db.bookSourceDao().getBookSource(book.origin)?.let { App.db.bookSourceDao().getBookSource(book.origin)?.let {
webBook = WebBook(it) webBook = WebBook(it)
} }
@ -72,7 +74,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
callBack?.loadContent() callBack?.loadContent()
} }
if (inBookshelf) { if (inBookshelf) {
saveRead(book) saveRead()
} }
} }
@ -117,19 +119,17 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
prevTextChapter = curTextChapter prevTextChapter = curTextChapter
curTextChapter = nextTextChapter curTextChapter = nextTextChapter
nextTextChapter = null nextTextChapter = null
bookData.value?.let { book?.let {
if (curTextChapter == null) { if (curTextChapter == null) {
loadContent(it, durChapterIndex) loadContent(durChapterIndex)
} else if (upContent) { } else if (upContent) {
callBack?.upContent() callBack?.upContent()
} }
loadContent(it, durChapterIndex.plus(1)) loadContent(durChapterIndex.plus(1))
launch(IO) { launch(IO) {
for (i in 2..10) { for (i in 2..10) {
delay(100) delay(100)
bookData.value?.let { book -> download(durChapterIndex + i)
download(book, durChapterIndex + i)
}
} }
} }
} }
@ -140,47 +140,48 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
nextTextChapter = curTextChapter nextTextChapter = curTextChapter
curTextChapter = prevTextChapter curTextChapter = prevTextChapter
prevTextChapter = null prevTextChapter = null
bookData.value?.let { book?.let {
if (curTextChapter == null) { if (curTextChapter == null) {
loadContent(it, durChapterIndex) loadContent(durChapterIndex)
} else if (upContent) { } else if (upContent) {
callBack?.upContent() callBack?.upContent()
} }
loadContent(it, durChapterIndex.minus(1)) loadContent(durChapterIndex.minus(1))
launch(IO) { launch(IO) {
for (i in -5..-2) { for (i in -5..-2) {
delay(100) delay(100)
bookData.value?.let { book -> download(durChapterIndex + i)
download(book, durChapterIndex + i)
}
} }
} }
} }
} }
fun loadContent(book: Book, index: Int) { fun loadContent(index: Int) {
book?.let { book ->
if (addLoading(index)) { if (addLoading(index)) {
execute { execute {
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)
removeLoading(chapter.index) removeLoading(chapter.index)
} ?: download(book, chapter) } ?: download(chapter)
} ?: removeLoading(index) } ?: removeLoading(index)
}.onError { }.onError {
removeLoading(index) removeLoading(index)
} }
} }
} }
}
private fun download(book: Book, index: Int) { private fun download(index: Int) {
book?.let { book ->
if (addLoading(index)) { if (addLoading(index)) {
execute { execute {
App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter -> App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter ->
if (BookHelp.hasContent(book, chapter)) { if (BookHelp.hasContent(book, chapter)) {
removeLoading(chapter.index) removeLoading(chapter.index)
} else { } else {
download(book, chapter) download(chapter)
} }
} ?: removeLoading(index) } ?: removeLoading(index)
}.onError { }.onError {
@ -188,8 +189,10 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
} }
} }
} }
}
private fun download(book: Book, chapter: BookChapter) { private fun download(chapter: BookChapter) {
book?.let { book ->
webBook?.getContent(book, chapter, scope = this) webBook?.getContent(book, chapter, scope = this)
?.onSuccess(IO) { content -> ?.onSuccess(IO) { content ->
if (content.isNullOrEmpty()) { if (content.isNullOrEmpty()) {
@ -205,6 +208,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
removeLoading(chapter.index) removeLoading(chapter.index)
} }
} }
}
private fun addLoading(index: Int): Boolean { private fun addLoading(index: Int): Boolean {
synchronized(this) { synchronized(this) {
@ -225,19 +229,19 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
if (chapter.index in durChapterIndex - 1..durChapterIndex + 1) { if (chapter.index in durChapterIndex - 1..durChapterIndex + 1) {
val c = BookHelp.disposeContent( val c = BookHelp.disposeContent(
chapter.title, chapter.title,
bookData.value!!.name, book!!.name,
webBook?.bookSource?.bookSourceUrl, webBook?.bookSource?.bookSourceUrl,
content, content,
bookData.value!!.useReplaceRule book!!.useReplaceRule
) )
callBack?.contentLoadFinish(chapter, c) callBack?.contentLoadFinish(chapter, c)
} }
} }
} }
fun changeTo(book: Book) { fun changeTo(book1: Book) {
execute { execute {
bookData.value?.let { book?.let {
App.db.bookDao().delete(it.bookUrl) App.db.bookDao().delete(it.bookUrl)
} }
prevTextChapter = null prevTextChapter = null
@ -246,15 +250,15 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
withContext(Main) { withContext(Main) {
callBack?.upContent() callBack?.upContent()
} }
App.db.bookDao().insert(book) App.db.bookDao().insert(book1)
bookData.postValue(book) book = book1
App.db.bookSourceDao().getBookSource(book.origin)?.let { App.db.bookSourceDao().getBookSource(book1.origin)?.let {
webBook = WebBook(it) webBook = WebBook(it)
} }
if (book.tocUrl.isEmpty()) { if (book1.tocUrl.isEmpty()) {
loadBookInfo(book) { upChangeDurChapterIndex(book, it) } loadBookInfo(book1) { upChangeDurChapterIndex(book1, it) }
} else { } else {
loadChapterList(book) { upChangeDurChapterIndex(book, it) } loadChapterList(book1) { upChangeDurChapterIndex(book1, it) }
} }
} }
} }
@ -279,19 +283,19 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
} }
} }
fun openChapter(chapter: BookChapter) { fun openChapter(index: Int) {
prevTextChapter = null prevTextChapter = null
curTextChapter = null curTextChapter = null
nextTextChapter = null nextTextChapter = null
if (chapter.index != durChapterIndex) { if (index != durChapterIndex) {
durChapterIndex = chapter.index durChapterIndex = index
durPageIndex = 0 durPageIndex = 0
} }
saveRead() saveRead()
callBack?.loadContent() callBack?.loadContent()
} }
fun saveRead(book: Book? = bookData.value) { fun saveRead() {
execute { execute {
book?.let { book -> book?.let { book ->
book.lastCheckCount = 0 book.lastCheckCount = 0
@ -308,7 +312,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
fun removeFromBookshelf(success: (() -> Unit)?) { fun removeFromBookshelf(success: (() -> Unit)?) {
execute { execute {
bookData.value?.let { book?.let {
App.db.bookDao().delete(it.bookUrl) App.db.bookDao().delete(it.bookUrl)
} }
}.onSuccess { }.onSuccess {
@ -318,7 +322,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
fun upBookSource() { fun upBookSource() {
execute { execute {
bookData.value?.let { book -> book?.let { book ->
App.db.bookSourceDao().getBookSource(book.origin)?.let { App.db.bookSourceDao().getBookSource(book.origin)?.let {
webBook = WebBook(it) webBook = WebBook(it)
} }
@ -330,7 +334,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
execute { execute {
App.db.bookChapterDao().getChapter(book.bookUrl, durChapterIndex)?.let { chapter -> App.db.bookChapterDao().getChapter(book.bookUrl, durChapterIndex)?.let { chapter ->
BookHelp.delContent(book, chapter) BookHelp.delContent(book, chapter)
loadContent(book, durChapterIndex) loadContent(durChapterIndex)
} }
} }
} }

@ -153,7 +153,7 @@ class BgTextConfigDialog : DialogFragment() {
with(holder.itemView) { with(holder.itemView) {
ImageLoader.load(context, context.assets.open("bg/$item").readBytes()) ImageLoader.load(context, context.assets.open("bg/$item").readBytes())
.centerCrop() .centerCrop()
.setAsBitmap(iv_bg) .into(iv_bg)
tv_name.text = item.substringBeforeLast(".") tv_name.text = item.substringBeforeLast(".")
this.onClick { this.onClick {
ReadBookConfig.getConfig().setBg(1, item) ReadBookConfig.getConfig().setBg(1, item)

@ -248,7 +248,7 @@ class ReadStyleDialog : DialogFragment() {
} }
ReadBookConfig.getConfig(i).apply { ReadBookConfig.getConfig(i).apply {
when (bgType()) { when (bgType()) {
2 -> ImageLoader.load(requireContext(), bgStr()).centerCrop().setAsDrawable(iv) 2 -> ImageLoader.load(requireContext(), bgStr()).centerCrop().into(iv)
else -> iv.setImageDrawable(bgDrawable(100, 150)) else -> iv.setImageDrawable(bgDrawable(100, 150))
} }
} }

@ -72,7 +72,7 @@ class SearchAdapter(context: Context, val callBack: CallBack) :
.placeholder(R.drawable.image_cover_default) .placeholder(R.drawable.image_cover_default)
.error(R.drawable.image_cover_default) .error(R.drawable.image_cover_default)
.centerCrop() .centerCrop()
.setAsDrawable(iv_cover) .into(iv_cover)
} }
onClick { onClick {
callBack.showBookInfo(searchBook.name, searchBook.author) callBack.showBookInfo(searchBook.name, searchBook.author)
@ -89,7 +89,7 @@ class SearchAdapter(context: Context, val callBack: CallBack) :
.placeholder(R.drawable.image_cover_default) .placeholder(R.drawable.image_cover_default)
.error(R.drawable.image_cover_default) .error(R.drawable.image_cover_default)
.centerCrop() .centerCrop()
.setAsDrawable(iv_cover) .into(iv_cover)
} }
3 -> { 3 -> {
val kinds = searchBook.getKindList() val kinds = searchBook.getKindList()
@ -136,6 +136,8 @@ class SearchAdapter(context: Context, val callBack: CallBack) :
} }
5 -> tv_introduce.text = 5 -> tv_introduce.text =
context.getString(R.string.intro_show, searchBook.intro) context.getString(R.string.intro_show, searchBook.intro)
else -> {
}
} }
} }
} }

@ -27,10 +27,11 @@ class ChapterListActivity : VMBaseActivity<ChapterListViewModel>(R.layout.activi
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
viewModel.bookUrl = intent.getStringExtra("bookUrl") viewModel.bookUrl = intent.getStringExtra("bookUrl")
viewModel.loadBook() viewModel.loadBook {
view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager) view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager)
tab_layout.setupWithViewPager(view_pager) tab_layout.setupWithViewPager(view_pager)
} }
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.search_view, menu) menuInflater.inflate(R.menu.search_view, menu)

@ -1,5 +1,7 @@
package io.legado.app.ui.chapterlist package io.legado.app.ui.chapterlist
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
@ -9,11 +11,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseFragment import io.legado.app.base.VMBaseFragment
import io.legado.app.constant.Bus
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.getViewModelOfActivity
import io.legado.app.utils.postEvent
import kotlinx.android.synthetic.main.fragment_chapter_list.* import kotlinx.android.synthetic.main.fragment_chapter_list.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
@ -45,14 +45,13 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
} }
private fun initData() { private fun initData() {
viewModel.bookDate.observe(viewLifecycleOwner, Observer {
loadBookFinish(it)
})
viewModel.bookUrl?.let { bookUrl -> viewModel.bookUrl?.let { bookUrl ->
App.db.bookChapterDao().observeByBook(bookUrl).observe(viewLifecycleOwner, Observer { App.db.bookChapterDao().observeByBook(bookUrl).observe(viewLifecycleOwner, Observer {
adapter.setItems(it) adapter.setItems(it)
viewModel.bookDate.value?.let { book -> viewModel.book?.let {
loadBookFinish(book) durChapterIndex = it.durChapterIndex
tv_current_chapter_info.text = it.durChapterTitle
recycler_view.scrollToPosition(durChapterIndex)
} }
}) })
} }
@ -66,28 +65,22 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
} }
} }
tv_current_chapter_info.onClick { tv_current_chapter_info.onClick {
viewModel.bookDate.value?.let { viewModel.book?.let {
recycler_view.scrollToPosition(it.durChapterIndex) recycler_view.scrollToPosition(it.durChapterIndex)
} }
} }
} }
private fun loadBookFinish(book: Book) {
durChapterIndex = book.durChapterIndex
tv_current_chapter_info.text = book.durChapterTitle
recycler_view.scrollToPosition(durChapterIndex)
}
override fun durChapterIndex(): Int { override fun durChapterIndex(): Int {
return durChapterIndex return durChapterIndex
} }
override fun openChapter(bookChapter: BookChapter) { override fun openChapter(bookChapter: BookChapter) {
postEvent(Bus.OPEN_CHAPTER, bookChapter) activity?.setResult(RESULT_OK, Intent().putExtra("index", bookChapter.index))
activity?.finish() activity?.finish()
} }
override fun book(): Book? { override fun book(): Book? {
return viewModel.bookDate.value return viewModel.book
} }
} }

@ -2,23 +2,21 @@ package io.legado.app.ui.chapterlist
import android.app.Application import android.app.Application
import androidx.lifecycle.MutableLiveData
import io.legado.app.App import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
class ChapterListViewModel(application: Application) : BaseViewModel(application) { class ChapterListViewModel(application: Application) : BaseViewModel(application) {
var bookDate = MutableLiveData<Book>()
var bookUrl: String? = null var bookUrl: String? = null
var book: Book? = null
fun loadBook() { fun loadBook(success: () -> Unit) {
execute { execute {
bookUrl?.let { bookUrl?.let {
App.db.bookDao().getBook(it)?.let { book -> book = App.db.bookDao().getBook(it)
bookDate.postValue(book)
}
} }
}.onSuccess {
success()
} }
} }
} }

@ -63,7 +63,7 @@ class ExploreShowAdapter(context: Context, val callBack: CallBack) :
.placeholder(R.drawable.image_cover_default) .placeholder(R.drawable.image_cover_default)
.error(R.drawable.image_cover_default) .error(R.drawable.image_cover_default)
.centerCrop() .centerCrop()
.setAsDrawable(iv_cover) .into(iv_cover)
} }
onClick { onClick {
callBack.showBookInfo(item.toBook()) callBack.showBookInfo(item.toBook())

@ -30,7 +30,7 @@ class BooksAdapter(context: Context, private val callBack: CallBack) :
.placeholder(R.drawable.image_cover_default) .placeholder(R.drawable.image_cover_default)
.error(R.drawable.image_cover_default) .error(R.drawable.image_cover_default)
.centerCrop() .centerCrop()
.setAsDrawable(iv_cover) .into(iv_cover)
} }
onClick { callBack.open(item) } onClick { callBack.open(item) }
onLongClick { onLongClick {

@ -73,6 +73,7 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca
kindList.map { kind -> kindList.map { kind ->
val tv = LayoutInflater.from(context) val tv = LayoutInflater.from(context)
.inflate(R.layout.item_text, gl_child, false) .inflate(R.layout.item_text, gl_child, false)
gl_child.addView(tv)
tv.text_view.text = kind.title tv.text_view.text = kind.title
tv.text_view.onClick { tv.text_view.onClick {
kind.url?.let { kindUrl -> kind.url?.let { kindUrl ->
@ -83,7 +84,6 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca
) )
} }
} }
gl_child.addView(tv)
} }
} }
}.onFinally { }.onFinally {

@ -19,7 +19,7 @@ class RssAdapter(context: Context, val callBack: CallBack) :
.centerCrop() .centerCrop()
.placeholder(R.drawable.image_rss) .placeholder(R.drawable.image_rss)
.error(R.drawable.image_rss) .error(R.drawable.image_rss)
.setAsBitmap(iv_icon) .into(iv_icon)
onClick { callBack.openRss(item) } onClick { callBack.openRss(item) }
} }
} }

@ -28,7 +28,7 @@ class RssArticlesAdapter(context: Context, val callBack: CallBack) :
} else { } else {
image_view.visible() image_view.visible()
ImageLoader.load(context, item.image) ImageLoader.load(context, item.image)
.setAsBitmap(image_view) .into(image_view)
} }
if (item.read) { if (item.read) {
tv_title.textColorResource = R.color.tv_text_summary tv_title.textColorResource = R.color.tv_text_summary

@ -33,10 +33,8 @@
android:id="@+id/iv_cover" android:id="@+id/iv_cover"
android:layout_width="260dp" android:layout_width="260dp"
android:layout_height="260dp" android:layout_height="260dp"
android:layout_gravity="center"
android:contentDescription="@string/img_cover" android:contentDescription="@string/img_cover"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/image_cover_default"
app:civ_border_width="2dp" app:civ_border_width="2dp"
app:civ_border_color="@color/colorAccent" app:civ_border_color="@color/colorAccent"
app:layout_constraintBottom_toTopOf="@+id/ll_player_progress" app:layout_constraintBottom_toTopOf="@+id/ll_player_progress"
@ -50,27 +48,33 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="5dp" android:paddingStart="16dp"
android:paddingEnd="5dp" android:paddingEnd="16dp"
app:layout_constraintBottom_toTopOf="@+id/ll_play_menu"> app:layout_constraintBottom_toTopOf="@+id/ll_play_menu">
<TextView <TextView
android:id="@+id/tv_dur_time" android:id="@+id/tv_dur_time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:ignore="RtlSymmetry" /> android:text="00:00"
android:textColor="@color/md_white_1000"
tools:ignore="HardcodedText,RtlSymmetry" />
<io.legado.app.lib.theme.view.ATESeekBar <io.legado.app.lib.theme.view.ATESeekBar
android:id="@+id/player_progress" android:id="@+id/player_progress"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="25dp" android:layout_height="25dp"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_weight="1" /> android:layout_weight="1"
android:progressBackgroundTint="@color/md_dark_secondary" />
<TextView <TextView
android:id="@+id/tv_all_time" android:id="@+id/tv_all_time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:text="00:00"
android:textColor="@color/md_white_1000"
tools:ignore="HardcodedText" />
</LinearLayout> </LinearLayout>
@ -78,8 +82,8 @@
android:id="@+id/ll_play_menu" android:id="@+id/ll_play_menu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="15dp" android:paddingLeft="24dp"
android:paddingRight="15dp" android:paddingRight="24dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"> app:layout_constraintBottom_toBottomOf="parent">

@ -6,12 +6,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="3dp" android:layout_margin="3dp"
android:background="@drawable/selector_fillet_btn_bg" android:background="@drawable/selector_fillet_btn_bg"
android:clickable="true"
android:ellipsize="end" android:ellipsize="end"
android:focusable="true"
android:gravity="center" android:gravity="center"
android:padding="5dp" android:padding="5dp"
android:singleLine="true" android:maxLines="1"
android:textColor="@color/tv_text_default" android:textColor="@color/tv_text_default"
android:textSize="14sp" android:textSize="14sp"
tools:ignore="UnusedAttribute" /> tools:ignore="UnusedAttribute" />

@ -556,5 +556,6 @@
<string name="book_type">类型:</string> <string name="book_type">类型:</string>
<string name="book_type_text">文本</string> <string name="book_type_text">文本</string>
<string name="book_type_audio">音频</string> <string name="book_type_audio">音频</string>
<string name="to_backstage">转到后台</string>
</resources> </resources>

@ -9,7 +9,7 @@ buildscript {
maven { url 'https://plugins.gradle.org/m2/' } maven { url 'https://plugins.gradle.org/m2/' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.1' classpath 'com.android.tools.build:gradle:3.5.2'
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.2' classpath 'com.google.gms:google-services:4.3.2'

Loading…
Cancel
Save