Added dual page split setting (#4252)
* Add DualPageSplit option * remove extra line * Split double-page into two pages * Remove !isAnimated check and add (ALPHA) to the label * Fix missing insert pages * Pager cleanup * Add dual split to Webtoon and fix Vertical * Fix L2R/R2L * Add comments and refactor code in ImageUtil * Use a simpler split solution in webtoon mode Co-authored-by: weng <> Co-authored-by: Andreas E <andreas.everos@gmail.com>
This commit is contained in:
parent
aa67229daf
commit
b5017eebbf
@ -23,6 +23,8 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val showPageNumber = "pref_show_page_number_key"
|
const val showPageNumber = "pref_show_page_number_key"
|
||||||
|
|
||||||
|
const val dualPageSplit = "pref_dual_page_split"
|
||||||
|
|
||||||
const val showReadingMode = "pref_show_reading_mode"
|
const val showReadingMode = "pref_show_reading_mode"
|
||||||
|
|
||||||
const val trueColor = "pref_true_color_key"
|
const val trueColor = "pref_true_color_key"
|
||||||
|
@ -89,6 +89,8 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun showPageNumber() = flowPrefs.getBoolean(Keys.showPageNumber, true)
|
fun showPageNumber() = flowPrefs.getBoolean(Keys.showPageNumber, true)
|
||||||
|
|
||||||
|
fun dualPageSplit() = flowPrefs.getBoolean(Keys.dualPageSplit, false)
|
||||||
|
|
||||||
fun showReadingMode() = prefs.getBoolean(Keys.showReadingMode, true)
|
fun showReadingMode() = prefs.getBoolean(Keys.showReadingMode, true)
|
||||||
|
|
||||||
fun trueColor() = flowPrefs.getBoolean(Keys.trueColor, false)
|
fun trueColor() = flowPrefs.getBoolean(Keys.trueColor, false)
|
||||||
|
@ -65,6 +65,7 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : BaseBottomShee
|
|||||||
binding.backgroundColor.bindToIntPreference(preferences.readerTheme(), R.array.reader_themes_values)
|
binding.backgroundColor.bindToIntPreference(preferences.readerTheme(), R.array.reader_themes_values)
|
||||||
binding.showPageNumber.bindToPreference(preferences.showPageNumber())
|
binding.showPageNumber.bindToPreference(preferences.showPageNumber())
|
||||||
binding.fullscreen.bindToPreference(preferences.fullscreen())
|
binding.fullscreen.bindToPreference(preferences.fullscreen())
|
||||||
|
binding.dualPageSplit.bindToPreference(preferences.dualPageSplit())
|
||||||
binding.keepscreen.bindToPreference(preferences.keepScreenOn())
|
binding.keepscreen.bindToPreference(preferences.keepScreenOn())
|
||||||
binding.longTap.bindToPreference(preferences.readWithLongTap())
|
binding.longTap.bindToPreference(preferences.readWithLongTap())
|
||||||
binding.alwaysShowChapterTransition.bindToPreference(preferences.alwaysShowChapterTransition())
|
binding.alwaysShowChapterTransition.bindToPreference(preferences.alwaysShowChapterTransition())
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.model
|
||||||
|
|
||||||
|
class InsertPage(val parent: ReaderPage) : ReaderPage(parent.index, parent.url, parent.imageUrl) {
|
||||||
|
|
||||||
|
override var chapter: ReaderChapter = parent.chapter
|
||||||
|
|
||||||
|
init {
|
||||||
|
stream = parent.stream
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,12 @@ package eu.kanade.tachiyomi.ui.reader.model
|
|||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
class ReaderPage(
|
open class ReaderPage(
|
||||||
index: Int,
|
index: Int,
|
||||||
url: String = "",
|
url: String = "",
|
||||||
imageUrl: String? = null,
|
imageUrl: String? = null,
|
||||||
var stream: (() -> InputStream)? = null
|
var stream: (() -> InputStream)? = null
|
||||||
) : Page(index, url, imageUrl, null) {
|
) : Page(index, url, imageUrl, null) {
|
||||||
|
|
||||||
lateinit var chapter: ReaderChapter
|
open lateinit var chapter: ReaderChapter
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ abstract class ViewerConfig(preferences: PreferencesHelper, private val scope: C
|
|||||||
var volumeKeysInverted = false
|
var volumeKeysInverted = false
|
||||||
var trueColor = false
|
var trueColor = false
|
||||||
var alwaysShowChapterTransition = true
|
var alwaysShowChapterTransition = true
|
||||||
|
var dualPageSplit = false
|
||||||
var navigationMode = 0
|
var navigationMode = 0
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
@ -54,6 +55,9 @@ abstract class ViewerConfig(preferences: PreferencesHelper, private val scope: C
|
|||||||
|
|
||||||
preferences.alwaysShowChapterTransition()
|
preferences.alwaysShowChapterTransition()
|
||||||
.register({ alwaysShowChapterTransition = it })
|
.register({ alwaysShowChapterTransition = it })
|
||||||
|
|
||||||
|
preferences.dualPageSplit()
|
||||||
|
.register({ dualPageSplit = it }, { imagePropertyChangedListener?.invoke() })
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun defaultNavigation(): ViewerNavigation
|
protected abstract fun defaultNavigation(): ViewerNavigation
|
||||||
|
@ -28,6 +28,7 @@ import com.github.chrisbanes.photoview.PhotoView
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType
|
||||||
@ -241,6 +242,9 @@ class PagerPageHolder(
|
|||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnNext { isAnimated ->
|
.doOnNext { isAnimated ->
|
||||||
|
if (viewer.config.dualPageSplit) {
|
||||||
|
openStream = processDualPageSplit(openStream!!)
|
||||||
|
}
|
||||||
if (!isAnimated) {
|
if (!isAnimated) {
|
||||||
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
|
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
|
||||||
} else {
|
} else {
|
||||||
@ -253,6 +257,38 @@ class PagerPageHolder(
|
|||||||
.subscribe({}, {})
|
.subscribe({}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun processDualPageSplit(openStream: InputStream): InputStream {
|
||||||
|
var inputStream = openStream
|
||||||
|
val (isDoublePage, stream) = when (page) {
|
||||||
|
is InsertPage -> Pair(true, inputStream)
|
||||||
|
else -> ImageUtil.isDoublePage(inputStream)
|
||||||
|
}
|
||||||
|
inputStream = stream
|
||||||
|
if (isDoublePage) {
|
||||||
|
val side = when {
|
||||||
|
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
|
||||||
|
viewer is R2LPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT
|
||||||
|
viewer is L2RPagerViewer && page !is InsertPage -> ImageUtil.Side.LEFT
|
||||||
|
viewer is R2LPagerViewer && page !is InsertPage -> ImageUtil.Side.RIGHT
|
||||||
|
viewer is VerticalPagerViewer && page !is InsertPage -> ImageUtil.Side.RIGHT
|
||||||
|
viewer is VerticalPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT
|
||||||
|
else -> error("We should choose a side!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page !is InsertPage) {
|
||||||
|
onPageSplit()
|
||||||
|
}
|
||||||
|
|
||||||
|
inputStream = ImageUtil.splitInHalf(inputStream, side)
|
||||||
|
}
|
||||||
|
return inputStream
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onPageSplit() {
|
||||||
|
val newPage = InsertPage(page)
|
||||||
|
viewer.onPageSplit(page, newPage)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the page has an error.
|
* Called when the page has an error.
|
||||||
*/
|
*/
|
||||||
|
@ -12,6 +12,7 @@ import androidx.viewpager.widget.ViewPager
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
|
||||||
@ -371,4 +372,8 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onPageSplit(currentPage: ReaderPage, newPage: InsertPage) {
|
||||||
|
adapter.onPageSplit(currentPage, newPage, this::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
||||||
@ -18,7 +19,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
|||||||
/**
|
/**
|
||||||
* List of currently set items.
|
* List of currently set items.
|
||||||
*/
|
*/
|
||||||
var items: List<Any> = emptyList()
|
var items: MutableList<Any> = mutableListOf()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var nextTransition: ChapterTransition.Next? = null
|
var nextTransition: ChapterTransition.Next? = null
|
||||||
@ -80,6 +81,9 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resets double-page splits, else insert pages get misplaced
|
||||||
|
items.filterIsInstance<InsertPage>().also { items.removeAll(it) }
|
||||||
|
|
||||||
if (viewer is R2LPagerViewer) {
|
if (viewer is R2LPagerViewer) {
|
||||||
newItems.reverse()
|
newItems.reverse()
|
||||||
}
|
}
|
||||||
@ -120,4 +124,31 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
|||||||
}
|
}
|
||||||
return POSITION_NONE
|
return POSITION_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onPageSplit(current: Any?, newPage: InsertPage, clazz: Class<out PagerViewer>) {
|
||||||
|
if (current !is ReaderPage) return
|
||||||
|
|
||||||
|
val currentIndex = items.indexOf(current)
|
||||||
|
|
||||||
|
val placeAtIndex = when {
|
||||||
|
clazz.isAssignableFrom(L2RPagerViewer::class.java) -> currentIndex + 1
|
||||||
|
clazz.isAssignableFrom(VerticalPagerViewer::class.java) -> currentIndex + 1
|
||||||
|
clazz.isAssignableFrom(R2LPagerViewer::class.java) -> currentIndex
|
||||||
|
else -> currentIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// It will enter a endless cycle of insert pages
|
||||||
|
if (clazz.isAssignableFrom(R2LPagerViewer::class.java) && items[placeAtIndex - 1] is InsertPage) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same here it will enter a endless cycle of insert pages
|
||||||
|
if (items[placeAtIndex] is InsertPage) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add(placeAtIndex, newPage)
|
||||||
|
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,6 +287,14 @@ class WebtoonPageHolder(
|
|||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnNext { isAnimated ->
|
.doOnNext { isAnimated ->
|
||||||
|
if (viewer.config.dualPageSplit) {
|
||||||
|
val (isDoublePage, stream) = ImageUtil.isDoublePage(openStream!!)
|
||||||
|
openStream = if (!isDoublePage) {
|
||||||
|
stream
|
||||||
|
} else {
|
||||||
|
ImageUtil.splitAndMerge(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!isAnimated) {
|
if (!isAnimated) {
|
||||||
val subsamplingView = initSubsamplingImageView()
|
val subsamplingView = initSubsamplingImageView()
|
||||||
subsamplingView.isVisible = true
|
subsamplingView.isVisible = true
|
||||||
|
@ -50,6 +50,11 @@ class SettingsReaderController : SettingsController() {
|
|||||||
summaryRes = R.string.pref_show_reading_mode_summary
|
summaryRes = R.string.pref_show_reading_mode_summary
|
||||||
defaultValue = true
|
defaultValue = true
|
||||||
}
|
}
|
||||||
|
switchPreference {
|
||||||
|
key = Keys.dualPageSplit
|
||||||
|
titleRes = R.string.pref_dual_page_split
|
||||||
|
defaultValue = false
|
||||||
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
switchPreference {
|
switchPreference {
|
||||||
key = Keys.trueColor
|
key = Keys.trueColor
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
package eu.kanade.tachiyomi.util.system
|
package eu.kanade.tachiyomi.util.system
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Rect
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
|
|
||||||
@ -68,4 +74,71 @@ object ImageUtil {
|
|||||||
GIF("image/gif", "gif"),
|
GIF("image/gif", "gif"),
|
||||||
WEBP("image/webp", "webp")
|
WEBP("image/webp", "webp")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the image is a double image (width > height), return the result and original stream
|
||||||
|
*/
|
||||||
|
fun isDoublePage(imageStream: InputStream): Pair<Boolean, InputStream> {
|
||||||
|
val imageBytes = imageStream.readBytes()
|
||||||
|
|
||||||
|
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
|
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
|
||||||
|
|
||||||
|
return Pair(options.outWidth > options.outHeight, ByteArrayInputStream(imageBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the 'side' part from imageStream and return it as InputStream.
|
||||||
|
*/
|
||||||
|
fun splitInHalf(imageStream: InputStream, side: Side): InputStream {
|
||||||
|
val imageBytes = imageStream.readBytes()
|
||||||
|
|
||||||
|
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||||
|
val height = imageBitmap.height
|
||||||
|
val width = imageBitmap.width
|
||||||
|
|
||||||
|
val singlePage = Rect(0, 0, width / 2, height)
|
||||||
|
|
||||||
|
val half = Bitmap.createBitmap(width / 2, height, Bitmap.Config.ARGB_8888)
|
||||||
|
val part = when (side) {
|
||||||
|
Side.RIGHT -> Rect(width - width / 2, 0, width, height)
|
||||||
|
Side.LEFT -> Rect(0, 0, width / 2, height)
|
||||||
|
}
|
||||||
|
val canvas = Canvas(half)
|
||||||
|
canvas.drawBitmap(imageBitmap, part, singlePage, null)
|
||||||
|
val output = ByteArrayOutputStream()
|
||||||
|
half.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||||
|
|
||||||
|
return ByteArrayInputStream(output.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split the image into left and right parts, then merge them into a new image.
|
||||||
|
*/
|
||||||
|
fun splitAndMerge(imageStream: InputStream): InputStream {
|
||||||
|
val imageBytes = imageStream.readBytes()
|
||||||
|
|
||||||
|
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||||
|
val height = imageBitmap.height
|
||||||
|
val width = imageBitmap.width
|
||||||
|
|
||||||
|
val result = Bitmap.createBitmap(width / 2, height * 2, Bitmap.Config.ARGB_8888)
|
||||||
|
val canvas = Canvas(result)
|
||||||
|
// right -> upper
|
||||||
|
val rightPart = Rect(width - width / 2, 0, width, height)
|
||||||
|
val upperPart = Rect(0, 0, width / 2, height)
|
||||||
|
canvas.drawBitmap(imageBitmap, rightPart, upperPart, null)
|
||||||
|
// left -> bottom
|
||||||
|
val leftPart = Rect(0, 0, width / 2, height)
|
||||||
|
val bottomPart = Rect(0, height, width / 2, height * 2)
|
||||||
|
canvas.drawBitmap(imageBitmap, leftPart, bottomPart, null)
|
||||||
|
|
||||||
|
val output = ByteArrayOutputStream()
|
||||||
|
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||||
|
return ByteArrayInputStream(output.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Side {
|
||||||
|
RIGHT, LEFT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,14 @@
|
|||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
app:layout_constraintTop_toBottomOf="@id/show_page_number" />
|
app:layout_constraintTop_toBottomOf="@id/show_page_number" />
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/dual_page_split"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/pref_dual_page_split"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/fullscreen" />
|
||||||
|
|
||||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
android:id="@+id/cutout_short"
|
android:id="@+id/cutout_short"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -158,7 +166,7 @@
|
|||||||
android:text="@string/pref_cutout_short"
|
android:text="@string/pref_cutout_short"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintTop_toBottomOf="@id/fullscreen"
|
app:layout_constraintTop_toBottomOf="@id/dual_page_split"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
@ -250,6 +250,7 @@
|
|||||||
|
|
||||||
<!-- Reader section -->
|
<!-- Reader section -->
|
||||||
<string name="pref_fullscreen">Fullscreen</string>
|
<string name="pref_fullscreen">Fullscreen</string>
|
||||||
|
<string name="pref_dual_page_split">Dual page split (ALPHA)</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_lock_orientation">Lock orientation</string>
|
<string name="pref_lock_orientation">Lock orientation</string>
|
||||||
<string name="pref_page_transitions">Animate page transitions</string>
|
<string name="pref_page_transitions">Animate page transitions</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user