Add foldable device support (auto two-page)
This commit is contained in:
@@ -155,6 +155,9 @@ dependencies {
|
|||||||
implementation libs.androidx.work.runtime
|
implementation libs.androidx.work.runtime
|
||||||
implementation libs.guava
|
implementation libs.guava
|
||||||
|
|
||||||
|
// Foldable/Window layout
|
||||||
|
implementation libs.androidx.window
|
||||||
|
|
||||||
implementation libs.androidx.room.runtime
|
implementation libs.androidx.room.runtime
|
||||||
implementation libs.androidx.room.ktx
|
implementation libs.androidx.room.ktx
|
||||||
ksp libs.androidx.room.compiler
|
ksp libs.androidx.room.compiler
|
||||||
|
|||||||
@@ -138,6 +138,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
get() = prefs.getBoolean(KEY_READER_DOUBLE_PAGES, false)
|
get() = prefs.getBoolean(KEY_READER_DOUBLE_PAGES, false)
|
||||||
set(value) = prefs.edit { putBoolean(KEY_READER_DOUBLE_PAGES, value) }
|
set(value) = prefs.edit { putBoolean(KEY_READER_DOUBLE_PAGES, value) }
|
||||||
|
|
||||||
|
var isReaderDoubleOnFoldable: Boolean
|
||||||
|
get() = prefs.getBoolean(KEY_READER_DOUBLE_FOLDABLE, false)
|
||||||
|
set(value) = prefs.edit { putBoolean(KEY_READER_DOUBLE_FOLDABLE, value) }
|
||||||
|
|
||||||
@get:FloatRange(0.0, 1.0)
|
@get:FloatRange(0.0, 1.0)
|
||||||
var readerDoublePagesSensitivity: Float
|
var readerDoublePagesSensitivity: Float
|
||||||
get() = prefs.getFloat(KEY_READER_DOUBLE_PAGES_SENSITIVITY, 0.5f)
|
get() = prefs.getFloat(KEY_READER_DOUBLE_PAGES_SENSITIVITY, 0.5f)
|
||||||
@@ -682,6 +686,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val KEY_LOCAL_STORAGE = "local_storage"
|
const val KEY_LOCAL_STORAGE = "local_storage"
|
||||||
const val KEY_READER_DOUBLE_PAGES = "reader_double_pages"
|
const val KEY_READER_DOUBLE_PAGES = "reader_double_pages"
|
||||||
const val KEY_READER_DOUBLE_PAGES_SENSITIVITY = "reader_double_pages_sensitivity"
|
const val KEY_READER_DOUBLE_PAGES_SENSITIVITY = "reader_double_pages_sensitivity"
|
||||||
|
const val KEY_READER_DOUBLE_FOLDABLE = "reader_double_foldable"
|
||||||
const val KEY_READER_ZOOM_BUTTONS = "reader_zoom_buttons"
|
const val KEY_READER_ZOOM_BUTTONS = "reader_zoom_buttons"
|
||||||
const val KEY_READER_CONTROL_LTR = "reader_taps_ltr"
|
const val KEY_READER_CONTROL_LTR = "reader_taps_ltr"
|
||||||
const val KEY_READER_NAVIGATION_INVERTED = "reader_navigation_inverted"
|
const val KEY_READER_NAVIGATION_INVERTED = "reader_navigation_inverted"
|
||||||
|
|||||||
@@ -24,10 +24,15 @@ import androidx.transition.Fade
|
|||||||
import androidx.transition.Slide
|
import androidx.transition.Slide
|
||||||
import androidx.transition.TransitionManager
|
import androidx.transition.TransitionManager
|
||||||
import androidx.transition.TransitionSet
|
import androidx.transition.TransitionSet
|
||||||
|
import androidx.window.layout.FoldingFeature
|
||||||
|
import androidx.window.layout.WindowInfoTracker
|
||||||
|
import android.content.res.Configuration
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
@@ -109,6 +114,8 @@ class ReaderActivity :
|
|||||||
private var gestureInsets: Insets = Insets.NONE
|
private var gestureInsets: Insets = Insets.NONE
|
||||||
private lateinit var readerManager: ReaderManager
|
private lateinit var readerManager: ReaderManager
|
||||||
private val hideUiRunnable = Runnable { setUiIsVisible(false) }
|
private val hideUiRunnable = Runnable { setUiIsVisible(false) }
|
||||||
|
// Tracks whether the foldable device is in an unfolded state (half-opened or flat)
|
||||||
|
private var isFoldUnfolded: Boolean = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -187,6 +194,25 @@ class ReaderActivity :
|
|||||||
viewBinding.zoomControl.isVisible = it
|
viewBinding.zoomControl.isVisible = it
|
||||||
}
|
}
|
||||||
addMenuProvider(ReaderMenuProvider(viewModel))
|
addMenuProvider(ReaderMenuProvider(viewModel))
|
||||||
|
|
||||||
|
// Observe foldable window layout to auto-enable double-page if configured
|
||||||
|
WindowInfoTracker.getOrCreate(this)
|
||||||
|
.windowLayoutInfo(this)
|
||||||
|
.onEach { info ->
|
||||||
|
val fold = info.displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull()
|
||||||
|
val unfolded = when (fold?.state) {
|
||||||
|
FoldingFeature.State.HALF_OPENED, FoldingFeature.State.FLAT -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
if (unfolded != isFoldUnfolded) {
|
||||||
|
isFoldUnfolded = unfolded
|
||||||
|
applyDoubleModeAuto()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(lifecycleScope)
|
||||||
|
|
||||||
|
// Apply initial double-mode considering foldable setting
|
||||||
|
applyDoubleModeAuto()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getParentActivityIntent(): Intent? {
|
override fun getParentActivityIntent(): Intent? {
|
||||||
@@ -341,7 +367,17 @@ class ReaderActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDoubleModeChanged(isEnabled: Boolean) {
|
override fun onDoubleModeChanged(isEnabled: Boolean) {
|
||||||
readerManager.setDoubleReaderMode(isEnabled)
|
// Combine manual toggle with foldable auto setting
|
||||||
|
applyDoubleModeAuto(isEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyDoubleModeAuto(manualEnabled: Boolean? = null) {
|
||||||
|
val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
|
// Auto double-page on foldable when device is unfolded (half-opened or flat)
|
||||||
|
val autoFoldable = settings.isReaderDoubleOnFoldable && isFoldUnfolded
|
||||||
|
val manualLandscape = (manualEnabled ?: settings.isReaderDoubleOnLandscape) && isLandscape
|
||||||
|
val autoEnabled = autoFoldable || manualLandscape
|
||||||
|
readerManager.setDoubleReaderMode(autoEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setKeepScreenOn(isKeep: Boolean) {
|
private fun setKeepScreenOn(isKeep: Boolean) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class ReaderManager(
|
|||||||
fun setDoubleReaderMode(isEnabled: Boolean) {
|
fun setDoubleReaderMode(isEnabled: Boolean) {
|
||||||
val mode = currentMode
|
val mode = currentMode
|
||||||
val prevReader = currentReader?.javaClass
|
val prevReader = currentReader?.javaClass
|
||||||
invalidateTypesMap(isEnabled && isLandscape())
|
invalidateTypesMap(isEnabled)
|
||||||
val newReader = modeMap[mode]
|
val newReader = modeMap[mode]
|
||||||
if (mode != null && newReader != prevReader) {
|
if (mode != null && newReader != prevReader) {
|
||||||
replace(mode)
|
replace(mode)
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ class ReaderConfigSheet :
|
|||||||
binding.buttonVertical.isChecked = mode == ReaderMode.VERTICAL
|
binding.buttonVertical.isChecked = mode == ReaderMode.VERTICAL
|
||||||
binding.switchDoubleReader.isChecked = settings.isReaderDoubleOnLandscape
|
binding.switchDoubleReader.isChecked = settings.isReaderDoubleOnLandscape
|
||||||
binding.switchDoubleReader.isEnabled = mode == ReaderMode.STANDARD || mode == ReaderMode.REVERSED
|
binding.switchDoubleReader.isEnabled = mode == ReaderMode.STANDARD || mode == ReaderMode.REVERSED
|
||||||
|
binding.switchDoubleFoldable.isChecked = settings.isReaderDoubleOnFoldable
|
||||||
|
binding.switchDoubleFoldable.isEnabled = binding.switchDoubleReader.isEnabled
|
||||||
binding.sliderDoubleSensitivity.setValueRounded(settings.readerDoublePagesSensitivity * 100f)
|
binding.sliderDoubleSensitivity.setValueRounded(settings.readerDoublePagesSensitivity * 100f)
|
||||||
binding.sliderDoubleSensitivity.setLabelFormatter(IntPercentLabelFormatter(binding.root.context))
|
binding.sliderDoubleSensitivity.setLabelFormatter(IntPercentLabelFormatter(binding.root.context))
|
||||||
binding.adjustSensitivitySlider(withAnimation = false)
|
binding.adjustSensitivitySlider(withAnimation = false)
|
||||||
@@ -104,6 +106,7 @@ class ReaderConfigSheet :
|
|||||||
binding.buttonScrollTimer.setOnClickListener(this)
|
binding.buttonScrollTimer.setOnClickListener(this)
|
||||||
binding.buttonBookmark.setOnClickListener(this)
|
binding.buttonBookmark.setOnClickListener(this)
|
||||||
binding.switchDoubleReader.setOnCheckedChangeListener(this)
|
binding.switchDoubleReader.setOnCheckedChangeListener(this)
|
||||||
|
binding.switchDoubleFoldable.setOnCheckedChangeListener(this)
|
||||||
binding.sliderDoubleSensitivity.addOnChangeListener(this)
|
binding.sliderDoubleSensitivity.addOnChangeListener(this)
|
||||||
|
|
||||||
viewModel.isBookmarkAdded.observe(viewLifecycleOwner) {
|
viewModel.isBookmarkAdded.observe(viewLifecycleOwner) {
|
||||||
@@ -182,6 +185,12 @@ class ReaderConfigSheet :
|
|||||||
viewBinding?.adjustSensitivitySlider(withAnimation = true)
|
viewBinding?.adjustSensitivitySlider(withAnimation = true)
|
||||||
findParentCallback(Callback::class.java)?.onDoubleModeChanged(isChecked)
|
findParentCallback(Callback::class.java)?.onDoubleModeChanged(isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.switch_double_foldable -> {
|
||||||
|
settings.isReaderDoubleOnFoldable = isChecked
|
||||||
|
// Re-evaluate double-page considering foldable state and current manual toggle
|
||||||
|
findParentCallback(Callback::class.java)?.onDoubleModeChanged(settings.isReaderDoubleOnLandscape)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +215,7 @@ class ReaderConfigSheet :
|
|||||||
}
|
}
|
||||||
viewBinding?.run {
|
viewBinding?.run {
|
||||||
switchDoubleReader.isEnabled = newMode == ReaderMode.STANDARD || newMode == ReaderMode.REVERSED
|
switchDoubleReader.isEnabled = newMode == ReaderMode.STANDARD || newMode == ReaderMode.REVERSED
|
||||||
|
switchDoubleFoldable.isEnabled = switchDoubleReader.isEnabled
|
||||||
adjustSensitivitySlider(withAnimation = true)
|
adjustSensitivitySlider(withAnimation = true)
|
||||||
}
|
}
|
||||||
if (newMode == mode) {
|
if (newMode == mode) {
|
||||||
@@ -242,12 +252,18 @@ class ReaderConfigSheet :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun SheetReaderConfigBinding.adjustSensitivitySlider(withAnimation: Boolean) {
|
private fun SheetReaderConfigBinding.adjustSensitivitySlider(withAnimation: Boolean) {
|
||||||
val isSliderVisible = switchDoubleReader.isEnabled && switchDoubleReader.isChecked
|
val isSubOptionsVisible = switchDoubleReader.isEnabled && switchDoubleReader.isChecked
|
||||||
if (isSliderVisible != sliderDoubleSensitivity.isVisible && withAnimation) {
|
val needTransition = withAnimation && (
|
||||||
|
(isSubOptionsVisible != sliderDoubleSensitivity.isVisible) ||
|
||||||
|
(isSubOptionsVisible != textDoubleSensitivity.isVisible) ||
|
||||||
|
(isSubOptionsVisible != switchDoubleFoldable.isVisible)
|
||||||
|
)
|
||||||
|
if (needTransition) {
|
||||||
TransitionManager.beginDelayedTransition(layoutMain)
|
TransitionManager.beginDelayedTransition(layoutMain)
|
||||||
}
|
}
|
||||||
sliderDoubleSensitivity.isVisible = isSliderVisible
|
sliderDoubleSensitivity.isVisible = isSubOptionsVisible
|
||||||
textDoubleSensitivity.isVisible = isSliderVisible
|
textDoubleSensitivity.isVisible = isSubOptionsVisible
|
||||||
|
switchDoubleFoldable.isVisible = isSubOptionsVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
|
|||||||
@@ -130,6 +130,21 @@
|
|||||||
android:textColor="?colorOnSurfaceVariant"
|
android:textColor="?colorOnSurfaceVariant"
|
||||||
app:drawableStartCompat="@drawable/ic_split_horizontal" />
|
app:drawableStartCompat="@drawable/ic_split_horizontal" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/switch_double_foldable"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_small"
|
||||||
|
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
||||||
|
android:minHeight="?android:listPreferredItemHeightSmall"
|
||||||
|
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||||
|
android:text="@string/auto_double_foldable"
|
||||||
|
android:textAppearance="@style/TextAppearance.Kotatsu.GridTitle"
|
||||||
|
android:textColor="?colorOnSurfaceVariant"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_double_sensitivity"
|
android:id="@+id/text_double_sensitivity"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -866,4 +866,5 @@
|
|||||||
<string name="pull_bottom_no_next">到底了</string>
|
<string name="pull_bottom_no_next">到底了</string>
|
||||||
<string name="enable_pull_gesture_title">启用推拉手势</string>
|
<string name="enable_pull_gesture_title">启用推拉手势</string>
|
||||||
<string name="enable_pull_gesture_summary">条漫模式下使用推拉手势切换章节</string>
|
<string name="enable_pull_gesture_summary">条漫模式下使用推拉手势切换章节</string>
|
||||||
|
<string name="auto_double_foldable">折叠设备自动双页</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -661,4 +661,5 @@
|
|||||||
<string name="download_new_chapters">下載新的漫畫章節</string>
|
<string name="download_new_chapters">下載新的漫畫章節</string>
|
||||||
<string name="enable_all_sources">啟用所有漫畫來源</string>
|
<string name="enable_all_sources">啟用所有漫畫來源</string>
|
||||||
<string name="all_sources_enabled">所有漫畫來源已啟用</string>
|
<string name="all_sources_enabled">所有漫畫來源已啟用</string>
|
||||||
|
<string name="auto_double_foldable">摺疊設備自動雙頁</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -579,6 +579,8 @@
|
|||||||
<string name="none">None</string>
|
<string name="none">None</string>
|
||||||
<string name="config_reset_confirm">Reset settings to default values? This action cannot be undone.</string>
|
<string name="config_reset_confirm">Reset settings to default values? This action cannot be undone.</string>
|
||||||
<string name="use_two_pages_landscape">Use two pages layout on landscape orientation (beta)</string>
|
<string name="use_two_pages_landscape">Use two pages layout on landscape orientation (beta)</string>
|
||||||
|
<string name="auto_double_foldable">Auto Two-Page On Foldable</string>
|
||||||
|
<string name="auto_double_foldable_summary">Enable two-page layout when device has a separating hinge</string>
|
||||||
<string name="two_page_scroll_sensitivity">Two-Page Scroll Sensitivity</string>
|
<string name="two_page_scroll_sensitivity">Two-Page Scroll Sensitivity</string>
|
||||||
<string name="default_webtoon_zoom_out">Default webtoon zoom out</string>
|
<string name="default_webtoon_zoom_out">Default webtoon zoom out</string>
|
||||||
<string name="fullscreen_mode">Fullscreen mode</string>
|
<string name="fullscreen_mode">Fullscreen mode</string>
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ viewpager2 = "1.1.0"
|
|||||||
webkit = "1.14.0"
|
webkit = "1.14.0"
|
||||||
workRuntime = "2.10.5"
|
workRuntime = "2.10.5"
|
||||||
workinspector = "1.2"
|
workinspector = "1.2"
|
||||||
|
window = "1.3.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
acra-dialog = { module = "ch.acra:acra-dialog", version.ref = "acra" }
|
acra-dialog = { module = "ch.acra:acra-dialog", version.ref = "acra" }
|
||||||
@@ -115,6 +116,7 @@ okhttp-tls = { module = "com.squareup.okhttp3:okhttp-tls", version.ref = "okhttp
|
|||||||
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
|
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
|
||||||
ssiv = { module = "com.github.KotatsuApp:subsampling-scale-image-view", version.ref = "ssiv" }
|
ssiv = { module = "com.github.KotatsuApp:subsampling-scale-image-view", version.ref = "ssiv" }
|
||||||
workinspector = { module = "com.github.Koitharu:WorkInspector", version.ref = "workinspector" }
|
workinspector = { module = "com.github.Koitharu:WorkInspector", version.ref = "workinspector" }
|
||||||
|
androidx-window = { module = "androidx.window:window", version.ref = "window" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "gradle" }
|
android-application = { id = "com.android.application", version.ref = "gradle" }
|
||||||
|
|||||||
Reference in New Issue
Block a user