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 imageScaleTypePref = readerPreferences.imageScaleType()
|
||||
val dualPageSplitPref = readerPreferences.dualPageSplitPaged()
|
||||
val rotateToFitPref = readerPreferences.dualPageRotateToFit()
|
||||
|
||||
val navMode by navModePref.collectAsState()
|
||||
val imageScaleType by imageScaleTypePref.collectAsState()
|
||||
val dualPageSplit by dualPageSplitPref.collectAsState()
|
||||
val rotateToFit by rotateToFitPref.collectAsState()
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(R.string.pager_viewer),
|
||||
|
@ -216,6 +218,10 @@ object SettingsReaderScreen : SearchableSettings {
|
|||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = dualPageSplitPref,
|
||||
title = stringResource(R.string.pref_dual_page_split),
|
||||
onValueChanged = {
|
||||
rotateToFitPref.set(false)
|
||||
true
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPreferences.dualPageInvertPaged(),
|
||||
|
@ -223,6 +229,19 @@ object SettingsReaderScreen : SearchableSettings {
|
|||
subtitle = stringResource(R.string.pref_dual_page_invert_summary),
|
||||
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 dualPageRotateToFit() = preferenceStore.getBoolean("pref_dual_page_rotate", false)
|
||||
|
||||
fun dualPageRotateToFitInvert() = preferenceStore.getBoolean("pref_dual_page_rotate_invert", false)
|
||||
|
||||
// endregion
|
||||
|
||||
// region Color filter
|
||||
|
|
|
@ -92,11 +92,26 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
|
|||
binding.pagerPrefsGroup.cropBorders.bindToPreference(readerPreferences.cropBorders())
|
||||
|
||||
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()
|
||||
.asHotFlow { binding.pagerPrefsGroup.dualPageInvert.isVisible = it }
|
||||
.asHotFlow {
|
||||
binding.pagerPrefsGroup.dualPageInvert.isVisible = it
|
||||
if (it) {
|
||||
binding.pagerPrefsGroup.dualPageRotateToFit.isChecked = false
|
||||
}
|
||||
}
|
||||
.launchIn((context as ReaderActivity).lifecycleScope)
|
||||
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
|
||||
protected set
|
||||
|
||||
var dualPageRotateToFit = false
|
||||
protected set
|
||||
|
||||
var dualPageRotateToFitInvert = false
|
||||
protected set
|
||||
|
||||
abstract var navigator: ViewerNavigation
|
||||
protected set
|
||||
|
||||
|
|
|
@ -94,6 +94,18 @@ class PagerConfig(
|
|||
|
||||
readerPreferences.dualPageInvertPaged()
|
||||
.register({ dualPageInvert = it }, { imagePropertyChangedListener?.invoke() })
|
||||
|
||||
readerPreferences.dualPageRotateToFit()
|
||||
.register(
|
||||
{ dualPageRotateToFit = it },
|
||||
{ imagePropertyChangedListener?.invoke() },
|
||||
)
|
||||
|
||||
readerPreferences.dualPageRotateToFitInvert()
|
||||
.register(
|
||||
{ dualPageRotateToFitInvert = it },
|
||||
{ imagePropertyChangedListener?.invoke() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun zoomTypeFromPreference(value: Int) {
|
||||
|
|
|
@ -180,6 +180,10 @@ class PagerPageHolder(
|
|||
}
|
||||
|
||||
private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream {
|
||||
if (viewer.config.dualPageRotateToFit) {
|
||||
return rotateDualPage(imageStream)
|
||||
}
|
||||
|
||||
if (!viewer.config.dualPageSplit) {
|
||||
return imageStream
|
||||
}
|
||||
|
@ -198,6 +202,16 @@ class PagerPageHolder(
|
|||
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 {
|
||||
var side = when {
|
||||
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
|
||||
|
|
|
@ -91,10 +91,30 @@
|
|||
android:visibility="gone"
|
||||
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
|
||||
android:id="@+id/tapping_prefs_group"
|
||||
android:layout_width="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>
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.graphics.Bitmap
|
|||
import android.graphics.BitmapFactory
|
||||
import android.graphics.BitmapRegionDecoder
|
||||
import android.graphics.Color
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
|
@ -151,6 +152,23 @@ object ImageUtil {
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -314,6 +314,8 @@
|
|||
<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_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_cutout_short">Show content in cutout area</string>
|
||||
<string name="pref_page_transitions">Animate page transitions</string>
|
||||
|
|
Reference in a new issue