This commit is contained in:
Koitharu
2024-04-29 19:06:35 +03:00
parent af510beb7b
commit e2d7f2890d
12 changed files with 81 additions and 29 deletions

View File

@@ -18,8 +18,8 @@ import com.google.android.material.R as materialR
class AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, TimeAnimator.TimeListener { class AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, TimeAnimator.TimeListener {
private val colorLow = context.getThemeColor(materialR.attr.colorSurfaceContainerLow) private val colorLow = context.getThemeColor(materialR.attr.colorBackgroundFloating)
private val colorHigh = context.getThemeColor(materialR.attr.colorSurfaceContainerHigh) private val colorHigh = context.getThemeColor(materialR.attr.colorSurfaceContainer)
private var currentColor: Int = colorLow private var currentColor: Int = colorLow
private var alpha: Int = 255 private var alpha: Int = 255
private val interpolator = FastOutSlowInInterpolator() private val interpolator = FastOutSlowInInterpolator()

View File

@@ -22,6 +22,10 @@ import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
@@ -131,14 +135,17 @@ abstract class BaseAdaptiveSheet<B : ViewBinding> : AppCompatDialogFragment() {
dialog?.window?.statusBarColor = defaultStatusBarColor dialog?.window?.statusBarColor = defaultStatusBarColor
} }
fun addSheetCallback(callback: AdaptiveSheetCallback) { fun addSheetCallback(callback: AdaptiveSheetCallback, lifecycleOwner: LifecycleOwner): Boolean {
val b = behavior ?: return val b = behavior ?: return false
b.addCallback(callback) b.addCallback(callback)
val rootView = dialog?.findViewById<View>(materialR.id.design_bottom_sheet) val rootView = dialog?.findViewById<View>(materialR.id.design_bottom_sheet)
?: dialog?.findViewById(materialR.id.coordinator) ?: dialog?.findViewById(materialR.id.coordinator)
?: view
if (rootView != null) { if (rootView != null) {
callback.onStateChanged(rootView, b.state) callback.onStateChanged(rootView, b.state)
} }
lifecycleOwner.lifecycle.addObserver(CallbackRemoveObserver(b, callback))
return true
} }
protected abstract fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): B protected abstract fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): B
@@ -146,7 +153,8 @@ abstract class BaseAdaptiveSheet<B : ViewBinding> : AppCompatDialogFragment() {
protected open fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) = Unit protected open fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) = Unit
fun startSupportActionMode(callback: ActionMode.Callback): ActionMode? { fun startSupportActionMode(callback: ActionMode.Callback): ActionMode? {
val delegate = (dialog as? AppCompatDialog)?.delegate ?: (activity as? AppCompatActivity)?.delegate ?: return null val delegate =
(dialog as? AppCompatDialog)?.delegate ?: (activity as? AppCompatActivity)?.delegate ?: return null
return delegate.startSupportActionMode(callback) return delegate.startSupportActionMode(callback)
} }
@@ -292,4 +300,16 @@ abstract class BaseAdaptiveSheet<B : ViewBinding> : AppCompatDialogFragment() {
} }
} }
} }
private class CallbackRemoveObserver(
private val behavior: AdaptiveSheetBehavior,
private val callback: AdaptiveSheetCallback,
) : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
owner.lifecycle.removeObserver(this)
behavior.removeCallback(callback)
}
}
} }

View File

@@ -93,7 +93,7 @@ fun ImageRequest.Builder.defaultPlaceholders(context: Context): ImageRequest.Bui
val errorColor = ColorUtils.blendARGB( val errorColor = ColorUtils.blendARGB(
context.getThemeColor(materialR.attr.colorErrorContainer), context.getThemeColor(materialR.attr.colorErrorContainer),
context.getThemeColor(materialR.attr.colorBackgroundFloating), context.getThemeColor(materialR.attr.colorBackgroundFloating),
0.4f, 0.25f,
) )
return placeholder(AnimatedPlaceholderDrawable(context)) return placeholder(AnimatedPlaceholderDrawable(context))
.fallback(ColorDrawable(context.getThemeColor(materialR.attr.colorSurfaceContainer))) .fallback(ColorDrawable(context.getThemeColor(materialR.attr.colorSurfaceContainer)))

View File

@@ -97,7 +97,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.ellipsize import org.koitharu.kotatsu.parsers.util.ellipsize
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.MangaListActivity
@@ -320,7 +320,7 @@ class DetailsActivity :
override fun onItemClick(item: Bookmark, view: View) { override fun onItemClick(item: Bookmark, view: View) {
startActivity( startActivity(
IntentBuilder(view.context).bookmark(item).incognito(true).build(), ReaderActivity.IntentBuilder(view.context).bookmark(item).incognito(true).build(),
) )
Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show() Toast.makeText(view.context, R.string.incognito_mode, Toast.LENGTH_SHORT).show()
} }
@@ -535,17 +535,18 @@ class DetailsActivity :
} }
private fun onHistoryChanged(info: HistoryInfo, isLoading: Boolean) = with(viewBinding) { private fun onHistoryChanged(info: HistoryInfo, isLoading: Boolean) = with(viewBinding) {
buttonRead.setTitle(if (info.history != null) R.string._continue else R.string.read) buttonRead.setTitle(if (info.canContinue) R.string._continue else R.string.read)
buttonRead.subtitle = when { buttonRead.subtitle = when {
isLoading -> getString(R.string.loading_) isLoading -> getString(R.string.loading_)
info.isIncognitoMode -> getString(R.string.incognito_mode) info.isIncognitoMode -> getString(R.string.incognito_mode)
info.isChapterMissing -> getString(R.string.chapter_is_missing)
info.currentChapter >= 0 -> getString(R.string.chapter_d_of_d, info.currentChapter + 1, info.totalChapters) info.currentChapter >= 0 -> getString(R.string.chapter_d_of_d, info.currentChapter + 1, info.totalChapters)
info.totalChapters == 0 -> getString(R.string.no_chapters) info.totalChapters == 0 -> getString(R.string.no_chapters)
info.totalChapters == -1 -> getString(R.string.error_occurred) info.totalChapters == -1 -> getString(R.string.error_occurred)
else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters) else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters)
} }
buttonRead.setProgress(info.history?.percent?.coerceIn(0f, 1f) ?: 0f, true) buttonRead.setProgress(info.history?.percent?.coerceIn(0f, 1f) ?: 0f, true)
buttonDownload?.isEnabled = info.isValid buttonDownload?.isEnabled = info.isValid && info.canDownload
buttonRead.isEnabled = info.isValid buttonRead.isEnabled = info.isValid
} }
@@ -605,7 +606,7 @@ class DetailsActivity :
.show() .show()
} else { } else {
startActivity( startActivity(
IntentBuilder(this) ReaderActivity.IntentBuilder(this)
.manga(manga) .manga(manga)
.branch(viewModel.selectedBranchValue) .branch(viewModel.selectedBranchValue)
.incognito(isIncognitoMode) .incognito(isIncognitoMode)

View File

@@ -84,8 +84,8 @@ class DetailsViewModel @Inject constructor(
) : BaseViewModel() { ) : BaseViewModel() {
private val intent = MangaIntent(savedStateHandle) private val intent = MangaIntent(savedStateHandle)
private val mangaId = intent.mangaId
private var loadingJob: Job private var loadingJob: Job
val mangaId = intent.mangaId
val onActionDone = MutableEventFlow<ReversibleAction>() val onActionDone = MutableEventFlow<ReversibleAction>()
val onShowTip = MutableEventFlow<Unit>() val onShowTip = MutableEventFlow<Unit>()
@@ -131,7 +131,7 @@ class DetailsViewModel @Inject constructor(
) )
val historyInfo: StateFlow<HistoryInfo> = combine( val historyInfo: StateFlow<HistoryInfo> = combine(
manga, details,
selectedBranch, selectedBranch,
history, history,
interactor.observeIncognitoMode(manga), interactor.observeIncognitoMode(manga),

View File

@@ -1,6 +1,9 @@
package org.koitharu.kotatsu.details.ui.model package org.koitharu.kotatsu.details.ui.model
import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
data class HistoryInfo( data class HistoryInfo(
@@ -8,26 +11,34 @@ data class HistoryInfo(
val currentChapter: Int, val currentChapter: Int,
val history: MangaHistory?, val history: MangaHistory?,
val isIncognitoMode: Boolean, val isIncognitoMode: Boolean,
val isChapterMissing: Boolean,
val canDownload: Boolean,
) { ) {
val isValid: Boolean val isValid: Boolean
get() = totalChapters >= 0 get() = totalChapters >= 0
val canContinue: Boolean
get() = history != null && !isChapterMissing
} }
fun HistoryInfo( fun HistoryInfo(
manga: Manga?, manga: MangaDetails?,
branch: String?, branch: String?,
history: MangaHistory?, history: MangaHistory?,
isIncognitoMode: Boolean isIncognitoMode: Boolean
): HistoryInfo { ): HistoryInfo {
val chapters = manga?.getChapters(branch) val chapters = manga?.chapters?.get(branch)
val currentChapter = if (history != null && !chapters.isNullOrEmpty()) {
chapters.indexOfFirst { it.id == history.chapterId }
} else {
-2
}
return HistoryInfo( return HistoryInfo(
totalChapters = chapters?.size ?: -1, totalChapters = chapters?.size ?: -1,
currentChapter = if (history != null && !chapters.isNullOrEmpty()) { currentChapter = currentChapter,
chapters.indexOfFirst { it.id == history.chapterId }
} else {
-1
},
history = history, history = history,
isIncognitoMode = isIncognitoMode, isIncognitoMode = isIncognitoMode,
isChapterMissing = currentChapter == -1,
canDownload = manga?.isLocal == false,
) )
} }

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.details.ui.pager
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible import androidx.core.view.isVisible
@@ -12,6 +13,12 @@ import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_COLLAPSED
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_DRAGGING
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_EXPANDED
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior.Companion.STATE_SETTLING
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.ui.util.ActionModeListener import org.koitharu.kotatsu.core.ui.util.ActionModeListener
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
@@ -28,7 +35,7 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), ActionModeListener { class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), ActionModeListener, AdaptiveSheetCallback {
@Inject @Inject
lateinit var settings: AppSettings lateinit var settings: AppSettings
@@ -58,6 +65,7 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
binding.toolbar.addMenuProvider(menuProvider) binding.toolbar.addMenuProvider(menuProvider)
actionModeDelegate?.addListener(this, viewLifecycleOwner) actionModeDelegate?.addListener(this, viewLifecycleOwner)
addSheetCallback(this, viewLifecycleOwner)
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.pager, this)) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.pager, this))
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.pager, null)) viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.pager, null))
@@ -65,6 +73,14 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
viewModel.newChaptersCount.observe(viewLifecycleOwner, ::onNewChaptersChanged) viewModel.newChaptersCount.observe(viewLifecycleOwner, ::onNewChaptersChanged)
} }
override fun onStateChanged(sheet: View, newState: Int) {
if (newState == STATE_DRAGGING || newState == STATE_SETTLING) {
return
}
val isActionModeStarted = actionModeDelegate?.isActionModeStarted == true
viewBinding?.toolbar?.menuView?.isVisible = newState != STATE_COLLAPSED && !isActionModeStarted
}
override fun onActionModeStarted(mode: ActionMode) { override fun onActionModeStarted(mode: ActionMode) {
expandAndLock() expandAndLock()
viewBinding?.toolbar?.menuView?.isVisible = false viewBinding?.toolbar?.menuView?.isVisible = false
@@ -72,7 +88,8 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
override fun onActionModeFinished(mode: ActionMode) { override fun onActionModeFinished(mode: ActionMode) {
unlock() unlock()
viewBinding?.toolbar?.menuView?.isVisible = true val state = behavior?.state ?: STATE_EXPANDED
viewBinding?.toolbar?.menuView?.isVisible = state != STATE_COLLAPSED
} }
override fun expandAndLock() { override fun expandAndLock() {

View File

@@ -75,7 +75,9 @@ class PagesViewModel @Inject constructor(
private suspend fun doInit(state: State) { private suspend fun doInit(state: State) {
chaptersLoader.init(state.details) chaptersLoader.init(state.details)
val initialChapterId = state.history?.chapterId ?: state.details.allChapters.firstOrNull()?.id ?: return val initialChapterId = state.history?.chapterId?.takeIf {
chaptersLoader.peekChapter(it) != null
} ?: state.details.allChapters.firstOrNull()?.id ?: return
if (!chaptersLoader.hasPages(initialChapterId)) { if (!chaptersLoader.hasPages(initialChapterId)) {
chaptersLoader.loadSingleChapter(initialChapterId) chaptersLoader.loadSingleChapter(initialChapterId)
} }

View File

@@ -10,6 +10,7 @@ import android.view.View.OnClickListener
import android.view.View.OnLongClickListener import android.view.View.OnLongClickListener
import android.view.View.OnTouchListener import android.view.View.OnTouchListener
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.ImageViewCompat import androidx.core.widget.ImageViewCompat
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@@ -78,7 +79,7 @@ fun categoryAD(
) )
} }
binding.imageViewTracker.isVisible = item.category.isTrackingEnabled binding.imageViewTracker.isVisible = item.category.isTrackingEnabled
binding.imageViewVisible.isVisible = item.category.isVisibleInLibrary binding.imageViewHidden.isGone = item.category.isVisibleInLibrary
repeat(coverViews.size) { i -> repeat(coverViews.size) { i ->
val cover = item.covers.getOrNull(i) val cover = item.covers.getOrNull(i)
coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run { coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run {

View File

@@ -53,7 +53,7 @@ class TagsCatalogSheet : BaseAdaptiveSheet<SheetTagsBinding>(), OnListItemClickL
binding.editSearch.onFocusChangeListener = this binding.editSearch.onFocusChangeListener = this
binding.editSearch.setOnEditorActionListener(this) binding.editSearch.setOnEditorActionListener(this)
viewModel.content.observe(viewLifecycleOwner, adapter) viewModel.content.observe(viewLifecycleOwner, adapter)
addSheetCallback(this) addSheetCallback(this, viewLifecycleOwner)
disableFitToContents() disableFitToContents()
} }

View File

@@ -262,7 +262,7 @@
android:id="@+id/chips_tags" android:id="@+id/chips_tags"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_small" android:layout_marginTop="@dimen/margin_normal"
android:paddingStart="@dimen/screen_padding" android:paddingStart="@dimen/screen_padding"
android:paddingEnd="@dimen/screen_padding" android:paddingEnd="@dimen/screen_padding"
app:chipSpacingHorizontal="6dp" app:chipSpacingHorizontal="6dp"

View File

@@ -104,13 +104,13 @@
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:contentDescription="@string/check_for_new_chapters" android:contentDescription="@string/check_for_new_chapters"
app:layout_constraintBottom_toBottomOf="@id/textView_subtitle" app:layout_constraintBottom_toBottomOf="@id/textView_subtitle"
app:layout_constraintEnd_toStartOf="@id/imageView_visible" app:layout_constraintEnd_toStartOf="@id/imageView_hidden"
app:layout_constraintStart_toEndOf="@id/textView_subtitle" app:layout_constraintStart_toEndOf="@id/textView_subtitle"
app:layout_constraintTop_toTopOf="@id/textView_subtitle" app:layout_constraintTop_toTopOf="@id/textView_subtitle"
app:srcCompat="@drawable/ic_notification" /> app:srcCompat="@drawable/ic_notification" />
<ImageView <ImageView
android:id="@+id/imageView_visible" android:id="@+id/imageView_hidden"
android:layout_width="16dp" android:layout_width="16dp"
android:layout_height="16dp" android:layout_height="16dp"
android:contentDescription="@string/show_on_shelf" android:contentDescription="@string/show_on_shelf"
@@ -118,7 +118,7 @@
app:layout_constraintEnd_toEndOf="@id/textView_title" app:layout_constraintEnd_toEndOf="@id/textView_title"
app:layout_constraintStart_toEndOf="@id/imageView_tracker" app:layout_constraintStart_toEndOf="@id/imageView_tracker"
app:layout_constraintTop_toTopOf="@id/textView_subtitle" app:layout_constraintTop_toTopOf="@id/textView_subtitle"
app:srcCompat="@drawable/ic_eye" /> app:srcCompat="@drawable/ic_eye_off" />
<ImageView <ImageView
android:id="@+id/imageView_edit" android:id="@+id/imageView_edit"