Option to reverse chapters order
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,11 @@ class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
|
||||
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<FragmentChaptersBinding>(),
|
||||
|
||||
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<FragmentChaptersBinding>(),
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun onChaptersChanged(list: List<ChapterListItem>) {
|
||||
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<FragmentChaptersBinding>(),
|
||||
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.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<Manga?>(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<Manga>()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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 <C : MutableCollection<ListModel>> List<Manga>.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
|
||||
)
|
||||
)
|
||||
|
||||
private fun getErrorIcon(error: Throwable) = when (error) {
|
||||
is CloudFlareProtectedException -> R.drawable.ic_denied_large
|
||||
else -> R.drawable.ic_error_large
|
||||
}
|
||||
12
app/src/main/res/drawable/ic_denied_large.xml
Normal file
12
app/src/main/res/drawable/ic_denied_large.xml
Normal file
@@ -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>
|
||||
13
app/src/main/res/menu/opt_chapters.xml
Normal file
13
app/src/main/res/menu/opt_chapters.xml
Normal file
@@ -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,37 +1,45 @@
|
||||
<?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">
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
android:icon="@drawable/ic_share"
|
||||
android:title="@string/share"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
android:icon="@drawable/ic_share"
|
||||
android:orderInCategory="10"
|
||||
android:title="@string/share"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_save"
|
||||
android:title="@string/save"
|
||||
android:visible="false"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_save"
|
||||
android:orderInCategory="40"
|
||||
android:title="@string/save"
|
||||
android:visible="false"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:title="@string/delete"
|
||||
android:visible="false"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:orderInCategory="40"
|
||||
android:title="@string/delete"
|
||||
android:visible="false"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item android:id="@+id/action_related"
|
||||
android:title="@string/related"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_related"
|
||||
android:orderInCategory="50"
|
||||
android:title="@string/related"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_browser"
|
||||
android:title="@string/open_in_browser"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_browser"
|
||||
android:orderInCategory="50"
|
||||
android:title="@string/open_in_browser"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_shortcut"
|
||||
android:title="@string/create_shortcut"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_shortcut"
|
||||
android:orderInCategory="50"
|
||||
android:title="@string/create_shortcut"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
@@ -189,7 +189,7 @@
|
||||
<string name="reader_mode_hint">Выбранный режим будет сохранён для текущей манги</string>
|
||||
<string name="silent">Без звука</string>
|
||||
<string name="captcha_required">Необходимо пройти CAPTCHA</string>
|
||||
<string name="resolve">Resolve</string>
|
||||
<string name="captcha_solve">Пройти</string>
|
||||
<string name="clear_cookies">Очистить куки</string>
|
||||
<string name="cookies_cleared">Все куки удалены</string>
|
||||
<string name="chapers_checking_progress">Проверка новых глав: %1$d из %2$d</string>
|
||||
@@ -199,4 +199,5 @@
|
||||
<string name="disable_power_optimization">Отключить оптимизацию батареи</string>
|
||||
<string name="power_optimization_already_disabled">Отпимизация батареи уже отключена</string>
|
||||
<string name="new_chapters_checking">Проверка новых глав</string>
|
||||
<string name="reverse">В обратном порядке</string>
|
||||
</resources>
|
||||
@@ -191,7 +191,7 @@
|
||||
<string name="reader_mode_hint">Chosen configuration will be remembered for this manga</string>
|
||||
<string name="silent">Silent</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="cookies_cleared">All cookies was removed</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="disable_power_optimization">Disable power optimization</string>
|
||||
<string name="new_chapters_checking">New chapters checking</string>
|
||||
<string name="reverse">Reverse</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user