This commit is contained in:
Horis 2024-01-24 10:07:07 +08:00
parent 9b49200a85
commit 5324dfcec3
5 changed files with 78 additions and 109 deletions

View File

@ -2,14 +2,21 @@ package io.legado.app.ui.book.source.manage
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.*
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.SubMenu
import android.view.WindowManager
import android.widget.EditText
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.core.os.bundleOf
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.ItemTouchHelper
import com.google.android.material.snackbar.Snackbar
import io.legado.app.R
@ -40,15 +47,31 @@ import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
import io.legado.app.ui.widget.recycler.ItemTouchCallback
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.*
import io.legado.app.utils.ACache
import io.legado.app.utils.applyTint
import io.legado.app.utils.cnCompare
import io.legado.app.utils.dpToPx
import io.legado.app.utils.hideSoftInput
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.launch
import io.legado.app.utils.observeEvent
import io.legado.app.utils.sendToClip
import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.share
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.startActivity
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
/**
* 书源管理界面
@ -67,6 +90,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
binding.titleBar.findViewById(R.id.search_view)
}
private var sourceFlowJob: Job? = null
private var checkMessageRefreshJob: Job? = null
private val groups = linkedSetOf<String>()
private var groupMenu: SubMenu? = null
override var sort = BookSourceSort.Default
@ -75,7 +99,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
private set
private var snackBar: Snackbar? = null
private var isPaused = false
private var searchKey: String? = null
private val qrResult = registerForActivityResult(QrCodeResult()) {
it ?: return@registerForActivityResult
showDialogFragment(ImportBookSourceDialog(it))
@ -256,7 +279,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
private fun upBookSource(searchKey: String? = null) {
this.searchKey = searchKey
sourceFlowJob?.cancel()
sourceFlowJob = lifecycleScope.launch {
when {
@ -338,7 +360,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
else -> data.reversed()
}
}
}.catch {
}.flowWithLifecycle(lifecycle).catch {
AppLog.put("书源界面更新书源出错", it)
}.flowOn(IO).conflate().collect { data ->
adapter.setItems(data, adapter.diffItemCallback, !Debug.isChecking)
@ -403,7 +425,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
R.id.menu_remove_group -> selectionRemoveFromGroups()
R.id.menu_export_selection -> viewModel.saveToFile(
adapter,
searchKey,
searchView.query?.toString(),
sortAscending,
sort
) { file ->
@ -419,7 +441,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
R.id.menu_share_source -> viewModel.saveToFile(
adapter,
searchKey,
searchView.query?.toString(),
sortAscending,
sort
) {
@ -452,7 +474,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
val firstItem = adapterItems.indexOf(selectItems.firstOrNull())
val lastItem = adapterItems.indexOf(selectItems.lastOrNull())
Debug.isChecking = firstItem >= 0 && lastItem >= 0
checkMessageRefreshJob(firstItem, lastItem).start()
startCheckMessageRefreshJob(firstItem, lastItem)
}
neutralButton(R.string.check_source_config)
cancelButton()
@ -469,7 +491,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
}
keepScreenOn(true)
CheckSource.resume(this)
checkMessageRefreshJob(0, 0).start()
startCheckMessageRefreshJob(0, 0)
}
@SuppressLint("InflateParams")
@ -558,11 +580,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
.setAction(R.string.cancel) {
CheckSource.stop(this)
Debug.finishChecking()
adapter.notifyItemRangeChanged(
0,
adapter.itemCount,
bundleOf(Pair("checkSourceMessage", null))
)
}.apply { show() }
}
}
@ -570,6 +587,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
keepScreenOn(false)
snackBar?.dismiss()
snackBar = null
adapter.notifyItemRangeChanged(
0,
adapter.itemCount,
bundleOf(Pair("checkSourceMessage", null))
)
groups.map { group ->
if (group.contains("失效") && searchView.query.isEmpty()) {
searchView.setQuery("失效", true)
@ -579,15 +601,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
}
}
private fun checkMessageRefreshJob(firstItem: Int, lastItem: Int): Job {
return lifecycleScope.async(start = CoroutineStart.LAZY) {
flow {
while (true) {
emit(Debug.isChecking)
delay(300L)
}
}.collect {
if (SystemUtils.isScreenOn() && !isPaused) {
private fun startCheckMessageRefreshJob(firstItem: Int, lastItem: Int) {
checkMessageRefreshJob?.cancel()
checkMessageRefreshJob = lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
while (isActive) {
if (lastItem == 0) {
adapter.notifyItemRangeChanged(
0,
@ -601,9 +619,10 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
bundleOf(Pair("checkSourceMessage", null))
)
}
}
if (!it) {
this.cancel()
if (!Debug.isChecking) {
checkMessageRefreshJob?.cancel()
}
delay(300L)
}
}
}

View File

@ -5,7 +5,10 @@ import android.os.Bundle
import android.view.View
import androidx.core.view.isGone
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -25,7 +28,6 @@ import io.legado.app.ui.book.audio.AudioPlayActivity
import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainViewModel
import io.legado.app.utils.SystemUtils
import io.legado.app.utils.cnCompare
import io.legado.app.utils.observeEvent
import io.legado.app.utils.setEdgeEffectColor
@ -62,7 +64,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
private val bookshelfLayout by lazy { AppConfig.bookshelfLayout }
private val booksAdapter: BaseBooksAdapter<*> by lazy {
if (bookshelfLayout == 0) {
BooksAdapterList(requireContext(), this, lifecycle)
BooksAdapterList(requireContext(), this, viewLifecycleOwner.lifecycle)
} else {
BooksAdapterGrid(requireContext(), this)
}
@ -154,7 +156,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
*/
private fun upRecyclerData() {
booksFlowJob?.cancel()
booksFlowJob = lifecycleScope.launch {
booksFlowJob = viewLifecycleOwner.lifecycleScope.launch {
appDb.bookDao.flowByGroup(groupId).map { list ->
//排序
when (bookSort) {
@ -172,9 +174,9 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
else -> list.sortedByDescending { it.durChapterTime }
}
}.flowOn(Dispatchers.Default).catch {
}.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch {
AppLog.put("书架更新出错", it)
}.conflate().collect { list ->
}.conflate().flowOn(Dispatchers.Default).collect { list ->
binding.tvEmptyMsg.isGone = list.isNotEmpty()
binding.refreshLayout.isEnabled = enableRefresh && list.isNotEmpty()
booksAdapter.setItems(list)
@ -197,29 +199,17 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
}
}
override fun onPause() {
super.onPause()
upLastUpdateTimeJob?.cancel()
booksFlowJob?.cancel()
}
override fun onResume() {
super.onResume()
startLastUpdateTimeJob()
upRecyclerData()
}
private fun startLastUpdateTimeJob() {
upLastUpdateTimeJob?.cancel()
if (!AppConfig.showLastUpdateTime) {
return
}
upLastUpdateTimeJob = lifecycleScope.launch {
while (isActive) {
if (SystemUtils.isScreenOn()) {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
while (isActive) {
booksAdapter.upLastUpdateTime()
delay(30 * 1000)
}
delay(30 * 1000)
}
}
}

View File

@ -8,6 +8,8 @@ import android.view.View
import androidx.appcompat.widget.SearchView
import androidx.core.view.isGone
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -34,7 +36,6 @@ import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
@ -67,14 +68,13 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
private val groups = linkedSetOf<String>()
private var exploreFlowJob: Job? = null
private var groupsMenu: SubMenu? = null
private var searchKey: String? = null
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
setSupportToolbar(binding.titleBar.toolbar)
initSearchView()
initRecyclerView()
initGroupData()
upExploreData(once = true)
upExploreData()
}
override fun onCompatCreateOptionsMenu(menu: Menu) {
@ -87,7 +87,6 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
override fun onPause() {
super.onPause()
searchView.clearFocus()
exploreFlowJob?.cancel()
}
private fun initSearchView() {
@ -126,7 +125,7 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
}
private fun initGroupData() {
lifecycleScope.launch {
viewLifecycleOwner.lifecycleScope.launch {
appDb.bookSourceDao.flowExploreGroups().conflate().collect {
groups.clear()
groups.addAll(it)
@ -135,10 +134,9 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
}
}
private fun upExploreData(searchKey: String? = null, once: Boolean = false) {
this.searchKey = searchKey
private fun upExploreData(searchKey: String? = null) {
exploreFlowJob?.cancel()
exploreFlowJob = lifecycleScope.launch {
exploreFlowJob = viewLifecycleOwner.lifecycleScope.launch {
when {
searchKey.isNullOrBlank() -> {
appDb.bookSourceDao.flowExplore()
@ -152,12 +150,11 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
else -> {
appDb.bookSourceDao.flowExplore(searchKey)
}
}.catch {
}.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch {
AppLog.put("发现界面更新数据出错", it)
}.conflate().flowOn(IO).collect {
binding.tvEmptyMsg.isGone = it.isNotEmpty() || searchView.query.isNotEmpty()
adapter.setItems(it, diffItemCallBack)
if (once) cancel()
delay(500)
}
}
@ -173,7 +170,7 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
}
override val scope: CoroutineScope
get() = lifecycleScope
get() = viewLifecycleOwner.lifecycleScope
override fun onCompatOptionsItemSelected(item: MenuItem) {
super.onCompatOptionsItemSelected(item)
@ -231,9 +228,4 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
}
}
override fun onResume() {
super.onResume()
upExploreData(searchKey)
}
}

View File

@ -1,17 +1,11 @@
package io.legado.app.ui.main.rss
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.lifecycle.Lifecycle
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.gif.GifDrawable
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
@ -39,35 +33,6 @@ class RssAdapter(context: Context, val callBack: CallBack, val lifecycle: Lifecy
val options = RequestOptions()
.set(OkHttpModelLoader.sourceOriginOption, item.sourceUrl)
ImageLoader.load(lifecycle, item.sourceIcon)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable
&& !lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
) {
ivIcon.post {
resource.stop()
}
}
return false
}
})
.apply(options)
.centerCrop()
.placeholder(R.drawable.image_rss)

View File

@ -7,6 +7,8 @@ import android.view.SubMenu
import android.view.View
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import io.legado.app.R
import io.legado.app.base.VMBaseFragment
@ -58,7 +60,7 @@ class RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),
private val binding by viewBinding(FragmentRssBinding::bind)
override val viewModel by viewModels<RssViewModel>()
private val adapter by lazy { RssAdapter(requireContext(), this, lifecycle) }
private val adapter by lazy { RssAdapter(requireContext(), this, viewLifecycleOwner.lifecycle) }
private val searchView: SearchView by lazy {
binding.titleBar.findViewById(R.id.search_view)
}
@ -142,22 +144,23 @@ class RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),
private fun initGroupData() {
groupsFlowJob?.cancel()
groupsFlowJob = lifecycleScope.launch {
groupsFlowJob = viewLifecycleOwner.lifecycleScope.launch {
appDb.rssSourceDao.flowGroupEnabled().catch {
AppLog.put("订阅界面获取分组数据失败\n${it.localizedMessage}", it)
}.flowOn(IO).conflate().collect {
groups.clear()
it.map { group ->
groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
}.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED)
.flowOn(IO).conflate().collect {
groups.clear()
it.map { group ->
groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
}
upGroupsMenu()
}
upGroupsMenu()
}
}
}
private fun upRssFlowJob(searchKey: String? = null) {
rssFlowJob?.cancel()
rssFlowJob = lifecycleScope.launch {
rssFlowJob = viewLifecycleOwner.lifecycleScope.launch {
when {
searchKey.isNullOrEmpty() -> appDb.rssSourceDao.flowEnabled()
searchKey.startsWith("group:") -> {
@ -166,7 +169,7 @@ class RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),
}
else -> appDb.rssSourceDao.flowEnabled(searchKey)
}.catch {
}.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch {
AppLog.put("订阅界面更新数据出错", it)
}.flowOn(IO).collect {
adapter.setItems(it)