Improve scrobbling selection
This commit is contained in:
@@ -15,7 +15,9 @@ import androidx.core.view.descendants
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.progressindicator.BaseProgressIndicator
|
import com.google.android.material.progressindicator.BaseProgressIndicator
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
@@ -161,3 +163,12 @@ val Toolbar.menuView: ActionMenuView?
|
|||||||
menu // to call ensureMenu()
|
menu // to call ensureMenu()
|
||||||
return children.firstNotNullOfOrNull { it as? ActionMenuView }
|
return children.firstNotNullOfOrNull { it as? ActionMenuView }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun MaterialButton.setProgressIcon() {
|
||||||
|
val progressDrawable = CircularProgressDrawable(context)
|
||||||
|
progressDrawable.strokeWidth = resources.resolveDp(2f)
|
||||||
|
progressDrawable.setColorSchemeColors(currentTextColor)
|
||||||
|
progressDrawable.setTintList(textColors)
|
||||||
|
icon = progressDrawable
|
||||||
|
progressDrawable.start()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package org.koitharu.kotatsu.explore.ui.adapter
|
package org.koitharu.kotatsu.explore.ui.adapter
|
||||||
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
@@ -15,10 +13,9 @@ import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
|||||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
|
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.setProgressIcon
|
||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||||
import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding
|
import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding
|
||||||
@@ -30,7 +27,6 @@ import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
|
|||||||
import org.koitharu.kotatsu.explore.ui.model.RecommendationsItem
|
import org.koitharu.kotatsu.explore.ui.model.RecommendationsItem
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
|
|
||||||
fun exploreButtonsAD(
|
fun exploreButtonsAD(
|
||||||
clickListener: View.OnClickListener,
|
clickListener: View.OnClickListener,
|
||||||
@@ -45,16 +41,7 @@ fun exploreButtonsAD(
|
|||||||
|
|
||||||
bind {
|
bind {
|
||||||
if (item.isRandomLoading) {
|
if (item.isRandomLoading) {
|
||||||
val icon = CircularProgressDrawable(context)
|
binding.buttonRandom.setProgressIcon()
|
||||||
icon.strokeWidth = context.resources.resolveDp(2f)
|
|
||||||
icon.setColorSchemeColors(
|
|
||||||
context.getThemeColor(
|
|
||||||
materialR.attr.colorPrimary,
|
|
||||||
Color.DKGRAY,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
binding.buttonRandom.icon = icon
|
|
||||||
icon.start()
|
|
||||||
} else {
|
} else {
|
||||||
binding.buttonRandom.setIconResource(R.drawable.ic_dice)
|
binding.buttonRandom.setIconResource(R.drawable.ic_dice)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.scrobbling.common.domain
|
package org.koitharu.kotatsu.scrobbling.common.domain
|
||||||
|
|
||||||
|
import androidx.annotation.FloatRange
|
||||||
import androidx.collection.LongSparseArray
|
import androidx.collection.LongSparseArray
|
||||||
import androidx.collection.getOrElse
|
import androidx.collection.getOrElse
|
||||||
import androidx.core.text.parseAsHtml
|
import androidx.core.text.parseAsHtml
|
||||||
@@ -11,9 +12,11 @@ import kotlinx.coroutines.flow.flow
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
import org.koitharu.kotatsu.core.util.ext.findKeyByValue
|
import org.koitharu.kotatsu.core.util.ext.findKeyByValue
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.sanitize
|
import org.koitharu.kotatsu.core.util.ext.sanitize
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository
|
||||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity
|
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity
|
||||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
|
||||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo
|
||||||
@@ -21,13 +24,12 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
|||||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
|
||||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
|
||||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import java.util.EnumMap
|
import java.util.EnumMap
|
||||||
|
|
||||||
abstract class Scrobbler(
|
abstract class Scrobbler(
|
||||||
protected val db: MangaDatabase,
|
protected val db: MangaDatabase,
|
||||||
val scrobblerService: ScrobblerService,
|
val scrobblerService: ScrobblerService,
|
||||||
private val repository: org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository,
|
private val repository: ScrobblerRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val infoCache = LongSparseArray<ScrobblerMangaInfo>()
|
private val infoCache = LongSparseArray<ScrobblerMangaInfo>()
|
||||||
@@ -76,7 +78,12 @@ abstract class Scrobbler(
|
|||||||
return entity.toScrobblingInfo()
|
return entity.toScrobblingInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract suspend fun updateScrobblingInfo(mangaId: Long, rating: Float, status: ScrobblingStatus?, comment: String?)
|
abstract suspend fun updateScrobblingInfo(
|
||||||
|
mangaId: Long,
|
||||||
|
@FloatRange(from = 0.0, to = 1.0) rating: Float,
|
||||||
|
status: ScrobblingStatus?,
|
||||||
|
comment: String?,
|
||||||
|
)
|
||||||
|
|
||||||
fun observeScrobblingInfo(mangaId: Long): Flow<ScrobblingInfo?> {
|
fun observeScrobblingInfo(mangaId: Long): Flow<ScrobblingInfo?> {
|
||||||
return db.getScrobblingDao().observe(scrobblerService.id, mangaId)
|
return db.getScrobblingDao().observe(scrobblerService.id, mangaId)
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
|
|||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.setProgressIcon
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
|
||||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||||
import org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding
|
import org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
||||||
@@ -80,6 +82,15 @@ class ScrobblingSelectorSheet :
|
|||||||
viewModel.onClose.observeEvent(viewLifecycleOwner) {
|
viewModel.onClose.observeEvent(viewLifecycleOwner) {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
viewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||||
|
binding.buttonDone.isEnabled = !isLoading
|
||||||
|
if (isLoading) {
|
||||||
|
binding.buttonDone.setProgressIcon()
|
||||||
|
} else {
|
||||||
|
binding.buttonDone.icon = null
|
||||||
|
}
|
||||||
|
binding.tabs.setTabsEnabled(!isLoading)
|
||||||
|
}
|
||||||
viewModel.selectedScrobblerIndex.observe(viewLifecycleOwner) { index ->
|
viewModel.selectedScrobblerIndex.observe(viewLifecycleOwner) { index ->
|
||||||
val tab = binding.tabs.getTabAt(index)
|
val tab = binding.tabs.getTabAt(index)
|
||||||
if (tab != null && !tab.isSelected) {
|
if (tab != null && !tab.isSelected) {
|
||||||
@@ -100,7 +111,7 @@ class ScrobblingSelectorSheet :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClick(item: ScrobblerManga, view: View) {
|
override fun onItemClick(item: ScrobblerManga, view: View) {
|
||||||
viewModel.selectedItemId.value = item.id
|
viewModel.selectItem(item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRetryClick(error: Throwable) {
|
override fun onRetryClick(error: Throwable) {
|
||||||
|
|||||||
@@ -14,20 +14,24 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.model.findById
|
||||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.require
|
import org.koitharu.kotatsu.core.util.ext.require
|
||||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||||
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
|
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
|
||||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
|
||||||
import org.koitharu.kotatsu.scrobbling.common.ui.selector.model.ScrobblerHint
|
import org.koitharu.kotatsu.scrobbling.common.ui.selector.model.ScrobblerHint
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -35,6 +39,8 @@ import javax.inject.Inject
|
|||||||
class ScrobblingSelectorViewModel @Inject constructor(
|
class ScrobblingSelectorViewModel @Inject constructor(
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
|
scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
|
||||||
|
private val historyRepository: HistoryRepository,
|
||||||
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga
|
val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga
|
||||||
@@ -92,6 +98,13 @@ class ScrobblingSelectorViewModel @Inject constructor(
|
|||||||
loadList(append = false)
|
loadList(append = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun selectItem(id: Long) {
|
||||||
|
if (doneJob?.isActive == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedItemId.value = id
|
||||||
|
}
|
||||||
|
|
||||||
fun loadNextPage() {
|
fun loadNextPage() {
|
||||||
if (scrobblerMangaList.value.isNotEmpty() && hasNextPage.value) {
|
if (scrobblerMangaList.value.isNotEmpty() && hasNextPage.value) {
|
||||||
loadList(append = true)
|
loadList(append = true)
|
||||||
@@ -109,7 +122,7 @@ class ScrobblingSelectorViewModel @Inject constructor(
|
|||||||
if (loadingJob?.isActive == true) {
|
if (loadingJob?.isActive == true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
loadingJob = launchJob(Dispatchers.Default) {
|
||||||
listError.value = null
|
listError.value = null
|
||||||
val offset = if (append) scrobblerMangaList.value.size else 0
|
val offset = if (append) scrobblerMangaList.value.size else 0
|
||||||
runCatchingCancellable {
|
runCatchingCancellable {
|
||||||
@@ -136,8 +149,31 @@ class ScrobblingSelectorViewModel @Inject constructor(
|
|||||||
if (targetId == NO_ID) {
|
if (targetId == NO_ID) {
|
||||||
onClose.call(Unit)
|
onClose.call(Unit)
|
||||||
}
|
}
|
||||||
doneJob = launchJob(Dispatchers.Default) {
|
doneJob = launchLoadingJob(Dispatchers.Default) {
|
||||||
|
val prevInfo = currentScrobbler.getScrobblingInfoOrNull(manga.id)
|
||||||
currentScrobbler.linkManga(manga.id, targetId)
|
currentScrobbler.linkManga(manga.id, targetId)
|
||||||
|
val history = historyRepository.getOne(manga)
|
||||||
|
currentScrobbler.updateScrobblingInfo(
|
||||||
|
mangaId = manga.id,
|
||||||
|
rating = prevInfo?.rating ?: manga.rating,
|
||||||
|
status = prevInfo?.status ?: if (history == null) {
|
||||||
|
ScrobblingStatus.PLANNED
|
||||||
|
} else {
|
||||||
|
ScrobblingStatus.READING
|
||||||
|
},
|
||||||
|
comment = prevInfo?.comment,
|
||||||
|
)
|
||||||
|
if (history != null) {
|
||||||
|
val chapter = mangaRepositoryFactory.create(manga.source)
|
||||||
|
.getDetails(manga)
|
||||||
|
.chapters?.findById(history.chapterId)
|
||||||
|
if (chapter != null) {
|
||||||
|
currentScrobbler.scrobble(
|
||||||
|
mangaId = manga.id,
|
||||||
|
chapter = chapter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
onClose.call(Unit)
|
onClose.call(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
app:tabMode="scrollable"
|
app:tabMode="scrollable"
|
||||||
tools:ignore="UnusedAttribute" />
|
tools:ignore="UnusedAttribute" />
|
||||||
|
|
||||||
<Button
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/button_done"
|
android:id="@+id/button_done"
|
||||||
style="@style/Widget.Material3.Button.UnelevatedButton"
|
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
Reference in New Issue
Block a user