Option to prefer Rtl reader

This commit is contained in:
Koitharu
2020-11-02 19:13:07 +02:00
parent 72fdc7796f
commit e497781359
16 changed files with 113 additions and 86 deletions

View File

@@ -70,7 +70,7 @@ dependencies {
implementation 'androidx.activity:activity-ktx:1.2.0-beta01' implementation 'androidx.activity:activity-ktx:1.2.0-beta01'
implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01' implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-beta01' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha06' implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha06'
implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01' implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01'

View File

@@ -88,6 +88,11 @@ class AppSettings private constructor(resources: Resources, private val prefs: S
false false
) )
val isPreferRtlReader by BoolPreferenceDelegate(
resources.getString(R.string.key_reader_prefer_rtl),
false
)
val trackSources by StringSetPreferenceDelegate( val trackSources by StringSetPreferenceDelegate(
resources.getString(R.string.key_track_sources), resources.getString(R.string.key_track_sources),
arraySetOf(TRACK_FAVOURITES, TRACK_HISTORY) arraySetOf(TRACK_FAVOURITES, TRACK_HISTORY)

View File

@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.core.prefs
enum class ReaderMode(val id: Int) { enum class ReaderMode(val id: Int) {
UNKNOWN(0),
STANDARD(1), STANDARD(1),
WEBTOON(2), WEBTOON(2),
REVERSED(3); REVERSED(3);

View File

@@ -10,7 +10,6 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.utils.ext.await import org.koitharu.kotatsu.utils.ext.await
import org.koitharu.kotatsu.utils.ext.medianOrNull import org.koitharu.kotatsu.utils.ext.medianOrNull
import java.io.InputStream import java.io.InputStream
@@ -24,7 +23,7 @@ object MangaUtils : KoinComponent {
*/ */
@WorkerThread @WorkerThread
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
suspend fun determineReaderMode(pages: List<MangaPage>): ReaderMode? { suspend fun determineMangaIsWebtoon(pages: List<MangaPage>): Boolean? {
try { try {
val page = pages.medianOrNull() ?: return null val page = pages.medianOrNull() ?: return null
val url = page.source.repository.getPageFullUrl(page) val url = page.source.repository.getPageFullUrl(page)
@@ -45,10 +44,7 @@ object MangaUtils : KoinComponent {
getBitmapSize(it.body?.byteStream()) getBitmapSize(it.body?.byteStream())
} }
} }
return when { return size.width * 2 < size.height
size.width * 2 < size.height -> ReaderMode.WEBTOON
else -> ReaderMode.STANDARD
}
} catch (e: Exception) { } catch (e: Exception) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
e.printStackTrace() e.printStackTrace()

View File

@@ -1,13 +1,10 @@
package org.koitharu.kotatsu.ui.base package org.koitharu.kotatsu.ui.base
import android.content.Context import android.content.Context
import android.os.Parcelable
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import coil.ImageLoader import coil.ImageLoader
import moxy.MvpAppCompatFragment import moxy.MvpAppCompatFragment
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.utils.delegates.ParcelableArgumentDelegate
import org.koitharu.kotatsu.utils.delegates.StringArgumentDelegate
abstract class BaseFragment( abstract class BaseFragment(
@LayoutRes contentLayoutId: Int @LayoutRes contentLayoutId: Int
@@ -15,11 +12,6 @@ abstract class BaseFragment(
protected val coil by inject<ImageLoader>() protected val coil by inject<ImageLoader>()
fun stringArg(name: String) = StringArgumentDelegate(name)
@Deprecated("Use extension", replaceWith = ReplaceWith("parcelableArgument(name)"))
fun <T : Parcelable> arg(name: String) = ParcelableArgumentDelegate<T>(name)
open fun getTitle(): CharSequence? = null open fun getTitle(): CharSequence? = null
override fun onAttach(context: Context) { override fun onAttach(context: Context) {

View File

@@ -125,7 +125,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
replace(R.id.container, ReversedReaderFragment.newInstance(state)) replace(R.id.container, ReversedReaderFragment.newInstance(state))
} }
} }
else -> if (currentReader !is PagerReaderFragment) { ReaderMode.STANDARD -> if (currentReader !is PagerReaderFragment) {
supportFragmentManager.commit { supportFragmentManager.commit {
replace(R.id.container, PagerReaderFragment.newInstance(state)) replace(R.id.container, PagerReaderFragment.newInstance(state))
} }
@@ -135,7 +135,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
when (mode) { when (mode) {
ReaderMode.WEBTOON -> R.drawable.ic_script ReaderMode.WEBTOON -> R.drawable.ic_script
ReaderMode.REVERSED -> R.drawable.ic_read_reversed ReaderMode.REVERSED -> R.drawable.ic_read_reversed
else -> R.drawable.ic_book_page ReaderMode.STANDARD -> R.drawable.ic_book_page
} }
) )
appbar_top.postDelayed(1000) { appbar_top.postDelayed(1000) {
@@ -158,70 +158,70 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
outState.putParcelable(EXTRA_STATE, state) outState.putParcelable(EXTRA_STATE, state)
} }
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem): Boolean {
R.id.action_reader_mode -> { when (item.itemId) {
ReaderConfigDialog.show( R.id.action_reader_mode -> {
supportFragmentManager, when (reader) { ReaderConfigDialog.show(
is PagerReaderFragment -> ReaderMode.STANDARD supportFragmentManager, when (reader) {
is WebtoonReaderFragment -> ReaderMode.WEBTOON is PagerReaderFragment -> ReaderMode.STANDARD
is ReversedReaderFragment -> ReaderMode.REVERSED is WebtoonReaderFragment -> ReaderMode.WEBTOON
else -> ReaderMode.UNKNOWN is ReversedReaderFragment -> ReaderMode.REVERSED
} else -> {
) showWaitWhileLoading()
true return false
} }
R.id.action_settings -> { }
startActivity(SimpleSettingsActivity.newReaderSettingsIntent(this)) )
true }
} R.id.action_settings -> {
R.id.action_chapters -> { startActivity(SimpleSettingsActivity.newReaderSettingsIntent(this))
ChaptersDialog.show( }
supportFragmentManager, R.id.action_chapters -> {
state.manga.chapters.orEmpty(), ChaptersDialog.show(
state.chapterId supportFragmentManager,
) state.manga.chapters.orEmpty(),
true state.chapterId
} )
R.id.action_screen_rotate -> { }
orientationHelper.toggleOrientation() R.id.action_screen_rotate -> {
true orientationHelper.toggleOrientation()
} }
R.id.action_pages_thumbs -> { R.id.action_pages_thumbs -> {
if (reader?.hasItems == true) { if (reader?.hasItems == true) {
val pages = reader?.getPages() val pages = reader?.getPages()
if (!pages.isNullOrEmpty()) { if (!pages.isNullOrEmpty()) {
PagesThumbnailsSheet.show( PagesThumbnailsSheet.show(
supportFragmentManager, pages, supportFragmentManager, pages,
state.chapter?.name ?: title?.toString().orEmpty() state.chapter?.name ?: title?.toString().orEmpty()
) )
} else {
showWaitWhileLoading()
}
} else { } else {
showWaitWhileLoading() showWaitWhileLoading()
} }
} else {
showWaitWhileLoading()
} }
true R.id.action_save_page -> {
} if (reader?.hasItems == true) {
R.id.action_save_page -> { if (ContextCompat.checkSelfPermission(
if (reader?.hasItems == true) { this,
if (ContextCompat.checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE
this, ) == PackageManager.PERMISSION_GRANTED
Manifest.permission.WRITE_EXTERNAL_STORAGE ) {
) == PackageManager.PERMISSION_GRANTED onActivityResult(true)
) { } else {
onActivityResult(true) registerForActivityResult(
ActivityResultContracts.RequestPermission(),
this
).launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
} else { } else {
registerForActivityResult( showWaitWhileLoading()
ActivityResultContracts.RequestPermission(),
this
).launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} }
} else {
showWaitWhileLoading()
} }
true else -> return super.onOptionsItemSelected(item)
} }
else -> super.onOptionsItemSelected(item) return true
} }
override fun onActivityResult(result: Boolean) { override fun onActivityResult(result: Boolean) {

View File

@@ -18,9 +18,8 @@ class ReaderConfigDialog : AlertDialogFragment(R.layout.dialog_reader_config),
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mode = arguments?.getInt(ARG_MODE, ReaderMode.UNKNOWN.id) mode = arguments?.getInt(ARG_MODE)
?.let { ReaderMode.valueOf(it) } ?.let { ReaderMode.valueOf(it) }
?.takeUnless { it == ReaderMode.UNKNOWN }
?: ReaderMode.STANDARD ?: ReaderMode.STANDARD
} }

View File

@@ -13,6 +13,7 @@ import org.koin.core.component.inject
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.domain.MangaDataRepository import org.koitharu.kotatsu.domain.MangaDataRepository
import org.koitharu.kotatsu.domain.MangaUtils import org.koitharu.kotatsu.domain.MangaUtils
@@ -28,27 +29,29 @@ import org.koitharu.kotatsu.utils.ext.mimeType
class ReaderPresenter : BasePresenter<ReaderView>() { class ReaderPresenter : BasePresenter<ReaderView>() {
private val dataRepository by inject<MangaDataRepository>() private val dataRepository by inject<MangaDataRepository>()
private val appSettings by inject<AppSettings>()
fun init(manga: Manga) { fun init(manga: Manga) {
presenterScope.launch { presenterScope.launch {
viewState.onLoadingStateChanged(isLoading = true) viewState.onLoadingStateChanged(isLoading = true)
try { try {
val mode = withContext(Dispatchers.IO) { val mode = withContext(Dispatchers.Default) {
val repo = manga.source.repository val repo = manga.source.repository
val chapter = val chapter =
(manga.chapters ?: throw RuntimeException("Chapters is null")).random() (manga.chapters ?: throw RuntimeException("Chapters is null")).random()
var mode = dataRepository.getReaderMode(manga.id) var mode = dataRepository.getReaderMode(manga.id)
if (mode == null) { if (mode == null) {
val pages = repo.getPages(chapter) val pages = repo.getPages(chapter)
mode = MangaUtils.determineReaderMode(pages) val isWebtoon = MangaUtils.determineMangaIsWebtoon(pages)
if (mode != null) { mode = getReaderMode(isWebtoon)
if (isWebtoon != null) {
dataRepository.savePreferences( dataRepository.savePreferences(
manga = manga, manga = manga,
mode = mode mode = mode
) )
} }
} }
mode ?: ReaderMode.UNKNOWN mode
} }
viewState.onInitReader(manga, mode) viewState.onInitReader(manga, mode)
} catch (_: CancellationException) { } catch (_: CancellationException) {
@@ -101,6 +104,12 @@ class ReaderPresenter : BasePresenter<ReaderView>() {
} }
} }
private fun getReaderMode(isWebtoon: Boolean?) = when {
isWebtoon == true -> ReaderMode.WEBTOON
appSettings.isPreferRtlReader -> ReaderMode.REVERSED
else -> ReaderMode.STANDARD
}
companion object : KoinComponent { companion object : KoinComponent {
fun saveState(state: ReaderState) { fun saveState(state: ReaderState) {

View File

@@ -3,14 +3,16 @@ package org.koitharu.kotatsu.ui.search
import moxy.ktx.moxyPresenter import moxy.ktx.moxyPresenter
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.ui.list.MangaListFragment import org.koitharu.kotatsu.ui.list.MangaListFragment
import org.koitharu.kotatsu.utils.ext.parcelableArgument
import org.koitharu.kotatsu.utils.ext.stringArgument
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class SearchFragment : MangaListFragment<Unit>() { class SearchFragment : MangaListFragment<Unit>() {
private val presenter by moxyPresenter(factory = ::SearchPresenter) private val presenter by moxyPresenter(factory = ::SearchPresenter)
private val query by stringArg(ARG_QUERY) private val query by stringArgument(ARG_QUERY)
private val source by arg<MangaSource>(ARG_SOURCE) private val source by parcelableArgument<MangaSource>(ARG_SOURCE)
override fun onRequestMoreItems(offset: Int) { override fun onRequestMoreItems(offset: Int) {
presenter.loadList(source, query.orEmpty(), offset) presenter.loadList(source, query.orEmpty(), offset)

View File

@@ -2,14 +2,15 @@ package org.koitharu.kotatsu.ui.search.global
import moxy.ktx.moxyPresenter import moxy.ktx.moxyPresenter
import org.koitharu.kotatsu.ui.list.MangaListFragment import org.koitharu.kotatsu.ui.list.MangaListFragment
import org.koitharu.kotatsu.utils.ext.stringArgument
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class GlobalSearchFragment: MangaListFragment<Unit>() { class GlobalSearchFragment : MangaListFragment<Unit>() {
private val presenter by moxyPresenter(factory = ::GlobalSearchPresenter) private val presenter by moxyPresenter(factory = ::GlobalSearchPresenter)
private val query by stringArg(ARG_QUERY) private val query by stringArgument(ARG_QUERY)
override fun onRequestMoreItems(offset: Int) { override fun onRequestMoreItems(offset: Int) {
if (offset == 0) { if (offset == 0) {

View File

@@ -20,4 +20,9 @@ inline fun <T : Parcelable> Fragment.parcelableArgument(name: String) =
lazy<T>(LazyThreadSafetyMode.NONE) { lazy<T>(LazyThreadSafetyMode.NONE) {
requireArguments().getParcelable(name) requireArguments().getParcelable(name)
?: error("No argument $name passed in ${javaClass.simpleName}") ?: error("No argument $name passed in ${javaClass.simpleName}")
} }
@Suppress("NOTHING_TO_INLINE")
inline fun Fragment.stringArgument(name: String) = lazy(LazyThreadSafetyMode.NONE) {
arguments?.getString(name)
}

View File

@@ -160,4 +160,6 @@
<string name="update_check_failed">Ошибка при проверке обновления</string> <string name="update_check_failed">Ошибка при проверке обновления</string>
<string name="no_update_available">Нет доступных обновлений</string> <string name="no_update_available">Нет доступных обновлений</string>
<string name="right_to_left">Справа налево</string> <string name="right_to_left">Справа налево</string>
<string name="prefer_rtl_reader">Предпочитать режим Справа налево</string>
<string name="prefer_rtl_reader_summary">Вы можете настроить режим чтения для каждой манги отдельно</string>
</resources> </resources>

View File

@@ -23,6 +23,7 @@
<string name="key_notifications_vibrate">notifications_vibrate</string> <string name="key_notifications_vibrate">notifications_vibrate</string>
<string name="key_notifications_light">notifications_light</string> <string name="key_notifications_light">notifications_light</string>
<string name="key_reader_animation">reader_animation</string> <string name="key_reader_animation">reader_animation</string>
<string name="key_reader_prefer_rtl">reader_prefer_rtl</string>
<string name="key_app_password">app_password</string> <string name="key_app_password">app_password</string>
<string name="key_protect_app">protect_app</string> <string name="key_protect_app">protect_app</string>
<string name="key_app_version">app_version</string> <string name="key_app_version">app_version</string>

View File

@@ -161,4 +161,6 @@
<string name="update_check_failed">Update check failed</string> <string name="update_check_failed">Update check failed</string>
<string name="no_update_available">No updates available</string> <string name="no_update_available">No updates available</string>
<string name="right_to_left">Right to left</string> <string name="right_to_left">Right to left</string>
<string name="prefer_rtl_reader">Prefer Right to left reader</string>
<string name="prefer_rtl_reader_summary">You can set up the reading mode for each manga separately</string>
</resources> </resources>

View File

@@ -70,6 +70,13 @@
android:title="@string/pages_animation" android:title="@string/pages_animation"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_reader_prefer_rtl"
android:summary="@string/prefer_rtl_reader_summary"
android:title="@string/prefer_rtl_reader"
app:iconSpaceReserved="false" />
<PreferenceCategory <PreferenceCategory
android:title="@string/new_chapters" android:title="@string/new_chapters"
app:allowDividerAbove="true" app:allowDividerAbove="true"

View File

@@ -13,8 +13,15 @@
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
android:title="@string/pages_animation"
android:key="@string/key_reader_animation" android:key="@string/key_reader_animation"
android:title="@string/pages_animation"
app:iconSpaceReserved="false" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_reader_prefer_rtl"
android:summary="@string/prefer_rtl_reader_summary"
android:title="@string/prefer_rtl_reader"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
</PreferenceScreen> </PreferenceScreen>