diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt index f8b5a4343..df5f6598d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt @@ -18,8 +18,8 @@ import com.google.android.material.R as materialR class AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, TimeAnimator.TimeListener { - private val colorLow = context.getThemeColor(materialR.attr.colorSurfaceContainerLow) - private val colorHigh = context.getThemeColor(materialR.attr.colorSurfaceContainerHigh) + private val colorLow = context.getThemeColor(materialR.attr.colorBackgroundFloating) + private val colorHigh = context.getThemeColor(materialR.attr.colorSurfaceContainer) private var currentColor: Int = colorLow private var alpha: Int = 255 private val interpolator = FastOutSlowInInterpolator() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/sheet/BaseAdaptiveSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/sheet/BaseAdaptiveSheet.kt index f53f5b220..212364282 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/sheet/BaseAdaptiveSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/sheet/BaseAdaptiveSheet.kt @@ -22,6 +22,10 @@ import androidx.core.graphics.ColorUtils import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat 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 com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog @@ -131,14 +135,17 @@ abstract class BaseAdaptiveSheet : AppCompatDialogFragment() { dialog?.window?.statusBarColor = defaultStatusBarColor } - fun addSheetCallback(callback: AdaptiveSheetCallback) { - val b = behavior ?: return + fun addSheetCallback(callback: AdaptiveSheetCallback, lifecycleOwner: LifecycleOwner): Boolean { + val b = behavior ?: return false b.addCallback(callback) val rootView = dialog?.findViewById(materialR.id.design_bottom_sheet) ?: dialog?.findViewById(materialR.id.coordinator) + ?: view if (rootView != null) { callback.onStateChanged(rootView, b.state) } + lifecycleOwner.lifecycle.addObserver(CallbackRemoveObserver(b, callback)) + return true } protected abstract fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): B @@ -146,7 +153,8 @@ abstract class BaseAdaptiveSheet : AppCompatDialogFragment() { protected open fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) = Unit 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) } @@ -292,4 +300,16 @@ abstract class BaseAdaptiveSheet : 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) + } + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt index 6a4aefddd..845f0ea50 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt @@ -93,7 +93,7 @@ fun ImageRequest.Builder.defaultPlaceholders(context: Context): ImageRequest.Bui val errorColor = ColorUtils.blendARGB( context.getThemeColor(materialR.attr.colorErrorContainer), context.getThemeColor(materialR.attr.colorBackgroundFloating), - 0.4f, + 0.25f, ) return placeholder(AnimatedPlaceholderDrawable(context)) .fallback(ColorDrawable(context.getThemeColor(materialR.attr.colorSurfaceContainer))) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index 1006f0e65..4b731dc56 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -97,7 +97,7 @@ import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaTag 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.ui.selector.ScrobblingSelectorSheet import org.koitharu.kotatsu.search.ui.MangaListActivity @@ -320,7 +320,7 @@ class DetailsActivity : override fun onItemClick(item: Bookmark, view: View) { 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() } @@ -535,17 +535,18 @@ class DetailsActivity : } 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 { isLoading -> getString(R.string.loading_) 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.totalChapters == 0 -> getString(R.string.no_chapters) info.totalChapters == -1 -> getString(R.string.error_occurred) else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters) } buttonRead.setProgress(info.history?.percent?.coerceIn(0f, 1f) ?: 0f, true) - buttonDownload?.isEnabled = info.isValid + buttonDownload?.isEnabled = info.isValid && info.canDownload buttonRead.isEnabled = info.isValid } @@ -605,7 +606,7 @@ class DetailsActivity : .show() } else { startActivity( - IntentBuilder(this) + ReaderActivity.IntentBuilder(this) .manga(manga) .branch(viewModel.selectedBranchValue) .incognito(isIncognitoMode) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 2195ff995..b1d0031ac 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -84,8 +84,8 @@ class DetailsViewModel @Inject constructor( ) : BaseViewModel() { private val intent = MangaIntent(savedStateHandle) - private val mangaId = intent.mangaId private var loadingJob: Job + val mangaId = intent.mangaId val onActionDone = MutableEventFlow() val onShowTip = MutableEventFlow() @@ -131,7 +131,7 @@ class DetailsViewModel @Inject constructor( ) val historyInfo: StateFlow = combine( - manga, + details, selectedBranch, history, interactor.observeIncognitoMode(manga), diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/HistoryInfo.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/HistoryInfo.kt index 66fa2d922..2944168ec 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/HistoryInfo.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/HistoryInfo.kt @@ -1,6 +1,9 @@ package org.koitharu.kotatsu.details.ui.model 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 data class HistoryInfo( @@ -8,26 +11,34 @@ data class HistoryInfo( val currentChapter: Int, val history: MangaHistory?, val isIncognitoMode: Boolean, + val isChapterMissing: Boolean, + val canDownload: Boolean, ) { val isValid: Boolean get() = totalChapters >= 0 + + val canContinue: Boolean + get() = history != null && !isChapterMissing } fun HistoryInfo( - manga: Manga?, + manga: MangaDetails?, branch: String?, history: MangaHistory?, isIncognitoMode: Boolean ): 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( totalChapters = chapters?.size ?: -1, - currentChapter = if (history != null && !chapters.isNullOrEmpty()) { - chapters.indexOfFirst { it.id == history.chapterId } - } else { - -1 - }, + currentChapter = currentChapter, history = history, isIncognitoMode = isIncognitoMode, + isChapterMissing = currentChapter == -1, + canDownload = manga?.isLocal == false, ) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt index 7f6d349c6..c280dcb37 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/ChaptersPagesSheet.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.details.ui.pager import android.os.Bundle import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.appcompat.view.ActionMode import androidx.core.view.isVisible @@ -12,6 +13,12 @@ import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver 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.util.ActionModeListener import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver @@ -28,7 +35,7 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver import javax.inject.Inject @AndroidEntryPoint -class ChaptersPagesSheet : BaseAdaptiveSheet(), ActionModeListener { +class ChaptersPagesSheet : BaseAdaptiveSheet(), ActionModeListener, AdaptiveSheetCallback { @Inject lateinit var settings: AppSettings @@ -58,6 +65,7 @@ class ChaptersPagesSheet : BaseAdaptiveSheet(), Actio binding.toolbar.addMenuProvider(menuProvider) actionModeDelegate?.addListener(this, viewLifecycleOwner) + addSheetCallback(this, viewLifecycleOwner) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.pager, this)) viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.pager, null)) @@ -65,6 +73,14 @@ class ChaptersPagesSheet : BaseAdaptiveSheet(), Actio 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) { expandAndLock() viewBinding?.toolbar?.menuView?.isVisible = false @@ -72,7 +88,8 @@ class ChaptersPagesSheet : BaseAdaptiveSheet(), Actio override fun onActionModeFinished(mode: ActionMode) { unlock() - viewBinding?.toolbar?.menuView?.isVisible = true + val state = behavior?.state ?: STATE_EXPANDED + viewBinding?.toolbar?.menuView?.isVisible = state != STATE_COLLAPSED } override fun expandAndLock() { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesViewModel.kt index 2700bba20..1f9bfa15e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PagesViewModel.kt @@ -75,7 +75,9 @@ class PagesViewModel @Inject constructor( private suspend fun doInit(state: State) { 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)) { chaptersLoader.loadSingleChapter(initialChapterId) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt index 1e0ae79b7..121bc77b3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt @@ -10,6 +10,7 @@ import android.view.View.OnClickListener import android.view.View.OnLongClickListener import android.view.View.OnTouchListener import androidx.core.graphics.ColorUtils +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.widget.ImageViewCompat import androidx.lifecycle.LifecycleOwner @@ -78,7 +79,7 @@ fun categoryAD( ) } binding.imageViewTracker.isVisible = item.category.isTrackingEnabled - binding.imageViewVisible.isVisible = item.category.isVisibleInLibrary + binding.imageViewHidden.isGone = item.category.isVisibleInLibrary repeat(coverViews.size) { i -> val cover = item.covers.getOrNull(i) coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogSheet.kt index dca92f60c..ea17faec5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogSheet.kt @@ -53,7 +53,7 @@ class TagsCatalogSheet : BaseAdaptiveSheet(), OnListItemClickL binding.editSearch.onFocusChangeListener = this binding.editSearch.setOnEditorActionListener(this) viewModel.content.observe(viewLifecycleOwner, adapter) - addSheetCallback(this) + addSheetCallback(this, viewLifecycleOwner) disableFitToContents() } diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml index e64cd889b..04bf6c818 100644 --- a/app/src/main/res/layout/activity_details.xml +++ b/app/src/main/res/layout/activity_details.xml @@ -262,7 +262,7 @@ android:id="@+id/chips_tags" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/margin_small" + android:layout_marginTop="@dimen/margin_normal" android:paddingStart="@dimen/screen_padding" android:paddingEnd="@dimen/screen_padding" app:chipSpacingHorizontal="6dp" diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml index cf7b3ccd9..cc71098b6 100644 --- a/app/src/main/res/layout/item_category.xml +++ b/app/src/main/res/layout/item_category.xml @@ -104,13 +104,13 @@ android:layout_marginEnd="4dp" android:contentDescription="@string/check_for_new_chapters" 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_constraintTop_toTopOf="@id/textView_subtitle" app:srcCompat="@drawable/ic_notification" /> + app:srcCompat="@drawable/ic_eye_off" />