同步进度功能补充 (#4352)

* 进度同步补充:
在初始阅读界面时拉取云端进度 --> 初始阅读界面保留原有拉取逻辑,除此之外的阅读界面处于已恢复状态时执行同步云端进度,若云端进度快于本地则弹窗提醒是否同步
无 --> 监听到网络切换到有网环境时执行同步云端进度,若云端进度快于本地则弹窗提醒是否同步
页面进入已暂停状态时上传阅读进度 --> 页面进入已暂停状态时执行同步云端进度

以上新增的同步云端进度逻辑为:如果本地进度快于服务器进度或者没有进度则进行上传,如果慢与服务器进度则返回云端进度按需执行

* 点击区域增加同步阅读进度功能

* 阅读界面初始化同步逻辑修改

* merge

* merge

* merge

* 增加覆盖云端进度

* merge

* merge

* 多次弹窗问题修改

* 补充的同步功能增加配置开关

* Revert "优化"

This reverts commit 8d4fba2e2f.

* merge

---------

Co-authored-by: mozhu <lcl_em@163.com>
This commit is contained in:
lcdgit
2024-11-15 19:33:16 +08:00
committed by GitHub
co-authored by mozhu
parent afa9ac2e97
commit fdd88a8510
17 changed files with 161 additions and 57 deletions
@@ -88,6 +88,7 @@ object PreferKey {
const val importKeepGroup = "importKeepGroup"
const val screenOrientation = "screenOrientation"
const val syncBookProgress = "syncBookProgress"
const val syncBookProgressPlus = "syncBookProgressPlus"
const val cronet = "Cronet"
const val antiAlias = "antiAlias"
const val bitmapCacheSize = "bitmapCacheSize"
@@ -451,6 +451,8 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
val syncBookProgress get() = appCtx.getPrefBoolean(PreferKey.syncBookProgress, true)
val syncBookProgressPlus get() = appCtx.getPrefBoolean(PreferKey.syncBookProgressPlus, false)
val mediaButtonOnExit get() = appCtx.getPrefBoolean("mediaButtonOnExit", true)
val readAloudByMediaButton
@@ -199,12 +199,47 @@ object ReadBook : CoroutineScope by MainScope() {
nextTextChapter?.clearSearchResult()
}
fun uploadProgress() {
fun uploadProgress(successAction: (() -> Unit)? = null) {
book?.let {
launch(IO) {
AppWebDav.uploadBookProgress(it)
ensureActive()
it.update()
successAction?.invoke()
}
}
}
/**
* 同步阅读进度
* 如果当前进度快于服务器进度或者没有进度进行上传,如果慢与服务器进度则执行传入动作
*/
fun syncProgress(
newProgressAction: ((progress: BookProgress) -> Unit)? = null,
uploadSuccessAction: (() -> Unit)? = null,
syncSuccessAction: (() -> Unit)? = null) {
if (!AppConfig.syncBookProgress) return
book?.let {
Coroutine.async {
AppWebDav.getBookProgress(it)
}.onError {
AppLog.put("拉取阅读进度失败", it)
}.onSuccess { progress ->
if (progress == null || progress.durChapterIndex < it.durChapterIndex ||
(progress.durChapterIndex == it.durChapterIndex
&& progress.durChapterPos < it.durChapterPos)) {
// 服务器没有进度或者进度比服务器快,上传现有进度
Coroutine.async {
AppWebDav.uploadBookProgress(BookProgress(it), uploadSuccessAction)
it.save()
}
} else if (progress.durChapterIndex > it.durChapterIndex ||
progress.durChapterPos > it.durChapterPos) {
// 进度比服务器慢,执行传入动作
newProgressAction?.invoke(progress)
} else {
syncSuccessAction?.invoke()
}
}
}
}
@@ -893,6 +928,8 @@ object ReadBook : CoroutineScope by MainScope() {
fun notifyBookChanged()
fun sureNewProgress(progress: BookProgress)
fun cancelSelect()
}
@@ -7,15 +7,10 @@ import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle
import android.os.Looper
import android.view.Gravity
import android.view.InputDevice
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.*
import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.get
import androidx.core.view.isVisible
@@ -24,12 +19,7 @@ import androidx.lifecycle.lifecycleScope
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import io.legado.app.BuildConfig
import io.legado.app.R
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppLog
import io.legado.app.constant.BookType
import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey
import io.legado.app.constant.Status
import io.legado.app.constant.*
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
@@ -39,14 +29,7 @@ import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.AppWebDav
import io.legado.app.help.IntentData
import io.legado.app.help.TTS
import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.isAudio
import io.legado.app.help.book.isEpub
import io.legado.app.help.book.isLocal
import io.legado.app.help.book.isLocalTxt
import io.legado.app.help.book.isMobi
import io.legado.app.help.book.removeType
import io.legado.app.help.book.*
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.config.ReadTipConfig
@@ -59,6 +42,7 @@ import io.legado.app.lib.theme.accentColor
import io.legado.app.model.ReadAloud
import io.legado.app.model.ReadBook
import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.receiver.NetworkChangedListener
import io.legado.app.model.localBook.EpubFile
import io.legado.app.model.localBook.MobiFile
import io.legado.app.receiver.TimeBatteryReceiver
@@ -69,7 +53,7 @@ import io.legado.app.ui.book.bookmark.BookmarkDialog
import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
import io.legado.app.ui.book.changesource.ChangeChapterSourceDialog
import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.config.AutoReadDialog
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.TEXT_COLOR
import io.legado.app.ui.book.read.config.MoreConfigDialog
@@ -96,38 +80,10 @@ import io.legado.app.ui.replace.ReplaceRuleActivity
import io.legado.app.ui.replace.edit.ReplaceEditActivity
import io.legado.app.ui.widget.PopupAction
import io.legado.app.ui.widget.dialog.PhotoDialog
import io.legado.app.utils.ACache
import io.legado.app.utils.Debounce
import io.legado.app.utils.LogUtils
import io.legado.app.utils.StartActivityContract
import io.legado.app.utils.applyOpenTint
import io.legado.app.utils.buildMainHandler
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString
import io.legado.app.utils.hexString
import io.legado.app.utils.iconItemOnLongClick
import io.legado.app.utils.invisible
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.isTrue
import io.legado.app.utils.launch
import io.legado.app.utils.navigationBarGravity
import io.legado.app.utils.observeEvent
import io.legado.app.utils.observeEventSticky
import io.legado.app.utils.postEvent
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.showHelp
import io.legado.app.utils.startActivity
import io.legado.app.utils.sysScreenOffTime
import io.legado.app.utils.throttle
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.visible
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
/**
* 阅读界面
@@ -245,6 +201,11 @@ class ReadBookActivity : BaseReadBookActivity(),
//恢复跳转前进度对话框的交互结果
private var confirmRestoreProcess: Boolean? = null
private val networkChangedListener by lazy {
NetworkChangedListener(this)
}
private var justInitData: Boolean = false
private var syncDialog: AlertDialog? = null
@SuppressLint("ClickableViewAccessibility")
override fun onActivityCreated(savedInstanceState: Bundle?) {
@@ -289,6 +250,7 @@ class ReadBookActivity : BaseReadBookActivity(),
viewModel.initData(intent)
false
}
justInitData = true
}
override fun onNewIntent(intent: Intent) {
@@ -318,6 +280,7 @@ class ReadBookActivity : BaseReadBookActivity(),
bookChanged = false
ReadBook.callBack = this
viewModel.initData(intent)
justInitData = true
} else {
//web端阅读时,app处于阅读界面,本地记录会覆盖web保存的进度,在此处恢复
ReadBook.webBookProgress?.let {
@@ -329,6 +292,14 @@ class ReadBookActivity : BaseReadBookActivity(),
registerReceiver(timeBatteryReceiver, timeBatteryReceiver.filter)
binding.readView.upTime()
screenOffTimerStart()
// 网络监听,当从无网切换到网络环境时同步进度(注意注册的同时就会收到监听,因此界面激活时无需重复执行同步操作)
networkChangedListener.register()
networkChangedListener.onNetworkChanged = {
// 当网络是可用状态且无需初始化时同步进度(初始化中已有同步进度逻辑)
if (AppConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData) {
ReadBook.syncProgress({progress -> sureNewProgress(progress)}, null)
}
}
}
override fun onPause() {
@@ -339,9 +310,15 @@ class ReadBookActivity : BaseReadBookActivity(),
unregisterReceiver(timeBatteryReceiver)
upSystemUiVisibility()
if (!BuildConfig.DEBUG) {
ReadBook.uploadProgress()
if (AppConfig.syncBookProgressPlus) {
ReadBook.syncProgress()
} else {
ReadBook.uploadProgress()
}
Backup.autoBack(this)
}
justInitData = false
networkChangedListener.unRegister()
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
@@ -408,6 +385,9 @@ class ReadBookActivity : BaseReadBookActivity(),
menu.findItem(R.id.menu_get_progress)?.isVisible = withContext(IO) {
AppWebDav.isOk
}
menu.findItem(R.id.menu_cover_progress)?.isVisible = withContext(IO) {
AppWebDav.isOk
}
}
}
@@ -551,6 +531,10 @@ class ReadBookActivity : BaseReadBookActivity(),
}
}
R.id.menu_cover_progress -> ReadBook.book?.let {
ReadBook.uploadProgress({ toastOnUi(R.string.upload_book_success) })
}
R.id.menu_same_title_removed -> {
ReadBook.book?.let {
val contentProcessor = ContentProcessor.get(it)
@@ -1499,6 +1483,18 @@ class ReadBookActivity : BaseReadBookActivity(),
}
}
override fun sureNewProgress(progress: BookProgress) {
syncDialog?.dismiss()
syncDialog = alert(R.string.get_book_progress) {
setMessage(R.string.cloud_progress_exceeds_current)
okButton {
ReadBook.setProgress(progress)
ReadBook.saveRead()
}
noButton()
}
}
override fun finish() {
val book = ReadBook.book ?: return super.finish()
@@ -125,7 +125,11 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
// 有章节跳转不同步阅读进度
ReadBook.chapterChanged = false
} else if (!isSameBook || !BaseReadAloudService.isRun) {
syncBookProgress(book)
if (AppConfig.syncBookProgressPlus) {
ReadBook.syncProgress({ progress -> ReadBook.callBack?.sureNewProgress(progress) }, null)
} else {
syncBookProgress(book)
}
}
if (!book.isLocal && ReadBook.bookSource == null) {
autoChangeSource(book.name, book.author)
@@ -35,7 +35,8 @@ class ClickActionConfigDialog : BaseDialogFragment(R.layout.dialog_click_action_
Pair(8, getString(R.string.edit_content)),
Pair(9, getString(R.string.replace_state_change)),
Pair(10, getString(R.string.chapter_list)),
Pair(11, getString(R.string.search_content))
Pair(11, getString(R.string.search_content)),
Pair(12, getString(R.string.sync_book_progress_t))
)
}
@@ -10,7 +10,9 @@ import android.view.MotionEvent
import android.view.ViewConfiguration
import android.view.WindowInsets
import android.widget.FrameLayout
import io.legado.app.R
import io.legado.app.constant.PageAnim
import io.legado.app.data.entities.BookProgress
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.model.ReadAloud
@@ -34,6 +36,7 @@ import io.legado.app.ui.book.read.page.provider.TextPageFactory
import io.legado.app.utils.activity
import io.legado.app.utils.canvasrecorder.pools.BitmapPool
import io.legado.app.utils.invisible
import io.legado.app.utils.longToastOnUi
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.throttle
import java.text.BreakIterator
@@ -434,6 +437,9 @@ class ReadView(context: Context, attrs: AttributeSet) :
9 -> callBack.changeReplaceRuleState()
10 -> callBack.openChapterList()
11 -> callBack.openSearchActivity(null)
12 -> ReadBook.syncProgress({progress -> callBack.sureNewProgress(progress)},
{ context.longToastOnUi(context.getString(R.string.upload_book_success)) },
{ context.longToastOnUi(context.getString(R.string.sync_book_progress_success)) })
}
}
@@ -726,5 +732,6 @@ class ReadView(context: Context, attrs: AttributeSet) :
fun changeReplaceRuleState()
fun openSearchActivity(searchWord: String?)
fun upSystemUiVisibility()
fun sureNewProgress(progress: BookProgress)
}
}