diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt index 793e2d102..5357a3ff4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt @@ -15,7 +15,9 @@ import androidx.core.view.descendants import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder +import androidx.swiperefreshlayout.widget.CircularProgressDrawable import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.button.MaterialButton import com.google.android.material.progressindicator.BaseProgressIndicator import com.google.android.material.slider.Slider import com.google.android.material.tabs.TabLayout @@ -161,3 +163,12 @@ val Toolbar.menuView: ActionMenuView? menu // to call ensureMenu() 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() +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt index 735f32df1..996bd17fa 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt @@ -1,9 +1,7 @@ package org.koitharu.kotatsu.explore.ui.adapter -import android.graphics.Color import android.view.View import androidx.lifecycle.LifecycleOwner -import androidx.swiperefreshlayout.widget.CircularProgressDrawable import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding 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.OnListItemClickListener 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.resolveDp 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.textAndVisible 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.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.Manga -import com.google.android.material.R as materialR fun exploreButtonsAD( clickListener: View.OnClickListener, @@ -45,16 +41,7 @@ fun exploreButtonsAD( bind { if (item.isRandomLoading) { - val icon = CircularProgressDrawable(context) - icon.strokeWidth = context.resources.resolveDp(2f) - icon.setColorSchemeColors( - context.getThemeColor( - materialR.attr.colorPrimary, - Color.DKGRAY, - ), - ) - binding.buttonRandom.icon = icon - icon.start() + binding.buttonRandom.setProgressIcon() } else { binding.buttonRandom.setIconResource(R.drawable.ic_dice) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/Scrobbler.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/Scrobbler.kt index 0a89ecaf6..f218ebee1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/Scrobbler.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/domain/Scrobbler.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.scrobbling.common.domain +import androidx.annotation.FloatRange import androidx.collection.LongSparseArray import androidx.collection.getOrElse import androidx.core.text.parseAsHtml @@ -11,9 +12,11 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import org.koitharu.kotatsu.core.db.MangaDatabase 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.parsers.model.MangaChapter 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.domain.model.ScrobblerManga 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.ScrobblingInfo import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus -import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import java.util.EnumMap abstract class Scrobbler( protected val db: MangaDatabase, val scrobblerService: ScrobblerService, - private val repository: org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository, + private val repository: ScrobblerRepository, ) { private val infoCache = LongSparseArray() @@ -76,7 +78,12 @@ abstract class Scrobbler( 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 { return db.getScrobblingDao().observe(scrobblerService.id, mangaId) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorSheet.kt index 3f098e3e0..582575958 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorSheet.kt @@ -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.observe 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.databinding.SheetScrobblingSelectorBinding import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener @@ -80,6 +82,15 @@ class ScrobblingSelectorSheet : viewModel.onClose.observeEvent(viewLifecycleOwner) { 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 -> val tab = binding.tabs.getTabAt(index) if (tab != null && !tab.isSelected) { @@ -100,7 +111,7 @@ class ScrobblingSelectorSheet : } override fun onItemClick(item: ScrobblerManga, view: View) { - viewModel.selectedItemId.value = item.id + viewModel.selectItem(item.id) } override fun onRetryClick(error: Throwable) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorViewModel.kt index 54c28c99f..380614ffe 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/ScrobblingSelectorViewModel.kt @@ -14,20 +14,24 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus 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.parser.MangaIntent +import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.require 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.LoadingFooter import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.parsers.util.runCatchingCancellable 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.ScrobblingStatus import org.koitharu.kotatsu.scrobbling.common.ui.selector.model.ScrobblerHint import javax.inject.Inject @@ -35,6 +39,8 @@ import javax.inject.Inject class ScrobblingSelectorViewModel @Inject constructor( savedStateHandle: SavedStateHandle, scrobblers: Set<@JvmSuppressWildcards Scrobbler>, + private val historyRepository: HistoryRepository, + private val mangaRepositoryFactory: MangaRepository.Factory, ) : BaseViewModel() { val manga = savedStateHandle.require(MangaIntent.KEY_MANGA).manga @@ -92,6 +98,13 @@ class ScrobblingSelectorViewModel @Inject constructor( loadList(append = false) } + fun selectItem(id: Long) { + if (doneJob?.isActive == true) { + return + } + selectedItemId.value = id + } + fun loadNextPage() { if (scrobblerMangaList.value.isNotEmpty() && hasNextPage.value) { loadList(append = true) @@ -109,7 +122,7 @@ class ScrobblingSelectorViewModel @Inject constructor( if (loadingJob?.isActive == true) { return } - loadingJob = launchLoadingJob(Dispatchers.Default) { + loadingJob = launchJob(Dispatchers.Default) { listError.value = null val offset = if (append) scrobblerMangaList.value.size else 0 runCatchingCancellable { @@ -136,8 +149,31 @@ class ScrobblingSelectorViewModel @Inject constructor( if (targetId == NO_ID) { onClose.call(Unit) } - doneJob = launchJob(Dispatchers.Default) { + doneJob = launchLoadingJob(Dispatchers.Default) { + val prevInfo = currentScrobbler.getScrobblingInfoOrNull(manga.id) 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) } } diff --git a/app/src/main/res/layout/sheet_scrobbling_selector.xml b/app/src/main/res/layout/sheet_scrobbling_selector.xml index f647026aa..96ad52c7c 100644 --- a/app/src/main/res/layout/sheet_scrobbling_selector.xml +++ b/app/src/main/res/layout/sheet_scrobbling_selector.xml @@ -34,7 +34,7 @@ app:tabMode="scrollable" tools:ignore="UnusedAttribute" /> -