Option to reverse chapters order

pull/26/head
Koitharu 5 years ago
parent d1aa0f0407
commit 7f37c1f99e

@ -8,5 +8,5 @@ class CloudFlareProtectedException(
val url: String val url: String
) : IOException("Protected by CloudFlare"), ResolvableException { ) : IOException("Protected by CloudFlare"), ResolvableException {
override val resolveTextId: Int = R.string.resolve override val resolveTextId: Int = R.string.captcha_solve
} }

@ -74,6 +74,8 @@ class AppSettings private constructor(private val prefs: SharedPreferences) :
var historyGrouping by BoolPreferenceDelegate(KEY_HISTORY_GROUPING, true) var historyGrouping by BoolPreferenceDelegate(KEY_HISTORY_GROUPING, true)
var chaptersReverse by BoolPreferenceDelegate(KEY_REVERSE_CHAPTERS, false)
val zoomMode by EnumPreferenceDelegate( val zoomMode by EnumPreferenceDelegate(
ZoomMode::class.java, ZoomMode::class.java,
KEY_ZOOM_MODE, KEY_ZOOM_MODE,
@ -175,5 +177,6 @@ class AppSettings private constructor(private val prefs: SharedPreferences) :
const val KEY_RESTORE = "restore" const val KEY_RESTORE = "restore"
const val KEY_HISTORY_GROUPING = "history_grouping" const val KEY_HISTORY_GROUPING = "history_grouping"
const val KEY_DOZE_WHITELIST = "doze_whitelist" const val KEY_DOZE_WHITELIST = "doze_whitelist"
const val KEY_REVERSE_CHAPTERS = "reverse_chapters"
} }
} }

@ -9,6 +9,6 @@ val detailsModule
get() = module { get() = module {
viewModel { (intent: MangaIntent) -> viewModel { (intent: MangaIntent) ->
DetailsViewModel(intent, get(), get(), get(), get(), get()) DetailsViewModel(intent, get(), get(), get(), get(), get(), get())
} }
} }

@ -33,6 +33,11 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private var selectionDecoration: ChaptersSelectionDecoration? = null private var selectionDecoration: ChaptersSelectionDecoration? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onInflateView( override fun onInflateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup? container: ViewGroup?
@ -51,6 +56,9 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged) viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged)
viewModel.isChaptersReversed.observe(viewLifecycleOwner) {
activity?.invalidateOptionsMenu()
}
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -59,12 +67,22 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
super.onDestroyView() super.onDestroyView()
} }
private fun onChaptersChanged(list: List<ChapterListItem>) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
chaptersAdapter?.items = list super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.opt_chapters, menu)
} }
private fun onLoadingStateChanged(isLoading: Boolean) { override fun onPrepareOptionsMenu(menu: Menu) {
binding.progressBar.isVisible = isLoading 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) { override fun onItemClick(item: MangaChapter, view: View) {
@ -159,4 +177,12 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
bottom = insets.bottom bottom = insets.bottom
) )
} }
private fun onChaptersChanged(list: List<ChapterListItem>) {
chaptersAdapter?.items = list
}
private fun onLoadingStateChanged(isLoading: Boolean) {
binding.progressBar.isVisible = isLoading
}
} }

@ -10,6 +10,7 @@ import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
import org.koitharu.kotatsu.core.model.Manga 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.details.ui.model.toListItem
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.history.domain.ChapterExtra
@ -25,7 +26,8 @@ class DetailsViewModel(
private val favouritesRepository: FavouritesRepository, private val favouritesRepository: FavouritesRepository,
private val localMangaRepository: LocalMangaRepository, private val localMangaRepository: LocalMangaRepository,
private val trackingRepository: TrackingRepository, private val trackingRepository: TrackingRepository,
private val mangaDataRepository: MangaDataRepository private val mangaDataRepository: MangaDataRepository,
private val settings: AppSettings
) : BaseViewModel() { ) : BaseViewModel() {
private val mangaData = MutableStateFlow<Manga?>(intent.manga) private val mangaData = MutableStateFlow<Manga?>(intent.manga)
@ -48,6 +50,12 @@ class DetailsViewModel(
trackingRepository.getNewChaptersCount(mangaId) trackingRepository.getNewChaptersCount(mangaId)
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0) }.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() val manga = mangaData.filterNotNull()
.asLiveData(viewModelScope.coroutineContext) .asLiveData(viewModelScope.coroutineContext)
val favouriteCategories = favourite val favouriteCategories = favourite
@ -56,17 +64,20 @@ class DetailsViewModel(
.asLiveData(viewModelScope.coroutineContext) .asLiveData(viewModelScope.coroutineContext)
val readingHistory = history val readingHistory = history
.asLiveData(viewModelScope.coroutineContext) .asLiveData(viewModelScope.coroutineContext)
val isChaptersReversed = chaptersReversed
.asLiveData(viewModelScope.coroutineContext)
val onMangaRemoved = SingleLiveEvent<Manga>() val onMangaRemoved = SingleLiveEvent<Manga>()
val chapters = combine( val chapters = combine(
mangaData.map { it?.chapters.orEmpty() }, mangaData.map { it?.chapters.orEmpty() },
history.map { it?.chapterId }, history.map { it?.chapterId },
newChapters newChapters,
) { chapters, currentId, newCount -> chaptersReversed
) { chapters, currentId, newCount, reversed ->
val currentIndex = chapters.indexOfFirst { it.id == currentId } val currentIndex = chapters.indexOfFirst { it.id == currentId }
val firstNewIndex = chapters.size - newCount val firstNewIndex = chapters.size - newCount
chapters.mapIndexed { index, chapter -> val res = chapters.mapIndexed { index, chapter ->
chapter.toListItem( chapter.toListItem(
when { when {
index >= firstNewIndex -> ChapterExtra.NEW index >= firstNewIndex -> ChapterExtra.NEW
@ -76,6 +87,7 @@ class DetailsViewModel(
} }
) )
} }
if (reversed) res.asReversed() else res
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
init { init {
@ -98,4 +110,8 @@ class DetailsViewModel(
onMangaRemoved.postCall(manga) onMangaRemoved.postCall(manga)
} }
} }
fun setChaptersReversed(newValue: Boolean) {
settings.chaptersReverse = newValue
}
} }

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.list.ui.model package org.koitharu.kotatsu.list.ui.model
import org.koitharu.kotatsu.R 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.exceptions.resolve.ResolvableException
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
@ -46,7 +47,7 @@ fun <C : MutableCollection<ListModel>> List<Manga>.toUi(destination: C, mode: Li
fun Throwable.toErrorState(canRetry: Boolean = true) = ErrorState( fun Throwable.toErrorState(canRetry: Boolean = true) = ErrorState(
exception = this, exception = this,
icon = R.drawable.ic_error_large, icon = getErrorIcon(this),
canRetry = canRetry, canRetry = canRetry,
buttonText = (this as? ResolvableException)?.resolveTextId ?: R.string.try_again buttonText = (this as? ResolvableException)?.resolveTextId ?: R.string.try_again
) )
@ -55,3 +56,8 @@ fun Throwable.toErrorFooter() = ErrorFooter(
exception = this, exception = this,
icon = R.drawable.ic_alert_outline icon = R.drawable.ic_alert_outline
) )
private fun getErrorIcon(error: Throwable) = when (error) {
is CloudFlareProtectedException -> R.drawable.ic_denied_large
else -> R.drawable.ic_error_large
}

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:tint="@color/error"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22 2 17.5 2 12 6.5 2 12 2M12 4C10.1 4 8.4 4.6 7.1 5.7L18.3 16.9C19.3 15.5 20 13.8 20 12C20 7.6 16.4 4 12 4M16.9 18.3L5.7 7.1C4.6 8.4 4 10.1 4 12C4 16.4 7.6 20 12 20C13.9 20 15.6 19.4 16.9 18.3Z" />
</vector>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_reversed"
android:checkable="true"
android:orderInCategory="20"
android:title="@string/reverse"
app:showAsAction="never" />
</menu>

@ -1,36 +1,44 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_share" android:id="@+id/action_share"
android:icon="@drawable/ic_share" android:icon="@drawable/ic_share"
android:orderInCategory="10"
android:title="@string/share" android:title="@string/share"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/action_save" android:id="@+id/action_save"
android:orderInCategory="40"
android:title="@string/save" android:title="@string/save"
android:visible="false" android:visible="false"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item
android:id="@+id/action_delete" android:id="@+id/action_delete"
android:orderInCategory="40"
android:title="@string/delete" android:title="@string/delete"
android:visible="false" android:visible="false"
app:showAsAction="never" /> app:showAsAction="never" />
<item android:id="@+id/action_related" <item
android:id="@+id/action_related"
android:orderInCategory="50"
android:title="@string/related" android:title="@string/related"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item
android:id="@+id/action_browser" android:id="@+id/action_browser"
android:orderInCategory="50"
android:title="@string/open_in_browser" android:title="@string/open_in_browser"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item
android:id="@+id/action_shortcut" android:id="@+id/action_shortcut"
android:orderInCategory="50"
android:title="@string/create_shortcut" android:title="@string/create_shortcut"
app:showAsAction="never" /> app:showAsAction="never" />

@ -189,7 +189,7 @@
<string name="reader_mode_hint">Выбранный режим будет сохранён для текущей манги</string> <string name="reader_mode_hint">Выбранный режим будет сохранён для текущей манги</string>
<string name="silent">Без звука</string> <string name="silent">Без звука</string>
<string name="captcha_required">Необходимо пройти CAPTCHA</string> <string name="captcha_required">Необходимо пройти CAPTCHA</string>
<string name="resolve">Resolve</string> <string name="captcha_solve">Пройти</string>
<string name="clear_cookies">Очистить куки</string> <string name="clear_cookies">Очистить куки</string>
<string name="cookies_cleared">Все куки удалены</string> <string name="cookies_cleared">Все куки удалены</string>
<string name="chapers_checking_progress">Проверка новых глав: %1$d из %2$d</string> <string name="chapers_checking_progress">Проверка новых глав: %1$d из %2$d</string>
@ -199,4 +199,5 @@
<string name="disable_power_optimization">Отключить оптимизацию батареи</string> <string name="disable_power_optimization">Отключить оптимизацию батареи</string>
<string name="power_optimization_already_disabled">Отпимизация батареи уже отключена</string> <string name="power_optimization_already_disabled">Отпимизация батареи уже отключена</string>
<string name="new_chapters_checking">Проверка новых глав</string> <string name="new_chapters_checking">Проверка новых глав</string>
<string name="reverse">В обратном порядке</string>
</resources> </resources>

@ -191,7 +191,7 @@
<string name="reader_mode_hint">Chosen configuration will be remembered for this manga</string> <string name="reader_mode_hint">Chosen configuration will be remembered for this manga</string>
<string name="silent">Silent</string> <string name="silent">Silent</string>
<string name="captcha_required">CAPTCHA is required</string> <string name="captcha_required">CAPTCHA is required</string>
<string name="resolve">Resolve</string> <string name="captcha_solve">Solve</string>
<string name="clear_cookies">Clear cookies</string> <string name="clear_cookies">Clear cookies</string>
<string name="cookies_cleared">All cookies was removed</string> <string name="cookies_cleared">All cookies was removed</string>
<string name="chapers_checking_progress">Checking for new chapters: %1$d of %2$d</string> <string name="chapers_checking_progress">Checking for new chapters: %1$d of %2$d</string>
@ -201,4 +201,5 @@
<string name="power_optimization_simmary">Helps with background operations such as checking for new chapters. Use only if you have a troubles with it</string> <string name="power_optimization_simmary">Helps with background operations such as checking for new chapters. Use only if you have a troubles with it</string>
<string name="disable_power_optimization">Disable power optimization</string> <string name="disable_power_optimization">Disable power optimization</string>
<string name="new_chapters_checking">New chapters checking</string> <string name="new_chapters_checking">New chapters checking</string>
<string name="reverse">Reverse</string>
</resources> </resources>
Loading…
Cancel
Save