From 7f37c1f99e61f73ce2cb4487d3e9dbbed4d817b2 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 25 Jan 2021 19:09:01 +0200 Subject: [PATCH] Option to reverse chapters order --- .../CloudFlareProtectedException.kt | 2 +- .../kotatsu/core/prefs/AppSettings.kt | 3 + .../koitharu/kotatsu/details/DetailsModule.kt | 2 +- .../kotatsu/details/ui/ChaptersFragment.kt | 34 ++++++++-- .../kotatsu/details/ui/DetailsViewModel.kt | 24 +++++-- .../list/ui/model/ListModelConversionExt.kt | 10 ++- app/src/main/res/drawable/ic_denied_large.xml | 12 ++++ app/src/main/res/menu/opt_chapters.xml | 13 ++++ app/src/main/res/menu/opt_details.xml | 64 +++++++++++-------- app/src/main/res/values-ru/strings.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- 11 files changed, 128 insertions(+), 42 deletions(-) create mode 100644 app/src/main/res/drawable/ic_denied_large.xml create mode 100644 app/src/main/res/menu/opt_chapters.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt index f435f9930..db2cdca33 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt @@ -8,5 +8,5 @@ class CloudFlareProtectedException( val url: String ) : IOException("Protected by CloudFlare"), ResolvableException { - override val resolveTextId: Int = R.string.resolve + override val resolveTextId: Int = R.string.captcha_solve } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 1427eea24..3b627a13e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -74,6 +74,8 @@ class AppSettings private constructor(private val prefs: SharedPreferences) : var historyGrouping by BoolPreferenceDelegate(KEY_HISTORY_GROUPING, true) + var chaptersReverse by BoolPreferenceDelegate(KEY_REVERSE_CHAPTERS, false) + val zoomMode by EnumPreferenceDelegate( ZoomMode::class.java, KEY_ZOOM_MODE, @@ -175,5 +177,6 @@ class AppSettings private constructor(private val prefs: SharedPreferences) : const val KEY_RESTORE = "restore" const val KEY_HISTORY_GROUPING = "history_grouping" const val KEY_DOZE_WHITELIST = "doze_whitelist" + const val KEY_REVERSE_CHAPTERS = "reverse_chapters" } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt b/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt index 176b50785..bc8ec0a3d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt @@ -9,6 +9,6 @@ val detailsModule get() = module { viewModel { (intent: MangaIntent) -> - DetailsViewModel(intent, get(), get(), get(), get(), get()) + DetailsViewModel(intent, get(), get(), get(), get(), get(), get()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt index 8a9baca1e..8ebdf8389 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt @@ -33,6 +33,11 @@ class ChaptersFragment : BaseFragment(), private var actionMode: ActionMode? = null private var selectionDecoration: ChaptersSelectionDecoration? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + override fun onInflateView( inflater: LayoutInflater, container: ViewGroup? @@ -51,6 +56,9 @@ class ChaptersFragment : BaseFragment(), viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged) + viewModel.isChaptersReversed.observe(viewLifecycleOwner) { + activity?.invalidateOptionsMenu() + } } override fun onDestroyView() { @@ -59,12 +67,22 @@ class ChaptersFragment : BaseFragment(), super.onDestroyView() } - private fun onChaptersChanged(list: List) { - chaptersAdapter?.items = list + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.opt_chapters, menu) } - private fun onLoadingStateChanged(isLoading: Boolean) { - binding.progressBar.isVisible = isLoading + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + menu.findItem(R.id.action_reversed).isChecked = viewModel.isChaptersReversed.value == true + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.action_reversed -> { + viewModel.setChaptersReversed(!item.isChecked) + true + } + else -> super.onOptionsItemSelected(item) } override fun onItemClick(item: MangaChapter, view: View) { @@ -159,4 +177,12 @@ class ChaptersFragment : BaseFragment(), bottom = insets.bottom ) } + + private fun onChaptersChanged(list: List) { + chaptersAdapter?.items = list + } + + private fun onLoadingStateChanged(isLoading: Boolean) { + binding.progressBar.isVisible = isLoading + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 5c56f9cf1..247af2b9f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -10,6 +10,7 @@ import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.details.ui.model.toListItem import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.history.domain.ChapterExtra @@ -25,7 +26,8 @@ class DetailsViewModel( private val favouritesRepository: FavouritesRepository, private val localMangaRepository: LocalMangaRepository, private val trackingRepository: TrackingRepository, - private val mangaDataRepository: MangaDataRepository + private val mangaDataRepository: MangaDataRepository, + private val settings: AppSettings ) : BaseViewModel() { private val mangaData = MutableStateFlow(intent.manga) @@ -48,6 +50,12 @@ class DetailsViewModel( trackingRepository.getNewChaptersCount(mangaId) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0) + private val chaptersReversed = settings.observe() + .filter { it == AppSettings.KEY_REVERSE_CHAPTERS } + .map { settings.chaptersReverse } + .onStart { emit(settings.chaptersReverse) } + .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false) + val manga = mangaData.filterNotNull() .asLiveData(viewModelScope.coroutineContext) val favouriteCategories = favourite @@ -56,17 +64,20 @@ class DetailsViewModel( .asLiveData(viewModelScope.coroutineContext) val readingHistory = history .asLiveData(viewModelScope.coroutineContext) + val isChaptersReversed = chaptersReversed + .asLiveData(viewModelScope.coroutineContext) val onMangaRemoved = SingleLiveEvent() val chapters = combine( mangaData.map { it?.chapters.orEmpty() }, history.map { it?.chapterId }, - newChapters - ) { chapters, currentId, newCount -> + newChapters, + chaptersReversed + ) { chapters, currentId, newCount, reversed -> val currentIndex = chapters.indexOfFirst { it.id == currentId } val firstNewIndex = chapters.size - newCount - chapters.mapIndexed { index, chapter -> + val res = chapters.mapIndexed { index, chapter -> chapter.toListItem( when { index >= firstNewIndex -> ChapterExtra.NEW @@ -76,6 +87,7 @@ class DetailsViewModel( } ) } + if (reversed) res.asReversed() else res }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) init { @@ -98,4 +110,8 @@ class DetailsViewModel( onMangaRemoved.postCall(manga) } } + + fun setChaptersReversed(newValue: Boolean) { + settings.chaptersReverse = newValue + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt index a5c9412b0..834d89247 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.list.ui.model import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.prefs.ListMode @@ -46,7 +47,7 @@ fun > List.toUi(destination: C, mode: Li fun Throwable.toErrorState(canRetry: Boolean = true) = ErrorState( exception = this, - icon = R.drawable.ic_error_large, + icon = getErrorIcon(this), canRetry = canRetry, buttonText = (this as? ResolvableException)?.resolveTextId ?: R.string.try_again ) @@ -54,4 +55,9 @@ fun Throwable.toErrorState(canRetry: Boolean = true) = ErrorState( fun Throwable.toErrorFooter() = ErrorFooter( exception = this, icon = R.drawable.ic_alert_outline -) \ No newline at end of file +) + +private fun getErrorIcon(error: Throwable) = when (error) { + is CloudFlareProtectedException -> R.drawable.ic_denied_large + else -> R.drawable.ic_error_large +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_denied_large.xml b/app/src/main/res/drawable/ic_denied_large.xml new file mode 100644 index 000000000..79218578d --- /dev/null +++ b/app/src/main/res/drawable/ic_denied_large.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/opt_chapters.xml b/app/src/main/res/menu/opt_chapters.xml new file mode 100644 index 000000000..11cd67383 --- /dev/null +++ b/app/src/main/res/menu/opt_chapters.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/opt_details.xml b/app/src/main/res/menu/opt_details.xml index fbbf706fc..afc59ee34 100644 --- a/app/src/main/res/menu/opt_details.xml +++ b/app/src/main/res/menu/opt_details.xml @@ -1,37 +1,45 @@ - + - + - + - + - + - + - + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index db68f9eba..6ab5e3b79 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -189,7 +189,7 @@ Выбранный режим будет сохранён для текущей манги Без звука Необходимо пройти CAPTCHA - Resolve + Пройти Очистить куки Все куки удалены Проверка новых глав: %1$d из %2$d @@ -199,4 +199,5 @@ Отключить оптимизацию батареи Отпимизация батареи уже отключена Проверка новых глав + В обратном порядке \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 684899d15..ff9bb127a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -191,7 +191,7 @@ Chosen configuration will be remembered for this manga Silent CAPTCHA is required - Resolve + Solve Clear cookies All cookies was removed Checking for new chapters: %1$d of %2$d @@ -201,4 +201,5 @@ Helps with background operations such as checking for new chapters. Use only if you have a troubles with it Disable power optimization New chapters checking + Reverse \ No newline at end of file