Refactor for upstream Slider api changes
This commit is contained in:
1 parent
5c406ac8b0
commit
baf8333b70
15 files changed
+96
-115
No files matched your search
@@ -10,10 +10,6 @@ import androidx.compose.material.icons.filled.Brightness5
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -23,6 +19,8 @@ import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.ui.component.RollingNumber
|
||||
import com.ehviewer.core.ui.component.rememberIntPrefSliderState
|
||||
import com.ehviewer.core.ui.component.rememberIntSliderState
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
|
||||
@@ -34,12 +32,11 @@ fun ColorFilterSetting() = Column(modifier = Modifier.verticalScroll(rememberScr
|
||||
field = customBrightness,
|
||||
)
|
||||
AnimatedVisibility(visible = customBrightness.value) {
|
||||
val brightness = Settings.customBrightnessValue.asMutableState()
|
||||
val brightness = rememberIntPrefSliderState(Settings.customBrightnessValue, -75..100)
|
||||
SliderChoice(
|
||||
startSlot = { Icon(imageVector = Icons.Default.Brightness5, contentDescription = null) },
|
||||
endSlot = { RollingNumber(number = brightness.value, length = 3) },
|
||||
range = -75..100,
|
||||
field = brightness,
|
||||
state = brightness,
|
||||
)
|
||||
}
|
||||
val colorFilter = Settings.colorFilter.asMutableState()
|
||||
@@ -48,40 +45,32 @@ fun ColorFilterSetting() = Column(modifier = Modifier.verticalScroll(rememberScr
|
||||
field = colorFilter,
|
||||
)
|
||||
AnimatedVisibility(visible = colorFilter.value) {
|
||||
var color by Settings.colorFilterValue.asMutableState()
|
||||
val rf = remember { mutableIntStateOf(color.red) }
|
||||
val r by rf
|
||||
val gf = remember { mutableIntStateOf(color.green) }
|
||||
val g by gf
|
||||
val bf = remember { mutableIntStateOf(color.blue) }
|
||||
val b by bf
|
||||
val af = remember { mutableIntStateOf(color.alpha) }
|
||||
val a by af
|
||||
color = (a shl 24) or (r shl 16) or (g shl 8) or (b)
|
||||
var color by Settings.colorFilterValue
|
||||
val r = rememberIntSliderState(color.red, 0..255)
|
||||
val g = rememberIntSliderState(color.green, 0..255)
|
||||
val b = rememberIntSliderState(color.blue, 0..255)
|
||||
val a = rememberIntSliderState(color.alpha, 0..255)
|
||||
color = (a.value shl 24) or (r.value shl 16) or (g.value shl 8) or (b.value)
|
||||
Column {
|
||||
SliderChoice(
|
||||
startSlot = { Text(text = "R") },
|
||||
endSlot = { RollingNumber(number = r, length = 3) },
|
||||
range = 0..255,
|
||||
field = rf,
|
||||
endSlot = { RollingNumber(number = r.value, length = 3) },
|
||||
state = r,
|
||||
)
|
||||
SliderChoice(
|
||||
startSlot = { Text(text = "G") },
|
||||
endSlot = { RollingNumber(number = g, length = 3) },
|
||||
range = 0..255,
|
||||
field = gf,
|
||||
endSlot = { RollingNumber(number = g.value, length = 3) },
|
||||
state = g,
|
||||
)
|
||||
SliderChoice(
|
||||
startSlot = { Text(text = "B") },
|
||||
endSlot = { RollingNumber(number = b, length = 3) },
|
||||
range = 0..255,
|
||||
field = bf,
|
||||
endSlot = { RollingNumber(number = b.value, length = 3) },
|
||||
state = b,
|
||||
)
|
||||
SliderChoice(
|
||||
startSlot = { Text(text = "A") },
|
||||
endSlot = { RollingNumber(number = a, length = 3) },
|
||||
range = 0..255,
|
||||
field = af,
|
||||
endSlot = { RollingNumber(number = a.value, length = 3) },
|
||||
state = a,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.layout.LazyLayoutCacheWindow
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
@@ -93,6 +92,7 @@ import eu.kanade.tachiyomi.ui.reader.ReaderContentOverlay
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderPageSheetMeta
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.flow.merge
|
||||
@@ -215,9 +215,9 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
uiController.showTransientSystemBarsBySwipe = false
|
||||
}
|
||||
}
|
||||
val lazyListState = rememberLazyListState(LazyLayoutCacheWindow(SCROLL_FRACTION, SCROLL_FRACTION), pageLoader.startPage)
|
||||
val lazyListState = rememberLazyListState(pageLoader.startPage)
|
||||
val pagerState = rememberPagerState(pageLoader.startPage) { pageLoader.size }
|
||||
val syncState = rememberSliderPagerDoubleSyncState(lazyListState, pagerState, pageLoader)
|
||||
val sync = rememberSliderPagerDoubleSyncState(lazyListState, pagerState, pageLoader)
|
||||
var appbarVisible by remember { mutableStateOf(false) }
|
||||
val isWebtoon by rememberUpdatedState(ReadingModeType.isWebtoon(readingMode))
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
@@ -232,7 +232,7 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
syncState.Sync(isWebtoon) { appbarVisible = false }
|
||||
sync.Sync(isWebtoon) { appbarVisible = false }
|
||||
val bgColor by collectBackgroundColorAsState()
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
LaunchedEffect(isDarkTheme) {
|
||||
@@ -293,10 +293,10 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
onNavigationModeChange = { showNavigationOverlay = true },
|
||||
onSelectPage = onSelectPage,
|
||||
onMenuRegionClick = { appbarVisible = !appbarVisible },
|
||||
modifier = Modifier.background(bgColor).pointerInput(syncState) {
|
||||
modifier = Modifier.background(bgColor).pointerInput(sync) {
|
||||
awaitEachGesture {
|
||||
waitForUpOrCancellation()
|
||||
syncState.reset()
|
||||
sync.reset()
|
||||
showNavigationOverlay = false
|
||||
}
|
||||
}.fillMaxSize().windowInsetsPadding(insets),
|
||||
@@ -324,7 +324,7 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
)
|
||||
if (brightness) {
|
||||
LaunchedEffect(Unit) {
|
||||
Settings.customBrightnessValue.valueFlow().sample(100)
|
||||
Settings.customBrightnessValue.valueFlow().sample(100.milliseconds)
|
||||
.onCompletion { activity.setCustomBrightnessValue(0) }
|
||||
.collect { activity.setCustomBrightnessValue(it) }
|
||||
}
|
||||
@@ -333,7 +333,7 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
if (showPageNumber && !appbarVisible) {
|
||||
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.bodySmall) {
|
||||
PageIndicatorText(
|
||||
currentPage = syncState.sliderValue,
|
||||
currentPage = sync.slider.value,
|
||||
totalPages = pageLoader.size,
|
||||
modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding(),
|
||||
)
|
||||
@@ -344,9 +344,9 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
title = pageLoader.title,
|
||||
isRtl = readingMode == ReadingModeType.RIGHT_TO_LEFT,
|
||||
showSeekBar = showSeekbar,
|
||||
currentPage = syncState.sliderValue,
|
||||
slider = sync.slider,
|
||||
totalPages = pageLoader.size,
|
||||
onSliderValueChange = syncState::sliderScrollTo,
|
||||
onSliderValueChange = sync::sliderScrollTo,
|
||||
onClickSettings = {
|
||||
launch {
|
||||
dialog { cont ->
|
||||
|
||||
@@ -6,11 +6,11 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import com.ehviewer.core.ui.component.IntSliderState
|
||||
import com.hippo.ehviewer.gallery.PageLoader
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.drop
|
||||
@@ -23,12 +23,11 @@ class SliderPagerDoubleSync(
|
||||
private val pageLoader: PageLoader,
|
||||
) {
|
||||
private var sliderFollowPager by mutableStateOf(true)
|
||||
var sliderValue by mutableIntStateOf(pageLoader.startPage + 1)
|
||||
private set
|
||||
val slider = IntSliderState(pageLoader.startPage + 1, 1..pageLoader.size)
|
||||
|
||||
fun sliderScrollTo(index: Int) {
|
||||
sliderFollowPager = false
|
||||
sliderValue = index.coerceIn(1, pageLoader.size)
|
||||
slider.value = index
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
@@ -50,7 +49,7 @@ class SliderPagerDoubleSync(
|
||||
@Composable
|
||||
fun Sync(webtoon: Boolean, onPageSelected: () -> Unit) {
|
||||
val currentIndexFlow = remember(webtoon) {
|
||||
val initialIndex = sliderValue - 1
|
||||
val initialIndex = slider.value - 1
|
||||
sliderFollowPager = if (webtoon) {
|
||||
lazyListState.firstVisibleItemIndex == initialIndex
|
||||
} else {
|
||||
@@ -61,14 +60,14 @@ class SliderPagerDoubleSync(
|
||||
if (sliderFollowPager) {
|
||||
LaunchedEffect(currentIndexFlow) {
|
||||
currentIndexFlow.drop(1).collect { index ->
|
||||
sliderValue = index + 1
|
||||
slider.value = index + 1
|
||||
pageLoader.startPage = index
|
||||
onPageSelected()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LaunchedEffect(webtoon) {
|
||||
snapshotFlow { sliderValue - 1 }.collectLatest { index ->
|
||||
snapshotFlow { slider.value - 1 }.collectLatest { index ->
|
||||
if (webtoon) {
|
||||
lazyListState.scrollToItem(index)
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.layout.LazyLayoutCacheWindow
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -81,6 +82,7 @@ fun WebtoonViewer(
|
||||
state = lazyListState,
|
||||
contentPadding = PaddingValues(horizontal = sidePadding),
|
||||
verticalArrangement = Arrangement.spacedBy(if (withGaps) 15.dp else 0.dp),
|
||||
cacheWindow = LazyLayoutCacheWindow(SCROLL_FRACTION, SCROLL_FRACTION),
|
||||
) {
|
||||
items(items, key = { it.index }) { page ->
|
||||
PagerItem(
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.ui.component.IntSliderState
|
||||
import com.ehviewer.core.ui.component.Slider
|
||||
|
||||
@Composable
|
||||
@@ -84,20 +85,17 @@ fun SwitchChoice(title: String, field: MutableState<Boolean>) {
|
||||
fun SliderChoice(
|
||||
startSlot: @Composable () -> Unit,
|
||||
endSlot: @Composable () -> Unit,
|
||||
range: IntRange,
|
||||
field: MutableState<Int>,
|
||||
state: IntSliderState,
|
||||
) {
|
||||
var value by field
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
startSlot()
|
||||
Slider(
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
state = state,
|
||||
onValueChange = { state.value = it },
|
||||
modifier = Modifier.weight(1f).padding(8.dp),
|
||||
valueRange = range,
|
||||
)
|
||||
endSlot()
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.merge
|
||||
@@ -214,7 +215,7 @@ fun AnimatedVisibilityScope.AdvancedScreen(navigator: DestinationsNavigator) = S
|
||||
step = 3,
|
||||
title = stringResource(id = R.string.settings_advanced_hardware_bitmap_threshold),
|
||||
summary = stringResource(id = R.string.settings_advanced_hardware_bitmap_threshold_summary),
|
||||
state = Settings.hardwareBitmapThreshold.asMutableState(),
|
||||
pref = Settings.hardwareBitmapThreshold,
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
@@ -289,7 +290,7 @@ fun AnimatedVisibilityScope.AdvancedScreen(navigator: DestinationsNavigator) = S
|
||||
EhDB.putLocalFavorites(result.galleryInfoList)
|
||||
launchSnackbar(string(R.string.settings_advanced_backup_favorite_start, status))
|
||||
if (result.next != null) {
|
||||
delay(Settings.downloadDelay.value.toLong())
|
||||
delay(Settings.downloadDelay.value.milliseconds)
|
||||
favListUrlBuilder.setIndex(result.next, true)
|
||||
doBackup()
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ fun AnimatedVisibilityScope.DownloadScreen(navigator: DestinationsNavigator) = S
|
||||
maxValue = 10,
|
||||
minValue = MIN_SPEED_LEVEL,
|
||||
title = stringResource(id = R.string.settings_download_timeout_speed),
|
||||
state = Settings.timeoutSpeed.asMutableState(),
|
||||
pref = Settings.timeoutSpeed,
|
||||
display = ::speedLevelToSpeed,
|
||||
)
|
||||
val preloadImage = Settings.preloadImage.asMutableState()
|
||||
|
||||
@@ -193,7 +193,7 @@ fun AnimatedVisibilityScope.EhScreen(navigator: DestinationsNavigator) = Screen(
|
||||
minValue = 20,
|
||||
step = 7,
|
||||
title = stringResource(id = R.string.list_tile_thumb_size),
|
||||
state = Settings.listThumbSize.asMutableState(),
|
||||
pref = Settings.listThumbSize,
|
||||
)
|
||||
SimpleMenuPreferenceInt(
|
||||
title = stringResource(id = R.string.settings_eh_detail_size),
|
||||
@@ -207,7 +207,7 @@ fun AnimatedVisibilityScope.EhScreen(navigator: DestinationsNavigator) = Screen(
|
||||
maxValue = 10,
|
||||
minValue = 1,
|
||||
title = stringResource(id = R.string.settings_eh_thumb_columns),
|
||||
state = Settings.thumbColumns.asMutableState(),
|
||||
pref = Settings.thumbColumns,
|
||||
)
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.settings_eh_show_gallery_pages),
|
||||
@@ -235,7 +235,7 @@ fun AnimatedVisibilityScope.EhScreen(navigator: DestinationsNavigator) = Screen(
|
||||
minValue = -101,
|
||||
title = stringResource(id = R.string.settings_eh_show_gallery_comment_threshold),
|
||||
summary = stringResource(id = R.string.settings_eh_show_gallery_comment_threshold_summary),
|
||||
state = Settings.commentThreshold.asMutableState(),
|
||||
pref = Settings.commentThreshold,
|
||||
)
|
||||
}
|
||||
if (EhTagDatabase.translatable) {
|
||||
|
||||
@@ -21,7 +21,9 @@ import androidx.compose.ui.res.integerArrayResource
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import com.ehviewer.core.preferences.PrefDelegate
|
||||
import com.ehviewer.core.ui.component.RollingNumber
|
||||
import com.ehviewer.core.ui.component.rememberIntPrefSliderState
|
||||
import com.hippo.ehviewer.ui.openBrowser
|
||||
import com.hippo.ehviewer.util.ProgressDialog
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
@@ -71,19 +73,16 @@ fun SwitchPreference(title: String, summary: String? = null, state: MutableState
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun IntSliderPreference(maxValue: Int, minValue: Int = 0, step: Int = maxValue - minValue - 1, title: String, summary: String? = null, state: MutableState<Int>, enabled: Boolean = true, display: (Int) -> Int = { it }) {
|
||||
val sliderState = remember { mutableIntStateOf(state.value) }
|
||||
fun IntSliderPreference(maxValue: Int, minValue: Int = 0, step: Int = maxValue - minValue - 1, title: String, summary: String? = null, pref: PrefDelegate<Int>, enabled: Boolean = true, display: (Int) -> Int = { it }) {
|
||||
val state = rememberIntPrefSliderState(pref, minValue..maxValue, step)
|
||||
IntSliderPreference(
|
||||
state = state,
|
||||
title = { Text(title) },
|
||||
valueRange = minValue..maxValue,
|
||||
valueSteps = step,
|
||||
sliderState = sliderState,
|
||||
enabled = enabled,
|
||||
summary = summary?.let { { Text(it) } },
|
||||
valueText = {
|
||||
RollingNumber(
|
||||
number = display(sliderState.intValue),
|
||||
number = display(state.value),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
length = 5,
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -17,6 +18,7 @@ import com.ehviewer.core.util.launch
|
||||
import com.hippo.ehviewer.EhApplication.Companion.searchDatabase
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.ui.Screen
|
||||
import com.hippo.ehviewer.ui.isAuthenticationSupported
|
||||
import com.hippo.ehviewer.ui.main.NavigationIcon
|
||||
@@ -48,17 +50,17 @@ fun AnimatedVisibilityScope.PrivacyScreen(navigator: DestinationsNavigator) = Sc
|
||||
enabled = isAuthenticationSupported(),
|
||||
)
|
||||
AnimatedVisibility(visible = security.value) {
|
||||
val securityDelay = Settings.securityDelay.asMutableState()
|
||||
val summary = if (securityDelay.value == 0) {
|
||||
val delay by Settings.securityDelay.collectAsState()
|
||||
val summary = if (delay == 0) {
|
||||
stringResource(id = R.string.settings_privacy_require_unlock_delay_summary_immediately)
|
||||
} else {
|
||||
stringResource(id = R.string.settings_privacy_require_unlock_delay_summary, securityDelay.value)
|
||||
stringResource(id = R.string.settings_privacy_require_unlock_delay_summary, delay)
|
||||
}
|
||||
IntSliderPreference(
|
||||
maxValue = 30,
|
||||
title = stringResource(id = R.string.settings_privacy_require_unlock_delay),
|
||||
summary = summary,
|
||||
state = securityDelay,
|
||||
pref = Settings.securityDelay,
|
||||
enabled = isAuthenticationSupported(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.ui.component.IntSliderState
|
||||
import com.ehviewer.core.ui.component.Slider
|
||||
import com.ehviewer.core.ui.component.defaultMaxTickCount
|
||||
import com.ehviewer.core.ui.util.HapticFeedbackType
|
||||
@@ -29,7 +30,7 @@ import com.ehviewer.core.ui.util.rememberHapticFeedback
|
||||
@Composable
|
||||
fun ChapterNavigator(
|
||||
isRtl: Boolean,
|
||||
currentPage: Int,
|
||||
slider: IntSliderState,
|
||||
totalPages: Int,
|
||||
onSliderValueChange: (Int) -> Unit,
|
||||
containerColor: Color,
|
||||
@@ -40,14 +41,13 @@ fun ChapterNavigator(
|
||||
modifier = Modifier.padding(horizontal = horizontalPadding).clip(CircleShape).background(containerColor).padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(text = "$currentPage")
|
||||
val steps = totalPages - 2
|
||||
Text(text = "${slider.value}")
|
||||
val maxTickCount = defaultMaxTickCount()
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
if (steps < maxTickCount) {
|
||||
if (slider.state.steps < maxTickCount) {
|
||||
val sliderDragged by interactionSource.collectIsDraggedAsState()
|
||||
val hapticFeedback = rememberHapticFeedback()
|
||||
LaunchedEffect(currentPage) {
|
||||
LaunchedEffect(slider.value) {
|
||||
if (sliderDragged) {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.MOVE)
|
||||
}
|
||||
@@ -55,9 +55,7 @@ fun ChapterNavigator(
|
||||
}
|
||||
Slider(
|
||||
modifier = Modifier.weight(1f).padding(horizontal = 8.dp),
|
||||
value = currentPage,
|
||||
valueRange = 1..totalPages,
|
||||
steps = steps,
|
||||
state = slider,
|
||||
onValueChange = onSliderValueChange,
|
||||
maxTickCount = maxTickCount,
|
||||
interactionSource = interactionSource,
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.ui.component.IntSliderState
|
||||
import com.hippo.ehviewer.ui.main.NavigationIcon
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
|
||||
@@ -35,7 +36,7 @@ fun BoxScope.ReaderAppBars(
|
||||
title: String,
|
||||
isRtl: Boolean,
|
||||
showSeekBar: Boolean,
|
||||
currentPage: Int,
|
||||
slider: IntSliderState,
|
||||
totalPages: Int,
|
||||
onSliderValueChange: (Int) -> Unit,
|
||||
onClickSettings: () -> Unit,
|
||||
@@ -65,7 +66,7 @@ fun BoxScope.ReaderAppBars(
|
||||
if (showSeekBar && totalPages > 1) {
|
||||
ChapterNavigator(
|
||||
isRtl = isRtl,
|
||||
currentPage = currentPage,
|
||||
slider = slider,
|
||||
totalPages = totalPages,
|
||||
onSliderValueChange = onSliderValueChange,
|
||||
containerColor = backgroundColor,
|
||||
|
||||
@@ -21,39 +21,21 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableIntState
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.ehviewer.core.ui.component.IntSliderState
|
||||
import com.ehviewer.core.ui.component.Slider
|
||||
|
||||
@Composable
|
||||
fun IntSliderPreference(
|
||||
state: MutableState<Int>,
|
||||
state: IntSliderState,
|
||||
title: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
valueRange: IntRange = 0..1,
|
||||
valueSteps: Int = 0,
|
||||
sliderState: MutableIntState = remember { mutableIntStateOf(state.value) },
|
||||
enabled: Boolean = true,
|
||||
icon: @Composable (() -> Unit)? = null,
|
||||
summary: @Composable (() -> Unit)? = null,
|
||||
valueText: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
var value by state
|
||||
var sliderValue by sliderState
|
||||
var lastValue by remember { mutableIntStateOf(value) }
|
||||
SideEffect {
|
||||
if (value != lastValue) {
|
||||
sliderValue = value
|
||||
lastValue = value
|
||||
}
|
||||
}
|
||||
Preference(
|
||||
title = title,
|
||||
modifier = modifier,
|
||||
@@ -64,13 +46,10 @@ fun IntSliderPreference(
|
||||
summary?.invoke()
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Slider(
|
||||
value = sliderValue,
|
||||
onValueChange = { sliderValue = it },
|
||||
state = state,
|
||||
onValueChange = { state.value = it },
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = enabled,
|
||||
valueRange = valueRange,
|
||||
steps = valueSteps,
|
||||
onValueChangeFinished = { value = sliderValue },
|
||||
)
|
||||
if (valueText != null) {
|
||||
val theme = LocalPreferenceTheme.current
|
||||
|
||||
@@ -7,6 +7,7 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api(projects.core.common)
|
||||
api(projects.core.data)
|
||||
api(libs.compose.material3)
|
||||
api(libs.compose.material.icons.extended)
|
||||
api(libs.compose.material3.adaptive)
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SliderColors
|
||||
import androidx.compose.material3.SliderDefaults
|
||||
import androidx.compose.material3.SliderDefaults.drawStopIndicator
|
||||
import androidx.compose.material3.SliderState
|
||||
import androidx.compose.material3.rememberSliderState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -16,30 +17,41 @@ import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalWindowInfo
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.util.fastRoundToInt
|
||||
import com.ehviewer.core.preferences.PrefDelegate
|
||||
|
||||
class IntSliderState(val state: SliderState) {
|
||||
constructor(value: Int, range: IntRange, steps: Int = range.last - range.first - 1) : this(SliderState(value.toFloat(), steps, range.first.toFloat()..range.last.toFloat()))
|
||||
var value: Int
|
||||
get() = state.value.fastRoundToInt()
|
||||
set(value) {
|
||||
state.value = value.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberIntSliderState(value: Int, range: IntRange, steps: Int = range.last - range.first - 1): IntSliderState {
|
||||
val state = rememberSliderState(value.toFloat(), steps, range.first.toFloat()..range.last.toFloat())
|
||||
return remember(range, steps) { IntSliderState(state) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberIntPrefSliderState(value: PrefDelegate<Int>, range: IntRange, steps: Int = range.last - range.first - 1) = rememberIntSliderState(value.value, range, steps).also { value.value = it.value }
|
||||
|
||||
@Composable
|
||||
fun Slider(
|
||||
value: Int,
|
||||
state: IntSliderState,
|
||||
onValueChange: (Int) -> Unit,
|
||||
valueRange: IntRange,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
steps: Int = valueRange.last - valueRange.first - 1,
|
||||
maxTickCount: Int = defaultMaxTickCount(),
|
||||
onValueChangeFinished: (() -> Unit)? = null,
|
||||
colors: SliderColors = SliderDefaults.colors(),
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
val state = rememberSliderState(value.toFloat(), steps, valueRange.first.toFloat()..valueRange.last.toFloat())
|
||||
Slider(
|
||||
state = state,
|
||||
onValueChange = {
|
||||
onValueChange(it.fastRoundToInt())
|
||||
state.value = it
|
||||
},
|
||||
state = state.state,
|
||||
onValueChange = { onValueChange(it.fastRoundToInt()) },
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
onValueChangeFinished = onValueChangeFinished,
|
||||
colors = colors,
|
||||
interactionSource = interactionSource,
|
||||
track = { state ->
|
||||
@@ -48,7 +60,7 @@ fun Slider(
|
||||
trackCornerSize = Dp.Unspecified,
|
||||
enabled = enabled,
|
||||
colors = colors,
|
||||
drawTick = if (steps < maxTickCount) {
|
||||
drawTick = if (state.steps < maxTickCount) {
|
||||
{ offset, color ->
|
||||
drawStopIndicator(offset = offset, color = color, size = SliderDefaults.TickSize)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user