- Cloned from Aryan-Raj3112/episteme (AGPL-3.0) - Package: com.aryan.reader → org.dueattendant149.bookreader - Application ID: org.dueattendant149.bookreader - Added AGENTS.md with migration plan - Upstream: github.com/Aryan-Raj3112/episteme - Origin: git.dueattendant149.org/Atte149/book-reader
268 lines
10 KiB
Kotlin
268 lines
10 KiB
Kotlin
package org.dueattendant149.bookreader
|
|
|
|
import android.content.Context
|
|
import android.view.Window
|
|
import android.view.WindowManager
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.Row
|
|
import androidx.compose.foundation.layout.Spacer
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.height
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.layout.size
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.filled.Add
|
|
import androidx.compose.material.icons.filled.Remove
|
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
import androidx.compose.material3.HorizontalDivider
|
|
import androidx.compose.material3.Icon
|
|
import androidx.compose.material3.IconButton
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.ModalBottomSheet
|
|
import androidx.compose.material3.Switch
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.material3.TextButton
|
|
import androidx.compose.material3.rememberModalBottomSheetState
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.DisposableEffect
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.res.stringResource
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.core.content.edit
|
|
import org.dueattendant149.bookreader.shared.ui.ReaderMinimalSlider
|
|
import kotlin.math.roundToInt
|
|
|
|
private const val READER_PREFS_NAME = "reader_prefs"
|
|
private const val PREF_READER_BRIGHTNESS_USE_SYSTEM = "reader_brightness_use_system"
|
|
private const val PREF_READER_BRIGHTNESS_VALUE = "reader_brightness_value"
|
|
private const val DEFAULT_CUSTOM_BRIGHTNESS = 0.75f
|
|
private const val MIN_CUSTOM_BRIGHTNESS_PERCENT = 1
|
|
private const val MAX_CUSTOM_BRIGHTNESS_PERCENT = 100
|
|
private const val CUSTOM_BRIGHTNESS_STEP_PERCENT = 1
|
|
private const val MIN_CUSTOM_BRIGHTNESS = 0.01f
|
|
|
|
data class ReaderBrightnessSettings(
|
|
val useSystemBrightness: Boolean = true,
|
|
val customBrightness: Float = DEFAULT_CUSTOM_BRIGHTNESS
|
|
) {
|
|
val safeCustomBrightness: Float
|
|
get() = normalizeReaderBrightness(customBrightness)
|
|
}
|
|
|
|
internal fun normalizeReaderBrightness(brightness: Float): Float {
|
|
val percent = (brightness * 100f).roundToInt()
|
|
.coerceIn(MIN_CUSTOM_BRIGHTNESS_PERCENT, MAX_CUSTOM_BRIGHTNESS_PERCENT)
|
|
return percent / 100f
|
|
}
|
|
|
|
internal fun stepReaderBrightness(brightness: Float, percentDelta: Int): Float {
|
|
val currentPercent = (normalizeReaderBrightness(brightness) * 100f).roundToInt()
|
|
val nextPercent = (currentPercent + percentDelta)
|
|
.coerceIn(MIN_CUSTOM_BRIGHTNESS_PERCENT, MAX_CUSTOM_BRIGHTNESS_PERCENT)
|
|
return nextPercent / 100f
|
|
}
|
|
|
|
fun loadReaderBrightnessSettings(context: Context): ReaderBrightnessSettings {
|
|
val prefs = context.getSharedPreferences(READER_PREFS_NAME, Context.MODE_PRIVATE)
|
|
return ReaderBrightnessSettings(
|
|
useSystemBrightness = prefs.getBoolean(PREF_READER_BRIGHTNESS_USE_SYSTEM, true),
|
|
customBrightness = prefs.getFloat(PREF_READER_BRIGHTNESS_VALUE, DEFAULT_CUSTOM_BRIGHTNESS)
|
|
.let(::normalizeReaderBrightness)
|
|
)
|
|
}
|
|
|
|
fun saveReaderBrightnessSettings(context: Context, settings: ReaderBrightnessSettings) {
|
|
context.getSharedPreferences(READER_PREFS_NAME, Context.MODE_PRIVATE).edit {
|
|
putBoolean(PREF_READER_BRIGHTNESS_USE_SYSTEM, settings.useSystemBrightness)
|
|
putFloat(PREF_READER_BRIGHTNESS_VALUE, settings.safeCustomBrightness)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun ReaderBrightnessEffect(
|
|
window: Window?,
|
|
settings: ReaderBrightnessSettings
|
|
) {
|
|
DisposableEffect(window) {
|
|
val originalBrightness = window?.attributes?.screenBrightness
|
|
?: WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
|
onDispose {
|
|
window?.setReaderBrightness(originalBrightness)
|
|
}
|
|
}
|
|
|
|
LaunchedEffect(window, settings) {
|
|
val brightness = if (settings.useSystemBrightness) {
|
|
WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
|
} else {
|
|
settings.safeCustomBrightness
|
|
}
|
|
window?.setReaderBrightness(brightness)
|
|
}
|
|
}
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
@Composable
|
|
fun ReaderBrightnessSheet(
|
|
settings: ReaderBrightnessSettings,
|
|
onSettingsChange: (ReaderBrightnessSettings) -> Unit,
|
|
onDismiss: () -> Unit
|
|
) {
|
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
|
ModalBottomSheet(
|
|
onDismissRequest = onDismiss,
|
|
sheetState = sheetState
|
|
) {
|
|
Column(
|
|
modifier = Modifier
|
|
.fillMaxWidth()
|
|
.padding(horizontal = 24.dp, vertical = 8.dp)
|
|
.padding(bottom = 32.dp),
|
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
|
) {
|
|
Row(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.SpaceBetween
|
|
) {
|
|
Text(
|
|
text = stringResource(R.string.reader_brightness_title),
|
|
style = MaterialTheme.typography.titleLarge,
|
|
fontWeight = FontWeight.Bold
|
|
)
|
|
TextButton(onClick = onDismiss) {
|
|
Text(stringResource(R.string.action_done))
|
|
}
|
|
}
|
|
|
|
HorizontalDivider()
|
|
|
|
Row(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.SpaceBetween
|
|
) {
|
|
Column(modifier = Modifier.weight(1f)) {
|
|
Text(
|
|
text = stringResource(R.string.reader_brightness_system),
|
|
style = MaterialTheme.typography.titleMedium
|
|
)
|
|
Text(
|
|
text = stringResource(R.string.reader_brightness_system_desc),
|
|
style = MaterialTheme.typography.bodySmall,
|
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
)
|
|
}
|
|
Switch(
|
|
checked = settings.useSystemBrightness,
|
|
onCheckedChange = { useSystem ->
|
|
onSettingsChange(settings.copy(useSystemBrightness = useSystem))
|
|
}
|
|
)
|
|
}
|
|
|
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
Row(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.SpaceBetween
|
|
) {
|
|
Text(
|
|
text = stringResource(R.string.reader_brightness_custom),
|
|
style = MaterialTheme.typography.titleMedium
|
|
)
|
|
Text(
|
|
text = stringResource(
|
|
R.string.reader_brightness_percent,
|
|
(settings.safeCustomBrightness * 100f).roundToInt()
|
|
),
|
|
style = MaterialTheme.typography.labelLarge,
|
|
color = MaterialTheme.colorScheme.primary
|
|
)
|
|
}
|
|
ReaderBrightnessControl(
|
|
settings = settings,
|
|
onSettingsChange = onSettingsChange
|
|
)
|
|
Text(
|
|
text = stringResource(R.string.reader_brightness_custom_desc),
|
|
style = MaterialTheme.typography.bodySmall,
|
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
)
|
|
}
|
|
|
|
Spacer(Modifier.height(4.dp))
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun ReaderBrightnessControl(
|
|
settings: ReaderBrightnessSettings,
|
|
onSettingsChange: (ReaderBrightnessSettings) -> Unit
|
|
) {
|
|
val brightness = settings.safeCustomBrightness
|
|
val canDecrease = brightness > MIN_CUSTOM_BRIGHTNESS
|
|
val canIncrease = brightness < 1f
|
|
|
|
fun updateBrightness(value: Float) {
|
|
onSettingsChange(
|
|
settings.copy(
|
|
useSystemBrightness = false,
|
|
customBrightness = normalizeReaderBrightness(value)
|
|
)
|
|
)
|
|
}
|
|
|
|
Row(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
|
) {
|
|
IconButton(
|
|
onClick = {
|
|
updateBrightness(stepReaderBrightness(brightness, -CUSTOM_BRIGHTNESS_STEP_PERCENT))
|
|
},
|
|
enabled = canDecrease,
|
|
modifier = Modifier.size(36.dp)
|
|
) {
|
|
Icon(
|
|
imageVector = Icons.Default.Remove,
|
|
contentDescription = stringResource(R.string.content_desc_decrease),
|
|
modifier = Modifier.size(18.dp)
|
|
)
|
|
}
|
|
ReaderMinimalSlider(
|
|
value = brightness,
|
|
onValueChange = ::updateBrightness,
|
|
valueRange = MIN_CUSTOM_BRIGHTNESS..1f,
|
|
activeColor = MaterialTheme.colorScheme.primary,
|
|
inactiveColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
|
thumbColor = MaterialTheme.colorScheme.primary,
|
|
modifier = Modifier.weight(1f)
|
|
)
|
|
IconButton(
|
|
onClick = {
|
|
updateBrightness(stepReaderBrightness(brightness, CUSTOM_BRIGHTNESS_STEP_PERCENT))
|
|
},
|
|
enabled = canIncrease,
|
|
modifier = Modifier.size(36.dp)
|
|
) {
|
|
Icon(
|
|
imageVector = Icons.Default.Add,
|
|
contentDescription = stringResource(R.string.content_desc_increase),
|
|
modifier = Modifier.size(18.dp)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun Window.setReaderBrightness(brightness: Float) {
|
|
attributes = attributes.apply {
|
|
screenBrightness = brightness
|
|
}
|
|
}
|