Add "Rotate wide pages to fit" setting for paged reader
Originally authored in #7983 Co-authored-by: timothyng-164 <timothyng-164@users.noreply.github.com>
This commit is contained in:
parent
f94d902bb6
commit
953720472f
9 changed files with 113 additions and 3 deletions
|
@ -150,10 +150,12 @@ object SettingsReaderScreen : SearchableSettings {
|
||||||
val navModePref = readerPreferences.navigationModePager()
|
val navModePref = readerPreferences.navigationModePager()
|
||||||
val imageScaleTypePref = readerPreferences.imageScaleType()
|
val imageScaleTypePref = readerPreferences.imageScaleType()
|
||||||
val dualPageSplitPref = readerPreferences.dualPageSplitPaged()
|
val dualPageSplitPref = readerPreferences.dualPageSplitPaged()
|
||||||
|
val rotateToFitPref = readerPreferences.dualPageRotateToFit()
|
||||||
|
|
||||||
val navMode by navModePref.collectAsState()
|
val navMode by navModePref.collectAsState()
|
||||||
val imageScaleType by imageScaleTypePref.collectAsState()
|
val imageScaleType by imageScaleTypePref.collectAsState()
|
||||||
val dualPageSplit by dualPageSplitPref.collectAsState()
|
val dualPageSplit by dualPageSplitPref.collectAsState()
|
||||||
|
val rotateToFit by rotateToFitPref.collectAsState()
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(R.string.pager_viewer),
|
title = stringResource(R.string.pager_viewer),
|
||||||
|
@ -216,6 +218,10 @@ object SettingsReaderScreen : SearchableSettings {
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = dualPageSplitPref,
|
pref = dualPageSplitPref,
|
||||||
title = stringResource(R.string.pref_dual_page_split),
|
title = stringResource(R.string.pref_dual_page_split),
|
||||||
|
onValueChanged = {
|
||||||
|
rotateToFitPref.set(false)
|
||||||
|
true
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.dualPageInvertPaged(),
|
pref = readerPreferences.dualPageInvertPaged(),
|
||||||
|
@ -223,6 +229,19 @@ object SettingsReaderScreen : SearchableSettings {
|
||||||
subtitle = stringResource(R.string.pref_dual_page_invert_summary),
|
subtitle = stringResource(R.string.pref_dual_page_invert_summary),
|
||||||
enabled = dualPageSplit,
|
enabled = dualPageSplit,
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = rotateToFitPref,
|
||||||
|
title = stringResource(R.string.pref_page_rotate),
|
||||||
|
onValueChanged = {
|
||||||
|
dualPageSplitPref.set(false)
|
||||||
|
true
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = readerPreferences.dualPageRotateToFitInvert(),
|
||||||
|
title = stringResource(R.string.pref_page_rotate_invert),
|
||||||
|
enabled = rotateToFit,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,10 @@ class ReaderPreferences(
|
||||||
|
|
||||||
fun dualPageInvertWebtoon() = preferenceStore.getBoolean("pref_dual_page_invert_webtoon", false)
|
fun dualPageInvertWebtoon() = preferenceStore.getBoolean("pref_dual_page_invert_webtoon", false)
|
||||||
|
|
||||||
|
fun dualPageRotateToFit() = preferenceStore.getBoolean("pref_dual_page_rotate", false)
|
||||||
|
|
||||||
|
fun dualPageRotateToFitInvert() = preferenceStore.getBoolean("pref_dual_page_rotate_invert", false)
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Color filter
|
// region Color filter
|
||||||
|
|
|
@ -92,11 +92,26 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
|
||||||
binding.pagerPrefsGroup.cropBorders.bindToPreference(readerPreferences.cropBorders())
|
binding.pagerPrefsGroup.cropBorders.bindToPreference(readerPreferences.cropBorders())
|
||||||
|
|
||||||
binding.pagerPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitPaged())
|
binding.pagerPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitPaged())
|
||||||
// Makes it so that dual page invert gets hidden away when dual page split is turned off
|
|
||||||
readerPreferences.dualPageSplitPaged()
|
readerPreferences.dualPageSplitPaged()
|
||||||
.asHotFlow { binding.pagerPrefsGroup.dualPageInvert.isVisible = it }
|
.asHotFlow {
|
||||||
|
binding.pagerPrefsGroup.dualPageInvert.isVisible = it
|
||||||
|
if (it) {
|
||||||
|
binding.pagerPrefsGroup.dualPageRotateToFit.isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
.launchIn((context as ReaderActivity).lifecycleScope)
|
.launchIn((context as ReaderActivity).lifecycleScope)
|
||||||
binding.pagerPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertPaged())
|
binding.pagerPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertPaged())
|
||||||
|
|
||||||
|
binding.pagerPrefsGroup.dualPageRotateToFit.bindToPreference(readerPreferences.dualPageRotateToFit())
|
||||||
|
readerPreferences.dualPageRotateToFit()
|
||||||
|
.asHotFlow {
|
||||||
|
binding.pagerPrefsGroup.dualPageRotateToFitInvert.isVisible = it
|
||||||
|
if (it) {
|
||||||
|
binding.pagerPrefsGroup.dualPageSplit.isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn((context as ReaderActivity).lifecycleScope)
|
||||||
|
binding.pagerPrefsGroup.dualPageRotateToFitInvert.bindToPreference(readerPreferences.dualPageRotateToFitInvert())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -37,6 +37,12 @@ abstract class ViewerConfig(readerPreferences: ReaderPreferences, private val sc
|
||||||
var dualPageInvert = false
|
var dualPageInvert = false
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
|
var dualPageRotateToFit = false
|
||||||
|
protected set
|
||||||
|
|
||||||
|
var dualPageRotateToFitInvert = false
|
||||||
|
protected set
|
||||||
|
|
||||||
abstract var navigator: ViewerNavigation
|
abstract var navigator: ViewerNavigation
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,18 @@ class PagerConfig(
|
||||||
|
|
||||||
readerPreferences.dualPageInvertPaged()
|
readerPreferences.dualPageInvertPaged()
|
||||||
.register({ dualPageInvert = it }, { imagePropertyChangedListener?.invoke() })
|
.register({ dualPageInvert = it }, { imagePropertyChangedListener?.invoke() })
|
||||||
|
|
||||||
|
readerPreferences.dualPageRotateToFit()
|
||||||
|
.register(
|
||||||
|
{ dualPageRotateToFit = it },
|
||||||
|
{ imagePropertyChangedListener?.invoke() },
|
||||||
|
)
|
||||||
|
|
||||||
|
readerPreferences.dualPageRotateToFitInvert()
|
||||||
|
.register(
|
||||||
|
{ dualPageRotateToFitInvert = it },
|
||||||
|
{ imagePropertyChangedListener?.invoke() },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun zoomTypeFromPreference(value: Int) {
|
private fun zoomTypeFromPreference(value: Int) {
|
||||||
|
|
|
@ -180,6 +180,10 @@ class PagerPageHolder(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream {
|
private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream {
|
||||||
|
if (viewer.config.dualPageRotateToFit) {
|
||||||
|
return rotateDualPage(imageStream)
|
||||||
|
}
|
||||||
|
|
||||||
if (!viewer.config.dualPageSplit) {
|
if (!viewer.config.dualPageSplit) {
|
||||||
return imageStream
|
return imageStream
|
||||||
}
|
}
|
||||||
|
@ -198,6 +202,16 @@ class PagerPageHolder(
|
||||||
return splitInHalf(imageStream)
|
return splitInHalf(imageStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream {
|
||||||
|
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||||
|
return if (isDoublePage) {
|
||||||
|
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
|
||||||
|
ImageUtil.rotateImage(imageStream, rotation)
|
||||||
|
} else {
|
||||||
|
imageStream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun splitInHalf(imageStream: InputStream): InputStream {
|
private fun splitInHalf(imageStream: InputStream): InputStream {
|
||||||
var side = when {
|
var side = when {
|
||||||
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
|
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
|
||||||
|
|
|
@ -91,10 +91,30 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/dual_page_rotate_to_fit"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/pref_page_rotate"
|
||||||
|
android:textColor="?android:attr/textColorSecondary" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/dual_page_rotate_to_fit_invert"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/pref_page_rotate_invert"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Group
|
<androidx.constraintlayout.widget.Group
|
||||||
android:id="@+id/tapping_prefs_group"
|
android:id="@+id/tapping_prefs_group"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:constraint_referenced_ids="pager_nav,tapping_inverted,dual_page_split,dual_page_invert" />
|
app:constraint_referenced_ids="pager_nav,tapping_inverted,dual_page_split,dual_page_invert,dual_page_rotate_to_fit,dual_page_rotate_to_fit_invert" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.BitmapRegionDecoder
|
import android.graphics.BitmapRegionDecoder
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.Matrix
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
@ -151,6 +152,23 @@ object ImageUtil {
|
||||||
return ByteArrayInputStream(output.toByteArray())
|
return ByteArrayInputStream(output.toByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun rotateImage(imageStream: InputStream, degrees: Float): InputStream {
|
||||||
|
val imageBytes = imageStream.readBytes()
|
||||||
|
|
||||||
|
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||||
|
val rotated = rotateBitMap(imageBitmap, degrees)
|
||||||
|
|
||||||
|
val output = ByteArrayOutputStream()
|
||||||
|
rotated.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||||
|
|
||||||
|
return ByteArrayInputStream(output.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rotateBitMap(bitmap: Bitmap, degrees: Float): Bitmap {
|
||||||
|
val matrix = Matrix().apply { postRotate(degrees) }
|
||||||
|
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split the image into left and right parts, then merge them into a new image.
|
* Split the image into left and right parts, then merge them into a new image.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -314,6 +314,8 @@
|
||||||
<string name="pref_dual_page_split">Split wide pages</string>
|
<string name="pref_dual_page_split">Split wide pages</string>
|
||||||
<string name="pref_dual_page_invert">Invert split page placement</string>
|
<string name="pref_dual_page_invert">Invert split page placement</string>
|
||||||
<string name="pref_dual_page_invert_summary">If the placement of the split wide pages don\'t match reading direction</string>
|
<string name="pref_dual_page_invert_summary">If the placement of the split wide pages don\'t match reading direction</string>
|
||||||
|
<string name="pref_page_rotate">Rotate wide pages to fit</string>
|
||||||
|
<string name="pref_page_rotate_invert">Flip orientation of rotated wide pages</string>
|
||||||
<string name="pref_long_strip_split">Split tall images (BETA)</string>
|
<string name="pref_long_strip_split">Split tall images (BETA)</string>
|
||||||
<string name="pref_cutout_short">Show content in cutout area</string>
|
<string name="pref_cutout_short">Show content in cutout area</string>
|
||||||
<string name="pref_page_transitions">Animate page transitions</string>
|
<string name="pref_page_transitions">Animate page transitions</string>
|
||||||
|
|
Reference in a new issue