diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index 2cd2e148a..c34c502a8 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -374,4 +374,8 @@ object PreferenceKeys { const val hideUpdatesButton = "pref_hide_updates_button" const val hideHistoryButton = "pref_hide_history_button" + + const val pageLayout = "page_layout" + + const val invertDoublePages = "invert_double_pages" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index ac864479e..dcfc62eb8 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.data.track.anilist.Anilist import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig import eu.kanade.tachiyomi.widget.ExtendedNavigationView import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.onEach @@ -489,4 +490,8 @@ class PreferencesHelper(val context: Context) { fun hideUpdatesButton() = flowPrefs.getBoolean(Keys.hideUpdatesButton, false) fun hideHistoryButton() = flowPrefs.getBoolean(Keys.hideHistoryButton, false) + + fun pageLayout() = flowPrefs.getInt(Keys.pageLayout, PagerConfig.PageLayout.AUTOMATIC) + + fun invertDoublePages() = flowPrefs.getBoolean(Keys.invertDoublePages, false) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 472d0be1e..06c8f6f2a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -58,6 +58,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsSheet import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer +import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer @@ -66,6 +67,7 @@ import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.GLUtil import eu.kanade.tachiyomi.util.system.hasDisplayCutout +import eu.kanade.tachiyomi.util.system.isLTR import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.defaultBar import eu.kanade.tachiyomi.util.view.hideBar @@ -114,6 +116,10 @@ class ReaderActivity : BaseRxActivity() addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } } + + const val SHIFT_DOUBLE_PAGES = "shiftingDoublePages" + const val SHIFTED_PAGE_INDEX = "shiftedPageIndex" + const val SHIFTED_CHAP_INDEX = "shiftedChapterIndex" } private val preferences: PreferencesHelper by injectLazy() @@ -143,6 +149,10 @@ class ReaderActivity : BaseRxActivity() private val autoScrollFlow = MutableSharedFlow() private var autoScrollJob: Job? = null private val sourceManager: SourceManager by injectLazy() + + private var lastShiftDoubleState: Boolean? = null + private var indexPageToShift: Int? = null + private var indexChapterToShift: Long? = null // SY <-- /** @@ -192,6 +202,11 @@ class ReaderActivity : BaseRxActivity() // --> EH ehUtilsVisible = savedInstanceState.getBoolean(::ehUtilsVisible.name) // <-- EH + // SY --> + lastShiftDoubleState = savedInstanceState.get(SHIFT_DOUBLE_PAGES) as? Boolean + indexPageToShift = savedInstanceState.get(SHIFTED_PAGE_INDEX) as? Int + indexChapterToShift = savedInstanceState.get(SHIFTED_CHAP_INDEX) as? Long + // SY <-- } config = ReaderConfig() @@ -262,6 +277,18 @@ class ReaderActivity : BaseRxActivity() // EXH --> outState.putBoolean(::ehUtilsVisible.name, ehUtilsVisible) // EXH <-- + // SY --> + (viewer as? PagerViewer)?.let { pViewer -> + val config = pViewer.config + outState.putBoolean(SHIFT_DOUBLE_PAGES, config.shiftDoublePage) + if (config.shiftDoublePage && config.doublePages) { + pViewer.getShiftedPage()?.let { + outState.putInt(SHIFTED_PAGE_INDEX, it.index) + outState.putLong(SHIFTED_CHAP_INDEX, it.chapter.chapter.id ?: 0L) + } + } + } + // SY <-- if (!isChangingConfigurations) { presenter.onSaveInstanceStateNonConfigurationChange() } @@ -543,6 +570,30 @@ class ReaderActivity : BaseRxActivity() } } + with(binding.doublePage) { + setTooltip(R.string.page_layout) + + setOnClickListener { + if (preferences.pageLayout().get() == PagerConfig.PageLayout.AUTOMATIC) { + (viewer as? PagerViewer)?.config?.let { config -> + config.doublePages = !config.doublePages + reloadChapters(config.doublePages, true) + } + updateBottomButtons() + } else { + preferences.pageLayout().set(1 - preferences.pageLayout().get()) + } + } + } + + with(binding.shiftPageButton) { + setTooltip(R.string.shift_double_pages) + + setOnClickListener { + shiftDoublePages() + } + } + binding.expandEhButton.clicks() .onEach { ehUtilsVisible = !ehUtilsVisible @@ -723,7 +774,8 @@ class ReaderActivity : BaseRxActivity() actionReadingMode.isVisible = ReaderBottomButton.ReadingMode.isIn(enabledButtons) actionRotation.isVisible = ReaderBottomButton.Rotation.isIn(enabledButtons) - // doublePage.isVisible = viewer is PagerViewer && ReaderBottomButton.PageLayout.isIn(enabledButtons) + doublePage.isVisible = + viewer is PagerViewer && ReaderBottomButton.PageLayout.isIn(enabledButtons) && !preferences.dualPageSplitPaged().get() actionCropBorders.isVisible = if (viewer is PagerViewer) { ReaderBottomButton.CropBordersPager.isIn(enabledButtons) @@ -739,8 +791,44 @@ class ReaderActivity : BaseRxActivity() ReaderBottomButton.WebView.isIn(enabledButtons) actionChapterList.isVisible = ReaderBottomButton.ViewChapters.isIn(enabledButtons) - // shiftPageButton.isVisible = ((viewer as? PagerViewer)?.config?.doublePages ?: false) && ReaderBottomButton.ShiftDoublePage.isIn(enabledButtons) - // binding.toolbar.menu.findItem(R.id.action_shift_double_page)?.isVisible = ((viewer as? PagerViewer)?.config?.doublePages ?: false) && !ReaderBottomButton.ShiftDoublePage.isIn(enabledButtons) + shiftPageButton.isVisible = (viewer as? PagerViewer)?.config?.doublePages ?: false + } + } + + fun reloadChapters(doublePages: Boolean, force: Boolean = false) { + val pViewer = viewer as? PagerViewer ?: return + pViewer.updateShifting() + if (!force && pViewer.config.autoDoublePages) { + setDoublePageMode(pViewer) + } else { + pViewer.config.doublePages = doublePages + } + val currentChapter = presenter.getCurrentChapter() + if (doublePages) { + // If we're moving from singe to double, we want the current page to be the first page + pViewer.config.shiftDoublePage = ( + binding.pageSeekbar.progress + + (currentChapter?.pages?.take(binding.pageSeekbar.progress)?.count { it.fullPage || it.isolatedPage } ?: 0) + ) % 2 != 0 + } + presenter.viewerChaptersRelay.value?.let { + pViewer.setChaptersDoubleShift(it) + } + } + + private fun setDoublePageMode(viewer: PagerViewer) { + val currentOrientation = resources.configuration.orientation + viewer.config.doublePages = currentOrientation == Configuration.ORIENTATION_LANDSCAPE + } + + private fun shiftDoublePages() { + (viewer as? PagerViewer)?.config?.let { config -> + config.shiftDoublePage = !config.shiftDoublePage + presenter.viewerChaptersRelay.value?.let { + (viewer as? PagerViewer)?.updateShifting() + (viewer as? PagerViewer)?.setChaptersDoubleShift(it) + invalidateOptionsMenu() + } } } // EXH <-- @@ -905,6 +993,13 @@ class ReaderActivity : BaseRxActivity() binding.viewerContainer.addView(newViewer.getView()) // SY --> + if (newViewer is PagerViewer) { + if (preferences.pageLayout().get() == PagerConfig.PageLayout.AUTOMATIC) { + setDoublePageMode(newViewer) + } + lastShiftDoubleState?.let { newViewer.config.shiftDoublePage = it } + } + val defaultReaderType = manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.get(manga.source)?.name)) if (preferences.useAutoWebtoon().get() && manga.readingModeType == ReadingModeType.DEFAULT.flagValue && defaultReaderType != null && defaultReaderType == ReadingModeType.WEBTOON.prefValue) { readingModeToast?.cancel() @@ -980,6 +1075,25 @@ class ReaderActivity : BaseRxActivity() */ fun setChapters(viewerChapters: ViewerChapters) { binding.pleaseWait.isVisible = false + // SY --> + if (indexChapterToShift != null && indexPageToShift != null) { + viewerChapters.currChapter.pages?.find { it.index == indexPageToShift && it.chapter.chapter.id == indexChapterToShift }?.let { + (viewer as? PagerViewer)?.updateShifting(it) + } + indexChapterToShift = null + indexPageToShift = null + } else if (lastShiftDoubleState != null) { + val currentChapter = viewerChapters.currChapter + (viewer as? PagerViewer)?.config?.shiftDoublePage = ( + currentChapter.requestedPage + + ( + currentChapter.pages?.take(currentChapter.requestedPage) + ?.count { it.fullPage || it.isolatedPage } ?: 0 + ) + ) % 2 != 0 + } + // SY <-- + viewer?.setChapters(viewerChapters) binding.toolbar.subtitle = viewerChapters.currChapter.chapter.name @@ -1045,25 +1159,31 @@ class ReaderActivity : BaseRxActivity() * bottom menu and delegates the change to the presenter. */ @SuppressLint("SetTextI18n") - fun onPageSelected(page: ReaderPage) { + fun onPageSelected(page: ReaderPage, hasExtraPage: Boolean = false) { val newChapter = presenter.onPageSelected(page) val pages = page.chapter.pages ?: return + val currentPage = if (hasExtraPage) { + if (resources.isLTR) "${page.number}-${page.number + 1}" else "${page.number + 1}-${page.number}" + } else { + "${page.number}" + } + // Set bottom page number - binding.pageNumber.text = "${page.number}/${pages.size}" + binding.pageNumber.text = "$currentPage/${pages.size}" // binding.pageText.text = "${page.number}/${pages.size}" // Set seekbar page number if (viewer !is R2LPagerViewer) { - binding.leftPageText.text = "${page.number}" + binding.leftPageText.text = currentPage binding.rightPageText.text = "${pages.size}" } else { - binding.rightPageText.text = "${page.number}" + binding.rightPageText.text = currentPage binding.leftPageText.text = "${pages.size}" } // SY --> - binding.abovePageText.text = "${page.number}" + binding.abovePageText.text = currentPage binding.belowPageText.text = "${pages.size}" // SY <-- @@ -1080,11 +1200,11 @@ class ReaderActivity : BaseRxActivity() * Called from the viewer whenever a [page] is long clicked. A bottom sheet with a list of * actions to perform is shown. */ - fun onPageLongTap(page: ReaderPage) { + fun onPageLongTap(page: ReaderPage, extraPage: ReaderPage? = null) { // EXH --> try { // EXH <-- - ReaderPageSheet(this, page).show() + ReaderPageSheet(this, page, extraPage).show() // EXH --> } catch (e: WindowManager.BadTokenException) { xLogE("Caught and ignoring reader page sheet launch exception!", e) @@ -1256,6 +1376,27 @@ class ReaderActivity : BaseRxActivity() preferences.grayscale().asFlow() .onEach { setGrayscale(it) } .launchIn(lifecycleScope) + + preferences.pageLayout().asFlow() + .drop(1) + .onEach { updateBottomButtons() } + .launchIn(lifecycleScope) + + preferences.dualPageSplitPaged().asFlow() + .drop(1) + .onEach { + if (viewer !is PagerViewer) return@onEach + updateBottomButtons() + reloadChapters( + !it && when (preferences.pageLayout().get()) { + PagerConfig.PageLayout.DOUBLE_PAGES -> true + PagerConfig.PageLayout.AUTOMATIC -> resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + else -> false + }, + true + ) + } + .launchIn(lifecycleScope) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt index 21ce88725..2aa27701d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt @@ -14,7 +14,8 @@ import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog */ class ReaderPageSheet( private val activity: ReaderActivity, - private val page: ReaderPage + private val page: ReaderPage, + private val extraPage: ReaderPage? = null ) : BaseBottomSheetDialog(activity) { private lateinit var binding: ReaderPageSheetBinding diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt index 6d09a9e37..fdd02e804 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt @@ -11,10 +11,21 @@ open class ReaderPage( // SY --> var bg: Drawable? = null, var bgType: Int? = null, + /** Value to check if this page is used to as if it was too wide */ + var shiftedPage: Boolean = false, + /** Value to check if a page is can be doubled up, but can't because the next page is too wide */ + var isolatedPage: Boolean = false, // SY <-- var stream: (() -> InputStream)? = null ) : Page(index, url, imageUrl, null) { open lateinit var chapter: ReaderChapter + + /** Value to check if a page is too wide to be doubled up */ + var fullPage: Boolean = false + set(value) { + field = value + if (value) shiftedPage = false + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderBottomButton.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderBottomButton.kt index e1c881520..196e399d0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderBottomButton.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderBottomButton.kt @@ -11,8 +11,7 @@ enum class ReaderBottomButton(val value: String, @StringRes val stringRes: Int) CropBordersPager("cbp", R.string.pref_crop_borders_pager), CropBordersContinuesVertical("cbc", R.string.pref_crop_borders_continuous_vertical), CropBordersWebtoon("cbw", R.string.pref_crop_borders_webtoon), - // PageLayout("pl", R.string.page_layout), - // ShiftDoublePage("sdp", R.string.shift_double_pages) + PageLayout("pl", R.string.page_layout), ; fun isIn(buttons: Collection) = value in buttons @@ -22,7 +21,8 @@ enum class ReaderBottomButton(val value: String, @StringRes val stringRes: Int) ViewChapters, WebView, CropBordersPager, - CropBordersContinuesVertical + CropBordersContinuesVertical, + PageLayout ).map { it.value }.toSet() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt index 32db7ad6c..238306686 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt @@ -87,6 +87,7 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr // SY --> binding.pagerPrefsGroup.pageTransitionsPager.bindToPreference(preferences.pageTransitionsPager()) + binding.pagerPrefsGroup.pageLayout.bindToPreference(preferences.pageLayout()) // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt index 0baad5d54..3ad4a6b76 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager +import android.graphics.Color +import androidx.annotation.ColorInt import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.reader.viewer.ViewerConfig import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation @@ -28,6 +30,8 @@ class PagerConfig( var dualPageSplitChangedListener: ((Boolean) -> Unit)? = null + var reloadChapterListener: ((Boolean) -> Unit)? = null + var imageScaleType = 1 private set @@ -39,6 +43,23 @@ class PagerConfig( // SY --> var usePageTransitions = false + + var shiftDoublePage = false + + var doublePages = preferences.pageLayout().get() == PageLayout.DOUBLE_PAGES && !preferences.dualPageSplitPaged().get() + set(value) { + field = value + if (!value) { + shiftDoublePage = false + } + } + + var invertDoublePages = false + + var autoDoublePages = preferences.pageLayout().get() == PageLayout.AUTOMATIC + + @ColorInt + var pageCanvasColor = Color.WHITE // SY <-- init { @@ -79,6 +100,33 @@ class PagerConfig( // SY --> preferences.pageTransitionsPager() .register({ usePageTransitions = it }, { imagePropertyChangedListener?.invoke() }) + preferences.readerTheme() + .register( + { + themeToColor(it) + }, + { + themeToColor(it) + reloadChapterListener?.invoke(doublePages) + } + ) + preferences.pageLayout() + .register( + { + autoDoublePages = it == PageLayout.AUTOMATIC + if (!autoDoublePages) { + doublePages = it == PageLayout.DOUBLE_PAGES && dualPageSplit == false + } + }, + { + autoDoublePages = it == PageLayout.AUTOMATIC + if (!autoDoublePages) { + doublePages = it == PageLayout.DOUBLE_PAGES && dualPageSplit == false + } + reloadChapterListener?.invoke(doublePages) + } + ) + // SY <-- } @@ -126,4 +174,18 @@ class PagerConfig( enum class ZoomType { Left, Center, Right } + + object PageLayout { + const val SINGLE_PAGE = 0 + const val DOUBLE_PAGES = 1 + const val AUTOMATIC = 2 + } + + fun themeToColor(theme: Int) { + pageCanvasColor = when (theme) { + 1 -> Color.BLACK + 2 -> 0x202125 + else -> Color.WHITE + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index 7b78bafaf..3c9245bf5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager import android.annotation.SuppressLint +import android.graphics.BitmapFactory import android.graphics.PointF import android.graphics.drawable.Animatable import android.view.GestureDetector @@ -27,16 +28,22 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.widget.ViewPagerAdapter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers +import timber.log.Timber import java.io.InputStream import java.nio.ByteBuffer import java.util.concurrent.TimeUnit +import kotlin.math.roundToInt /** * View of the ViewPager that contains a page of a chapter. @@ -44,14 +51,15 @@ import java.util.concurrent.TimeUnit @SuppressLint("ViewConstructor") class PagerPageHolder( val viewer: PagerViewer, - val page: ReaderPage + val page: ReaderPage, + private var extraPage: ReaderPage? = null ) : FrameLayout(viewer.activity), ViewPagerAdapter.PositionableView { /** * Item that identifies this view. Needed by the adapter to not recreate views. */ override val item - get() = page + get() = page to extraPage /** * Loading progress bar to indicate the current progress. @@ -88,14 +96,34 @@ class PagerPageHolder( */ private var progressSubscription: Subscription? = null + /** + * Subscription for status changes of the page. + */ + private var extraStatusSubscription: Subscription? = null + + /** + * Subscription for progress changes of the page. + */ + private var extraProgressSubscription: Subscription? = null + /** * Subscription used to read the header of the image. This is needed in order to instantiate * the appropiate image view depending if the image is animated (GIF). */ private var readImageHeaderSubscription: Subscription? = null + // SY --> + var status: Int = 0 + var extraStatus: Int = 0 + var progress: Int = 0 + var extraProgress: Int = 0 + private var skipExtra = false + var scope: CoroutineScope? = null + // SY <-- + init { addView(progressBar) + scope = CoroutineScope(Job() + Dispatchers.Default) observeStatus() } @@ -105,8 +133,10 @@ class PagerPageHolder( @SuppressLint("ClickableViewAccessibility") override fun onDetachedFromWindow() { super.onDetachedFromWindow() - unsubscribeProgress() - unsubscribeStatus() + unsubscribeProgress(1) + unsubscribeProgress(2) + unsubscribeStatus(1) + unsubscribeStatus(2) unsubscribeReadImageHeader() subsamplingImageView?.setOnImageEventListener(null) } @@ -122,7 +152,19 @@ class PagerPageHolder( val loader = page.chapter.pageLoader ?: return statusSubscription = loader.getPage(page) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { processStatus(it) } + .subscribe { + status = it + processStatus(it) + } + + val extraPage = extraPage ?: return + val loader2 = extraPage.chapter.pageLoader ?: return + extraStatusSubscription = loader2.getPage(extraPage) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + extraStatus = it + processStatus2(it) + } } /** @@ -139,6 +181,20 @@ class PagerPageHolder( .subscribe { value -> progressBar.setProgress(value) } } + private fun observeProgress2() { + extraProgressSubscription?.unsubscribe() + val extraPage = extraPage ?: return + extraProgressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS) + .map { extraPage.progress } + .distinctUntilChanged() + .onBackpressureLatest() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { value -> + extraProgress = value + progressBar.setProgress(((progress + extraProgress) / 2 * 0.95f).roundToInt()) + } + } + /** * Called when the status of the page changes. * @@ -153,12 +209,40 @@ class PagerPageHolder( setDownloading() } Page.READY -> { - setImage() - unsubscribeProgress() + if (extraStatus == Page.READY || extraPage == null) { + setImage() + } + unsubscribeProgress(1) } Page.ERROR -> { setError() - unsubscribeProgress() + unsubscribeProgress(1) + } + } + } + + /** + * Called when the status of the page changes. + * + * @param status the new status of the page. + */ + private fun processStatus2(status: Int) { + when (status) { + Page.QUEUE -> setQueued() + Page.LOAD_PAGE -> setLoading() + Page.DOWNLOAD_IMAGE -> { + observeProgress2() + setDownloading() + } + Page.READY -> { + if (this.status == Page.READY) { + setImage() + } + unsubscribeProgress(2) + } + Page.ERROR -> { + setError() + unsubscribeProgress(2) } } } @@ -166,17 +250,19 @@ class PagerPageHolder( /** * Unsubscribes from the status subscription. */ - private fun unsubscribeStatus() { - statusSubscription?.unsubscribe() - statusSubscription = null + private fun unsubscribeStatus(page: Int) { + val subscription = if (page == 1) statusSubscription else extraStatusSubscription + subscription?.unsubscribe() + if (page == 1) statusSubscription = null else extraStatusSubscription = null } /** * Unsubscribes from the progress subscription. */ - private fun unsubscribeProgress() { - progressSubscription?.unsubscribe() - progressSubscription = null + private fun unsubscribeProgress(page: Int) { + val subscription = if (page == 1) progressSubscription else extraProgressSubscription + subscription?.unsubscribe() + if (page == 1) progressSubscription = null else extraProgressSubscription = null } /** @@ -219,18 +305,31 @@ class PagerPageHolder( */ private fun setImage() { progressBar.isVisible = true - progressBar.completeAndFadeOut() + progressBar.isVisible = true + if (extraPage == null) { + progressBar.completeAndFadeOut() + } else { + progressBar.setProgress(95) + } retryButton?.isVisible = false decodeErrorLayout?.isVisible = false unsubscribeReadImageHeader() val streamFn = page.stream ?: return + val streamFn2 = extraPage?.stream var openStream: InputStream? = null readImageHeaderSubscription = Observable .fromCallable { val stream = streamFn().buffered(16) - openStream = process(item, stream) + // SY --> + val stream2 = if (extraPage != null) streamFn2?.invoke()?.buffered(16) else null + openStream = if (viewer.config.dualPageSplit) { + process(item.first, stream) + } else { + mergePages(stream, stream2) + } + // SY <-- ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF } @@ -273,6 +372,81 @@ class PagerPageHolder( return splitInHalf(imageStream) } + private fun mergePages(imageStream: InputStream, imageStream2: InputStream?): InputStream { + imageStream2 ?: return imageStream + if (page.fullPage) return imageStream + if (ImageUtil.findImageType(imageStream) == ImageUtil.ImageType.GIF) { + page.fullPage = true + skipExtra = true + return imageStream + } else if (ImageUtil.findImageType(imageStream2) == ImageUtil.ImageType.GIF) { + page.isolatedPage = true + extraPage?.fullPage = true + skipExtra = true + return imageStream + } + val imageBytes = imageStream.readBytes() + val imageBitmap = try { + BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + } catch (e: Exception) { + imageStream2.close() + imageStream.close() + page.fullPage = true + skipExtra = true + Timber.e("Cannot combine pages ${e.message}") + return imageBytes.inputStream() + } + scope?.launchUI { progressBar.setProgress(96) } + val height = imageBitmap.height + val width = imageBitmap.width + + if (height < width) { + imageStream2.close() + imageStream.close() + page.fullPage = true + skipExtra = true + return imageBytes.inputStream() + } + + val imageBytes2 = imageStream2.readBytes() + val imageBitmap2 = try { + BitmapFactory.decodeByteArray(imageBytes2, 0, imageBytes2.size) + } catch (e: Exception) { + imageStream2.close() + imageStream.close() + extraPage?.fullPage = true + skipExtra = true + page.isolatedPage = true + Timber.e("Cannot combine pages ${e.message}") + return imageBytes.inputStream() + } + scope?.launchUI { progressBar.setProgress(97) } + val height2 = imageBitmap2.height + val width2 = imageBitmap2.width + + if (height2 < width2) { + imageStream2.close() + imageStream.close() + extraPage?.fullPage = true + page.isolatedPage = true + skipExtra = true + return imageBytes.inputStream() + } + val isLTR = (viewer !is R2LPagerViewer).xor(viewer.config.invertDoublePages) + + imageStream.close() + imageStream2.close() + return ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, viewer.config.pageCanvasColor) { + scope?.launchUI { + if (it == 100) { + progressBar.completeAndFadeOut() + } else { + progressBar.setProgress(it) + } + } + } + } + private fun splitInHalf(imageStream: InputStream): InputStream { var side = when { viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt index 54434f69d..2a244e6d3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt @@ -72,6 +72,16 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { } } + private var pagerListener = object : ViewPager.SimpleOnPageChangeListener() { + override fun onPageSelected(position: Int) { + onPageChange(position) + } + + override fun onPageScrollStateChanged(state: Int) { + isIdle = state == ViewPager.SCROLL_STATE_IDLE + } + } + init { pager.isVisible = false // Don't layout the pager yet pager.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) @@ -79,15 +89,9 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { pager.id = R.id.reader_pager pager.adapter = adapter pager.addOnPageChangeListener( - object : ViewPager.SimpleOnPageChangeListener() { - override fun onPageSelected(position: Int) { - onPageChange(position) - } - - override fun onPageScrollStateChanged(state: Int) { - isIdle = state == ViewPager.SCROLL_STATE_IDLE - } - } + // SY --> + pagerListener + // SY <-- ) pager.tapListener = f@{ event -> if (!config.tappingEnabled) { @@ -107,9 +111,11 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { } pager.longTapListener = f@{ if (activity.menuVisible || config.longTapEnabled) { - val item = adapter.items.getOrNull(pager.currentItem) - if (item is ReaderPage) { - activity.onPageLongTap(item) + val item = adapter.joinedItems.getOrNull(pager.currentItem) + val firstPage = item?.first as? ReaderPage + val secondPage = item?.second as? ReaderPage + if (firstPage is ReaderPage) { + activity.onPageLongTap(firstPage, secondPage) return@f true } } @@ -122,6 +128,10 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { } } + config.reloadChapterListener = { + activity.reloadChapters(it) + } + config.imagePropertyChangedListener = { refreshAdapter() } @@ -153,13 +163,13 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { * Called when a new page (either a [ReaderPage] or [ChapterTransition]) is marked as active */ private fun onPageChange(position: Int) { - val page = adapter.items.getOrNull(position) + val page = adapter.joinedItems.getOrNull(position) if (page != null && currentPage != page) { - val allowPreload = checkAllowPreload(page as? ReaderPage) - currentPage = page - when (page) { - is ReaderPage -> onReaderPageSelected(page, allowPreload) - is ChapterTransition -> onTransitionSelected(page) + val allowPreload = checkAllowPreload(page.first as? ReaderPage) + currentPage = page.first + when (val aPage = page.first) { + is ReaderPage -> onReaderPageSelected(aPage, allowPreload, page.second != null) + is ChapterTransition -> onTransitionSelected(aPage) } } } @@ -187,10 +197,10 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { * Called when a [ReaderPage] is marked as active. It notifies the * activity of the change and requests the preload of the next chapter if this is the last page. */ - private fun onReaderPageSelected(page: ReaderPage, allowPreload: Boolean) { + private fun onReaderPageSelected(page: ReaderPage, allowPreload: Boolean, hasExtraPage: Boolean) { val pages = page.chapter.pages ?: return Timber.d("onReaderPageSelected: ${page.number}/${pages.size}") - activity.onPageSelected(page) + activity.onPageSelected(page, hasExtraPage) // Skip preload on inserts it causes unwanted page jumping if (page is InsertPage) { @@ -240,7 +250,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { */ private fun setChaptersInternal(chapters: ViewerChapters) { Timber.d("setChaptersInternal") - val forceTransition = config.alwaysShowChapterTransition || adapter.items.getOrNull(pager.currentItem) is ChapterTransition + val forceTransition = config.alwaysShowChapterTransition || adapter.joinedItems.getOrNull(pager.currentItem)?.first is ChapterTransition adapter.setChapters(chapters, forceTransition) // Layout the pager once a chapter is being set @@ -257,13 +267,21 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { */ override fun moveToPage(page: ReaderPage) { Timber.d("moveToPage ${page.number}") - val position = adapter.items.indexOf(page) + val position = adapter.joinedItems.indexOfFirst { it.first == page || it.second == page } if (position != -1) { val currentPosition = pager.currentItem pager.setCurrentItem(position, true) // manually call onPageChange since ViewPager listener is not triggered in this case if (currentPosition == position) { onPageChange(position) + } else { + // Call this since with double shift onPageChange wont get called (it shouldn't) + // Instead just update the page count in ui + val joinedItem = adapter.joinedItems.firstOrNull { it.first == page || it.second == page } + activity.onPageSelected( + joinedItem?.first as? ReaderPage ?: page, + joinedItem?.second != null + ) } } else { Timber.d("Page $page not found in adapter") @@ -399,4 +417,22 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { private fun cleanupPageSplit() { adapter.cleanupPageSplit() } + + // SY --> + fun setChaptersDoubleShift(chapters: ViewerChapters) { + // Remove Listener since we're about to change the size of the items + // If we don't the size change could put us on a new chapter + pager.removeOnPageChangeListener(pagerListener) + setChaptersInternal(chapters) + pager.addOnPageChangeListener(pagerListener) + // Since we removed the listener while shifting, call page change to update the ui + onPageChange(pager.currentItem) + } + + fun updateShifting(page: ReaderPage? = null) { + adapter.pageToShift = page ?: adapter.joinedItems.getOrNull(pager.currentItem)?.first as? ReaderPage + } + + fun getShiftedPage(): ReaderPage? = adapter.pageToShift + // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt index 888d83648..68f0508f1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters import eu.kanade.tachiyomi.widget.ViewPagerAdapter import timber.log.Timber +import kotlin.math.max /** * Pager adapter used by this [viewer] to where [ViewerChapters] updates are posted. @@ -17,11 +18,16 @@ import timber.log.Timber class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { /** - * List of currently set items. + * Paired list of currently set items. */ - var items: MutableList = mutableListOf() + var joinedItems: MutableList> = mutableListOf() private set + /** + * Single list of items + */ + private var subItems: MutableList = mutableListOf() + /** * Holds preprocessed items so they don't get removed when changing chapter */ @@ -32,6 +38,15 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { var currentChapter: ReaderChapter? = null + // SY --> + /** Page used to start the shifted pages */ + var pageToShift: ReaderPage? = null + + /** Varibles used to check if config of the pages have changed */ + private var shifted = viewer.config.shiftDoublePage + private var doubledUp = viewer.config.doublePages + // SY <-- + /** * Updates this adapter with the given [chapters]. It handles setting a few pages of the * next/previous chapter to allow seamless transitions and inverting the pages if the viewer @@ -102,15 +117,20 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { } // Resets double-page splits, else insert pages get misplaced - items.filterIsInstance().also { items.removeAll(it) } - - if (viewer is R2LPagerViewer) { - newItems.reverse() - } + subItems.filterIsInstance().also { subItems.removeAll(it) } preprocessed = mutableMapOf() - items = newItems - notifyDataSetChanged() + subItems = newItems.toMutableList() + + var useSecondPage = false + if (shifted != viewer.config.shiftDoublePage || (doubledUp != viewer.config.doublePages && doubledUp)) { + if (shifted && (doubledUp == viewer.config.doublePages)) { + useSecondPage = true + } + shifted = viewer.config.shiftDoublePage + } + doubledUp = viewer.config.doublePages + setJoinedItems(useSecondPage) // Will skip insert page otherwise if (insertPageLastPage != null) { @@ -122,15 +142,17 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { * Returns the amount of items of the adapter. */ override fun getCount(): Int { - return items.size + return joinedItems.size } /** * Creates a new view for the item at the given [position]. */ override fun createView(container: ViewGroup, position: Int): View { - return when (val item = items[position]) { - is ReaderPage -> PagerPageHolder(viewer, item) + val item = joinedItems[position].first + val item2 = joinedItems[position].second + return when (item) { + is ReaderPage -> PagerPageHolder(viewer, item, item2 as? ReaderPage) is ChapterTransition -> PagerTransitionHolder(viewer, item) else -> throw NotImplementedError("Holder for ${item.javaClass} not implemented") } @@ -141,7 +163,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { */ override fun getItemPosition(view: Any): Int { if (view is PositionableView) { - val position = items.indexOf(view.item) + val position = joinedItems.indexOf(view.item) if (position != -1) { return position } else { @@ -154,7 +176,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { fun onPageSplit(currentPage: Any?, newPage: InsertPage) { if (currentPage !is ReaderPage) return - val currentIndex = items.indexOf(currentPage) + val currentIndex = joinedItems.indexOfFirst { it.first == currentPage } // Put aside preprocessed pages for next chapter so they don't get removed when changing chapter if (currentPage.chapter.chapter.id != currentChapter?.chapter?.id) { @@ -169,23 +191,157 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { } // It will enter a endless cycle of insert pages - if (viewer is R2LPagerViewer && placeAtIndex - 1 >= 0 && items[placeAtIndex - 1] is InsertPage) { + if (viewer is R2LPagerViewer && placeAtIndex - 1 >= 0 && joinedItems[placeAtIndex - 1].first is InsertPage) { return } // Same here it will enter a endless cycle of insert pages - if (items[placeAtIndex] is InsertPage) { + if (joinedItems[placeAtIndex].first is InsertPage) { return } - items.add(placeAtIndex, newPage) + joinedItems.add(placeAtIndex, newPage to null) notifyDataSetChanged() } fun cleanupPageSplit() { - val insertPages = items.filterIsInstance(InsertPage::class.java) - items.removeAll(insertPages) + val insertPages = joinedItems.filter { it.first is InsertPage } + joinedItems.removeAll(insertPages) notifyDataSetChanged() } + + // SY --> + + private fun setJoinedItems(useSecondPage: Boolean = false) { + val oldCurrent = joinedItems.getOrNull(viewer.pager.currentItem) + if (!viewer.config.doublePages) { + // If not in double mode, set up items like before + subItems.forEach { + (it as? ReaderPage)?.shiftedPage = false + } + this.joinedItems = subItems.map { Pair(it, null) }.toMutableList() + if (viewer is R2LPagerViewer) { + joinedItems.reverse() + } + } else { + val pagedItems = mutableListOf>() + val otherItems = mutableListOf() + pagedItems.add(mutableListOf()) + // Step 1: segment the pages and transition pages + subItems.forEach { + if (it is ReaderPage) { + pagedItems.last().add(it) + } else { + otherItems.add(it) + pagedItems.add(mutableListOf()) + } + } + var pagedIndex = 0 + val subJoinedItems = mutableListOf>() + // Step 2: run through each set of pages + pagedItems.forEach { items -> + + items.forEach { + it?.shiftedPage = false + } + // Step 3: If pages have been shifted, + if (viewer.config.shiftDoublePage) { + run loop@{ + var index = items.indexOf(pageToShift) + if (pageToShift?.fullPage == true) { + index = max(0, index - 1) + } + // Go from the current page and work your way back to the first page, + // or the first page that's a full page. + // This is done in case user tries to shift a page after a full page + val fullPageBeforeIndex = max( + 0, + ( + if (index > -1) ( + items.take(index).indexOfLast { it?.fullPage == true } + ) else -1 + ) + ) + // Add a shifted page to the first place there isnt a full page + (fullPageBeforeIndex until items.size).forEach { + if (items[it]?.fullPage == false) { + items[it]?.shiftedPage = true + return@loop + } + } + } + } + + // Step 4: Add blanks for chunking + var itemIndex = 0 + while (itemIndex < items.size) { + items[itemIndex]?.isolatedPage = false + if (items[itemIndex]?.fullPage == true || items[itemIndex]?.shiftedPage == true) { + // Add a 'blank' page after each full page. It will be used when chunked to solo a page + items.add(itemIndex + 1, null) + if (items[itemIndex]?.fullPage == true && itemIndex > 0 && + items[itemIndex - 1] != null && (itemIndex - 1) % 2 == 0 + ) { + // If a page is a full page, check if the previous page needs to be isolated + // we should check if it's an even or odd page, since even pages need shifting + // For example if Page 1 is full, Page 0 needs to be isolated + // No need to take account shifted pages, because null additions should + // always have an odd index in the list + items[itemIndex - 1]?.isolatedPage = true + items.add(itemIndex, null) + itemIndex++ + } + itemIndex++ + } + itemIndex++ + } + + // Step 5: chunk em + if (items.isNotEmpty()) { + subJoinedItems.addAll( + items.chunked(2).map { Pair(it.first()!!, it.getOrNull(1)) } + ) + } + otherItems.getOrNull(pagedIndex)?.let { + subJoinedItems.add(Pair(it, null)) + pagedIndex++ + } + } + if (viewer is R2LPagerViewer) { + subJoinedItems.reverse() + } + + this.joinedItems = subJoinedItems + } + notifyDataSetChanged() + + // Step 6: Move back to our previous page or transition page + // The listener is likely off around now, but either way when shifting or doubling, + // we need to set the page back correctly + // We will however shift to the first page of the new chapter if the last page we were are + // on is not in the new chapter that has loaded + val newPage = + when { + (oldCurrent?.first as? ReaderPage)?.chapter != currentChapter && + (oldCurrent?.first as? ChapterTransition)?.from != currentChapter -> subItems.find { (it as? ReaderPage)?.chapter == currentChapter } + useSecondPage -> (oldCurrent?.second ?: oldCurrent?.first) + else -> oldCurrent?.first ?: return + } + var index = joinedItems.indexOfFirst { it.first == newPage || it.second == newPage } + if (newPage is ChapterTransition && index == -1) { + val newerPage = if (newPage is ChapterTransition.Next) { + joinedItems.filter { + (it.first as? ReaderPage)?.chapter == newPage.to + }.minByOrNull { (it.first as? ReaderPage)?.index ?: Int.MAX_VALUE }?.first + } else { + joinedItems.filter { + (it.first as? ReaderPage)?.chapter == newPage.to + }.maxByOrNull { (it.first as? ReaderPage)?.index ?: Int.MIN_VALUE }?.first + } + index = joinedItems.indexOfFirst { it.first == newerPage || it.second == newerPage } + } + viewer.pager.setCurrentItem(index, false) + } + // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt index 3202914d1..d4c3579c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt @@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig import eu.kanade.tachiyomi.util.preference.defaultValue import eu.kanade.tachiyomi.util.preference.entriesRes import eu.kanade.tachiyomi.util.preference.intListPreference @@ -515,6 +516,24 @@ class SettingsReaderController : SettingsController() { ReaderBottomButtonsDialog().showDialog(router) } } + intListPreference { + key = Keys.pageLayout + titleRes = R.string.page_layout + summaryRes = R.string.automatic_can_still_switch + entriesRes = arrayOf( + R.string.single_page, + R.string.double_pages, + R.string.automatic_orientation + ) + entryValues = arrayOf("0", "1", "2") + defaultValue = 2 + } + switchPreference { + key = Keys.invertDoublePages + titleRes = R.string.invert_double_pages + defaultValue = false + preferences.pageLayout().asImmediateFlow { isVisible = it != PagerConfig.PageLayout.SINGLE_PAGE } + } } // EXH <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt index 6a178a090..3b53eaf8e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt @@ -10,6 +10,7 @@ import android.graphics.Rect import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable +import androidx.annotation.ColorInt import androidx.core.graphics.alpha import androidx.core.graphics.blue import androidx.core.graphics.createBitmap @@ -20,6 +21,7 @@ import java.io.ByteArrayOutputStream import java.io.InputStream import java.net.URLConnection import kotlin.math.abs +import kotlin.math.max object ImageUtil { @@ -381,4 +383,45 @@ object ImageUtil { private fun Int.isWhite(): Boolean = red + blue + green > 740 + + fun mergeBitmaps( + imageBitmap: Bitmap, + imageBitmap2: Bitmap, + isLTR: Boolean, + @ColorInt background: Int = Color.WHITE, + progressCallback: ((Int) -> Unit)? = null + ): ByteArrayInputStream { + val height = imageBitmap.height + val width = imageBitmap.width + val height2 = imageBitmap2.height + val width2 = imageBitmap2.width + val maxHeight = max(height, height2) + val result = Bitmap.createBitmap(width + width2, max(height, height2), Bitmap.Config.ARGB_8888) + val canvas = Canvas(result) + canvas.drawColor(background) + val upperPart = Rect( + if (isLTR) 0 else width2, + (maxHeight - imageBitmap.height) / 2, + (if (isLTR) 0 else width2) + imageBitmap.width, + imageBitmap.height + (maxHeight - imageBitmap.height) / 2 + ) + canvas.drawBitmap(imageBitmap, imageBitmap.rect, upperPart, null) + progressCallback?.invoke(98) + val bottomPart = Rect( + if (!isLTR) 0 else width, + (maxHeight - imageBitmap2.height) / 2, + (if (!isLTR) 0 else width) + imageBitmap2.width, + imageBitmap2.height + (maxHeight - imageBitmap2.height) / 2 + ) + canvas.drawBitmap(imageBitmap2, imageBitmap2.rect, bottomPart, null) + progressCallback?.invoke(99) + + val output = ByteArrayOutputStream() + result.compress(Bitmap.CompressFormat.JPEG, 100, output) + progressCallback?.invoke(100) + return ByteArrayInputStream(output.toByteArray()) + } + + private val Bitmap.rect: Rect + get() = Rect(0, 0, width, height) } diff --git a/app/src/main/res/drawable-hdpi/ic_book_open_variant_24dp.xml b/app/src/main/res/drawable-hdpi/ic_book_open_variant_24dp.xml new file mode 100644 index 000000000..071e043e1 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_book_open_variant_24dp.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_page_next_outline_24dp.xml b/app/src/main/res/drawable-hdpi/ic_page_next_outline_24dp.xml new file mode 100644 index 000000000..930be5ded --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_page_next_outline_24dp.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/reader_activity.xml b/app/src/main/res/layout/reader_activity.xml index 63cca554b..b1ae5d8c0 100755 --- a/app/src/main/res/layout/reader_activity.xml +++ b/app/src/main/res/layout/reader_activity.xml @@ -440,13 +440,41 @@ android:background="@drawable/ripple_regular" android:contentDescription="@string/pref_rotation_type" android:padding="@dimen/screen_edge_margin" - app:layout_constraintEnd_toStartOf="@id/action_settings" + app:layout_constraintEnd_toStartOf="@id/double_page" app:layout_constraintStart_toEndOf="@+id/action_crop_borders" app:layout_constraintTop_toTopOf="parent" app:layout_constraintHorizontal_chainStyle="spread" app:srcCompat="@drawable/ic_screen_rotation_24dp" app:tint="?attr/colorOnPrimary" /> + + + + + + @string/dropped @string/repeating + + + @string/single_page + @string/double_pages + @string/automatic_orientation + \ No newline at end of file diff --git a/app/src/main/res/values/strings_sy.xml b/app/src/main/res/values/strings_sy.xml index a96cce69f..605d9db3b 100644 --- a/app/src/main/res/values/strings_sy.xml +++ b/app/src/main/res/values/strings_sy.xml @@ -279,6 +279,15 @@ Reading webtoon style + + Page layout + Shift one page over + Double pages + Single page + Automatic (based on orientation) + While using automatic page layout, you can still switch between layouts while reading without overriding this setting + Invert double pages + See Recommendations