Merge pull request #3880 from stefanosansone/fix/android-welcome-bottom-bar
Android - Fix bottom bar visibility and volume rocker state
This commit is contained in:
@ -27,8 +27,8 @@ android {
|
||||
applicationId = "app.omnivore.omnivore"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 2020000
|
||||
versionName = "0.202.0"
|
||||
versionCode = 2020001
|
||||
versionName = "0.202.1"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package app.omnivore.omnivore.core.datastore
|
||||
|
||||
// Keys
|
||||
const val omnivoreSelfHostedApiServer = "omnivoreSelfHostedAPIServer"
|
||||
const val omnivoreSelfHostedWebServer = "omnivoreSelfHostedWebServer"
|
||||
const val omnivoreAuthToken = "omnivoreAuthToken"
|
||||
@ -16,3 +17,4 @@ const val lastUsedSavedItemFilter = "lastUsedSavedItemFilter"
|
||||
const val lastUsedSavedItemSortFilter = "lastUsedSavedItemSortFilter"
|
||||
const val preferredTheme = "preferredTheme"
|
||||
const val followingTabActive = "followingTabActive"
|
||||
const val volumeForScroll = "volumeForScroll"
|
||||
@ -5,10 +5,8 @@ import android.widget.Toast
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.omnivore.omnivore.BuildConfig
|
||||
import app.omnivore.omnivore.R
|
||||
@ -78,7 +76,7 @@ class LoginViewModel @Inject constructor(
|
||||
var errorMessage by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
var hasValidUsername by mutableStateOf<Boolean>(false)
|
||||
var hasValidUsername by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var usernameValidationErrorMessage by mutableStateOf<String?>(null)
|
||||
@ -87,8 +85,12 @@ class LoginViewModel @Inject constructor(
|
||||
var pendingEmailUserCreds by mutableStateOf<PendingEmailUserCreds?>(null)
|
||||
private set
|
||||
|
||||
val hasAuthTokenLiveData: LiveData<Boolean> =
|
||||
datastoreRepository.hasAuthTokenFlow.distinctUntilChanged().asLiveData()
|
||||
val hasAuthTokenState: StateFlow<Boolean> =
|
||||
datastoreRepository.hasAuthTokenFlow.distinctUntilChanged().stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = true
|
||||
)
|
||||
|
||||
val registrationStateLiveData = MutableLiveData(RegistrationState.SocialLogin)
|
||||
|
||||
@ -96,7 +98,7 @@ class LoginViewModel @Inject constructor(
|
||||
followingTabActive
|
||||
).stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = true
|
||||
)
|
||||
|
||||
|
||||
@ -2,309 +2,289 @@ package app.omnivore.omnivore.feature.reader
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.core.designsystem.component.SwitchPreferenceWidget
|
||||
import app.omnivore.omnivore.feature.theme.OmnivoreTheme
|
||||
|
||||
@Composable
|
||||
fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
|
||||
val isDark = isSystemInDarkTheme()
|
||||
val currentWebPreferences = webReaderViewModel.storedWebPreferences(isDark)
|
||||
val isFontListExpanded = remember { mutableStateOf(false) }
|
||||
val highContrastTextSwitchState = remember { mutableStateOf(currentWebPreferences.prefersHighContrastText) }
|
||||
fun ReaderPreferencesView(
|
||||
webReaderViewModel: WebReaderViewModel
|
||||
) {
|
||||
val isDark = isSystemInDarkTheme()
|
||||
val currentWebPreferences = webReaderViewModel.storedWebPreferences(isDark)
|
||||
val isFontListExpanded = remember { mutableStateOf(false) }
|
||||
val highContrastTextSwitchState =
|
||||
remember { mutableStateOf(currentWebPreferences.prefersHighContrastText) }
|
||||
|
||||
val justifyTextSwitchState = remember { mutableStateOf(currentWebPreferences.prefersJustifyText) }
|
||||
val justifyTextSwitchState =
|
||||
remember { mutableStateOf(currentWebPreferences.prefersJustifyText) }
|
||||
|
||||
val selectedWebFontName = remember { mutableStateOf(currentWebPreferences.fontFamily.displayText) }
|
||||
val selectedWebFontName =
|
||||
remember { mutableStateOf(currentWebPreferences.fontFamily.displayText) }
|
||||
|
||||
var fontSizeSliderValue by remember { mutableStateOf(currentWebPreferences.textFontSize.toFloat()) }
|
||||
var marginSliderValue by remember { mutableStateOf(currentWebPreferences.maxWidthPercentage.toFloat()) }
|
||||
var lineSpacingSliderValue by remember { mutableStateOf(currentWebPreferences.lineHeight.toFloat()) }
|
||||
var fontSizeSliderValue by remember { mutableStateOf(currentWebPreferences.textFontSize.toFloat()) }
|
||||
var marginSliderValue by remember { mutableStateOf(currentWebPreferences.maxWidthPercentage.toFloat()) }
|
||||
var lineSpacingSliderValue by remember { mutableStateOf(currentWebPreferences.lineHeight.toFloat()) }
|
||||
|
||||
val themeState = remember { mutableStateOf(currentWebPreferences.storedThemePreference) }
|
||||
val themeState = remember { mutableStateOf(currentWebPreferences.storedThemePreference) }
|
||||
|
||||
val volumeForScrollState = remember { mutableStateOf(currentWebPreferences.shouldUseVolumeRockerForScroll) }
|
||||
|
||||
OmnivoreTheme {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 15.dp)
|
||||
.padding(vertical = 35.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 15.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text("Font", style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
))
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
Box {
|
||||
AssistChip(
|
||||
onClick = { isFontListExpanded.value = true },
|
||||
label = { Text(selectedWebFontName.value, color = Color(red = 137, green = 137, blue = 137)) },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
Icons.Default.ArrowDropDown,
|
||||
contentDescription = "Choose the Reader font",
|
||||
tint = Color(red = 137, green = 137, blue = 137)
|
||||
)
|
||||
},
|
||||
)
|
||||
if (isFontListExpanded.value) {
|
||||
DropdownMenu(
|
||||
expanded = isFontListExpanded.value,
|
||||
onDismissRequest = { isFontListExpanded.value = false },
|
||||
) {
|
||||
WebFont.values().forEach {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(it.displayText, style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
))
|
||||
},
|
||||
onClick = {
|
||||
webReaderViewModel.applyWebFont(it)
|
||||
selectedWebFontName.value = it.displayText
|
||||
isFontListExpanded.value = false
|
||||
},
|
||||
)
|
||||
val volumeForScrollState by webReaderViewModel.volumeRockerForScrollState.collectAsStateWithLifecycle()
|
||||
|
||||
OmnivoreTheme {
|
||||
// Temporary wrapping for margin while migrating components to design system
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 35.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 15.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 15.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
"Font", style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
Box {
|
||||
AssistChip(
|
||||
onClick = { isFontListExpanded.value = true },
|
||||
label = {
|
||||
Text(
|
||||
selectedWebFontName.value,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
Icons.Default.ArrowDropDown,
|
||||
contentDescription = "Choose the Reader font",
|
||||
tint = Color(red = 137, green = 137, blue = 137)
|
||||
)
|
||||
},
|
||||
)
|
||||
if (isFontListExpanded.value) {
|
||||
DropdownMenu(
|
||||
expanded = isFontListExpanded.value,
|
||||
onDismissRequest = { isFontListExpanded.value = false },
|
||||
) {
|
||||
WebFont.entries.forEach {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(
|
||||
it.displayText, style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
)
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
webReaderViewModel.applyWebFont(it)
|
||||
selectedWebFontName.value = it.displayText
|
||||
isFontListExpanded.value = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
stringResource(R.string.reader_preferences_view_font_size), style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
)
|
||||
)
|
||||
Slider(
|
||||
value = fontSizeSliderValue,
|
||||
onValueChange = {
|
||||
fontSizeSliderValue = it
|
||||
webReaderViewModel.setFontSize(it.toInt())
|
||||
},
|
||||
steps = 40,
|
||||
valueRange = 10f..50f,
|
||||
)
|
||||
|
||||
Text(
|
||||
stringResource(R.string.reader_preferences_view_margin), style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
)
|
||||
)
|
||||
Slider(
|
||||
value = marginSliderValue,
|
||||
onValueChange = {
|
||||
marginSliderValue = it
|
||||
webReaderViewModel.setMaxWidthPercentage(it.toInt())
|
||||
},
|
||||
steps = 40,
|
||||
valueRange = 60f..100f,
|
||||
)
|
||||
|
||||
Text(
|
||||
stringResource(R.string.reader_preferences_view_line_spacing), style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
)
|
||||
)
|
||||
Slider(
|
||||
value = lineSpacingSliderValue,
|
||||
onValueChange = {
|
||||
lineSpacingSliderValue = it
|
||||
webReaderViewModel.setLineHeight(it.toInt())
|
||||
},
|
||||
steps = 50,
|
||||
valueRange = 100f..300f,
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.reader_preferences_view_theme), style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
Text(
|
||||
stringResource(R.string.reader_preferences_view_auto), style = TextStyle(
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
)
|
||||
)
|
||||
Checkbox(checked = themeState.value == "System", onCheckedChange = {
|
||||
if (it) {
|
||||
themeState.value = "System"
|
||||
webReaderViewModel.updateStoredThemePreference("System")
|
||||
} else {
|
||||
val newThemeKey = if (isDark) "Black" else "Light"
|
||||
themeState.value = newThemeKey
|
||||
webReaderViewModel.updateStoredThemePreference(newThemeKey)
|
||||
}
|
||||
})
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
) {
|
||||
for (theme in Themes.entries) {
|
||||
if (theme.themeKey != "System") {
|
||||
val isSelected = theme.themeKey == themeState.value
|
||||
Button(
|
||||
onClick = {
|
||||
themeState.value = theme.themeKey
|
||||
webReaderViewModel.updateStoredThemePreference(theme.themeKey)
|
||||
},
|
||||
shape = CircleShape,
|
||||
border = BorderStroke(
|
||||
3.dp,
|
||||
if (isSelected) colorResource(R.color.cta_yellow) else Color.Transparent
|
||||
),
|
||||
modifier = Modifier.size(35.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
|
||||
containerColor = Color(theme.backgroundColor)
|
||||
)
|
||||
) {
|
||||
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(0.1F))
|
||||
}
|
||||
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(2.0F))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(stringResource(R.string.reader_preferences_view_font_size), style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
))
|
||||
Slider(
|
||||
value = fontSizeSliderValue,
|
||||
onValueChange = {
|
||||
fontSizeSliderValue = it
|
||||
webReaderViewModel.setFontSize(it.toInt())
|
||||
},
|
||||
steps = 40,
|
||||
valueRange = 10f..50f,
|
||||
)
|
||||
|
||||
Text(stringResource(R.string.reader_preferences_view_margin), style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
))
|
||||
Slider(
|
||||
value = marginSliderValue,
|
||||
onValueChange = {
|
||||
marginSliderValue = it
|
||||
webReaderViewModel.setMaxWidthPercentage(it.toInt())
|
||||
},
|
||||
steps = 40,
|
||||
valueRange = 60f..100f,
|
||||
)
|
||||
|
||||
Text(stringResource(R.string.reader_preferences_view_line_spacing), style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
))
|
||||
Slider(
|
||||
value = lineSpacingSliderValue,
|
||||
onValueChange = {
|
||||
lineSpacingSliderValue = it
|
||||
webReaderViewModel.setLineHeight(it.toInt())
|
||||
},
|
||||
steps = 50,
|
||||
valueRange = 100f..300f,
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(vertical = 4.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.reader_preferences_view_theme), style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
))
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
Text(stringResource(R.string.reader_preferences_view_auto), style = TextStyle(
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
))
|
||||
Checkbox(
|
||||
checked = themeState.value == "System",
|
||||
onCheckedChange = {
|
||||
if (it) {
|
||||
themeState.value = "System"
|
||||
webReaderViewModel.updateStoredThemePreference("System")
|
||||
} else {
|
||||
val newThemeKey = if (isDark) "Black" else "Light"
|
||||
themeState.value = newThemeKey
|
||||
webReaderViewModel.updateStoredThemePreference(newThemeKey)
|
||||
}
|
||||
})
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
) {
|
||||
for(theme in Themes.values()) {
|
||||
if (theme.themeKey != "System") {
|
||||
val isSelected = theme.themeKey == themeState.value
|
||||
Button(
|
||||
onClick = {
|
||||
themeState.value = theme.themeKey
|
||||
webReaderViewModel.updateStoredThemePreference(theme.themeKey)
|
||||
},
|
||||
shape = CircleShape,
|
||||
border = BorderStroke(3.dp, if (isSelected) colorResource(R.color.cta_yellow) else Color.Transparent),
|
||||
modifier = Modifier.size(35.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
|
||||
containerColor = Color(theme.backgroundColor)
|
||||
// TODO : Use state flow
|
||||
SwitchPreferenceWidget(
|
||||
title = stringResource(R.string.reader_preferences_view_high_constrast_text),
|
||||
checked = highContrastTextSwitchState.value,
|
||||
onCheckedChanged = {
|
||||
highContrastTextSwitchState.value = it
|
||||
webReaderViewModel.updateHighContrastTextPreference(it)
|
||||
},
|
||||
)
|
||||
// TODO : Use state flow
|
||||
SwitchPreferenceWidget(
|
||||
title = stringResource(R.string.reader_preferences_view_justify_text),
|
||||
checked = justifyTextSwitchState.value,
|
||||
onCheckedChanged = {
|
||||
justifyTextSwitchState.value = it
|
||||
webReaderViewModel.updateJustifyText(it)
|
||||
},
|
||||
)
|
||||
SwitchPreferenceWidget(
|
||||
title = stringResource(R.string.reader_preferences_view_volume_scroll),
|
||||
checked = volumeForScrollState,
|
||||
onCheckedChanged = { webReaderViewModel.setVolumeRockerForScrollState(it) },
|
||||
)
|
||||
) {
|
||||
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(0.1F))
|
||||
}
|
||||
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(2.0F))
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
stringResource(R.string.reader_preferences_view_high_constrast_text),
|
||||
style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137)
|
||||
))
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
Switch(
|
||||
checked = highContrastTextSwitchState.value,
|
||||
onCheckedChange = {
|
||||
highContrastTextSwitchState.value = it
|
||||
webReaderViewModel.updateHighContrastTextPreference(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
stringResource(R.string.reader_preferences_view_justify_text),
|
||||
style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137))
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
Switch(
|
||||
checked = justifyTextSwitchState.value,
|
||||
onCheckedChange = {
|
||||
justifyTextSwitchState.value = it
|
||||
webReaderViewModel.updateJustifyText(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
stringResource(R.string.reader_preferences_view_volume_scroll),
|
||||
style = TextStyle(
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(red = 137, green = 137, blue = 137))
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
Switch (
|
||||
checked = volumeForScrollState.value,
|
||||
onCheckedChange = {
|
||||
volumeForScrollState.value = it
|
||||
webReaderViewModel.updateVolumeRockerForScroll(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Stepper(label: String, onIncrease: () -> Unit, onDecrease: () -> Unit) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = label,
|
||||
modifier = Modifier
|
||||
.padding(bottom = 6.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
|
||||
IconButton(onClick = { onDecrease() }) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.minus),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
Divider(
|
||||
color = Color.Black,
|
||||
modifier = Modifier
|
||||
.height(20.dp)
|
||||
.width(1.dp)
|
||||
)
|
||||
|
||||
IconButton(onClick = { onIncrease() }) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.plus),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class WebPreferences(
|
||||
val textFontSize: Int,
|
||||
val lineHeight: Int,
|
||||
val maxWidthPercentage: Int,
|
||||
val themeKey: String,
|
||||
val storedThemePreference: String,
|
||||
val fontFamily: WebFont,
|
||||
val prefersHighContrastText: Boolean,
|
||||
val prefersJustifyText: Boolean,
|
||||
var shouldUseVolumeRockerForScroll: Boolean
|
||||
val textFontSize: Int,
|
||||
val lineHeight: Int,
|
||||
val maxWidthPercentage: Int,
|
||||
val themeKey: String,
|
||||
val storedThemePreference: String,
|
||||
val fontFamily: WebFont,
|
||||
val prefersHighContrastText: Boolean,
|
||||
val prefersJustifyText: Boolean
|
||||
)
|
||||
|
||||
@ -22,6 +22,7 @@ import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.omnivore.omnivore.R
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -32,7 +33,9 @@ import java.util.*
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Composable
|
||||
fun WebReader(
|
||||
styledContent: String, webReaderViewModel: WebReaderViewModel, currentTheme: Themes?
|
||||
styledContent: String,
|
||||
webReaderViewModel: WebReaderViewModel,
|
||||
currentTheme: Themes?
|
||||
) {
|
||||
val javascriptActionLoopUUID: UUID by webReaderViewModel.javascriptActionLoopUUIDLiveData.observeAsState(
|
||||
UUID.randomUUID()
|
||||
@ -41,6 +44,8 @@ fun WebReader(
|
||||
|
||||
WebView.setWebContentsDebuggingEnabled(true)
|
||||
|
||||
val volumeForScrollState by webReaderViewModel.volumeRockerForScrollState.collectAsStateWithLifecycle()
|
||||
|
||||
Box {
|
||||
AndroidView(factory = {
|
||||
OmnivoreWebView(it).apply {
|
||||
@ -170,7 +175,7 @@ fun WebReader(
|
||||
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
if (!webReaderViewModel.shouldUseVolumeRockerForScroll) {
|
||||
if (!volumeForScrollState) {
|
||||
return@setOnKeyListener false
|
||||
}
|
||||
scrollVertically(OmnivoreWebView.Direction.UP)
|
||||
@ -178,7 +183,7 @@ fun WebReader(
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
if (!webReaderViewModel.shouldUseVolumeRockerForScroll) {
|
||||
if (!volumeForScrollState) {
|
||||
return@setOnKeyListener false
|
||||
}
|
||||
scrollVertically(OmnivoreWebView.Direction.DOWN)
|
||||
|
||||
@ -28,6 +28,7 @@ import app.omnivore.omnivore.core.database.dao.SavedItemDao
|
||||
import app.omnivore.omnivore.core.database.entities.SavedItem
|
||||
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
|
||||
import app.omnivore.omnivore.core.datastore.DatastoreRepository
|
||||
import app.omnivore.omnivore.core.datastore.followingTabActive
|
||||
import app.omnivore.omnivore.core.datastore.preferredTheme
|
||||
import app.omnivore.omnivore.core.datastore.preferredWebFontFamily
|
||||
import app.omnivore.omnivore.core.datastore.preferredWebFontSize
|
||||
@ -35,6 +36,7 @@ import app.omnivore.omnivore.core.datastore.preferredWebLineHeight
|
||||
import app.omnivore.omnivore.core.datastore.preferredWebMaxWidthPercentage
|
||||
import app.omnivore.omnivore.core.datastore.prefersJustifyText
|
||||
import app.omnivore.omnivore.core.datastore.prefersWebHighContrastText
|
||||
import app.omnivore.omnivore.core.datastore.volumeForScroll
|
||||
import app.omnivore.omnivore.core.network.Networker
|
||||
import app.omnivore.omnivore.core.network.createNewLabel
|
||||
import app.omnivore.omnivore.core.network.saveUrl
|
||||
@ -42,14 +44,16 @@ import app.omnivore.omnivore.core.network.savedItem
|
||||
import app.omnivore.omnivore.feature.components.HighlightColor
|
||||
import app.omnivore.omnivore.feature.library.SavedItemAction
|
||||
import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput
|
||||
import app.omnivore.omnivore.utils.DatastoreKeys
|
||||
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
|
||||
import com.google.gson.Gson
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -83,7 +87,7 @@ enum class Themes(
|
||||
|
||||
@HiltViewModel
|
||||
class WebReaderViewModel @Inject constructor(
|
||||
private val datastoreRepo: DatastoreRepository,
|
||||
private val datastoreRepository: DatastoreRepository,
|
||||
private val dataService: DataService,
|
||||
private val networker: Networker,
|
||||
private val eventTracker: EventTracker,
|
||||
@ -457,28 +461,27 @@ class WebReaderViewModel @Inject constructor(
|
||||
javascriptActionLoopUUIDLiveData.value = UUID.randomUUID()
|
||||
}
|
||||
|
||||
val currentThemeKey: LiveData<String> = datastoreRepo
|
||||
val currentThemeKey: LiveData<String> = datastoreRepository
|
||||
.themeKeyFlow
|
||||
.distinctUntilChanged()
|
||||
.asLiveData()
|
||||
|
||||
fun storedWebPreferences(isDarkMode: Boolean): WebPreferences = runBlocking {
|
||||
val storedFontSize = datastoreRepo.getInt(preferredWebFontSize)
|
||||
val storedLineHeight = datastoreRepo.getInt(preferredWebLineHeight)
|
||||
val storedMaxWidth = datastoreRepo.getInt(preferredWebMaxWidthPercentage)
|
||||
val storedFontSize = datastoreRepository.getInt(preferredWebFontSize)
|
||||
val storedLineHeight = datastoreRepository.getInt(preferredWebLineHeight)
|
||||
val storedMaxWidth = datastoreRepository.getInt(preferredWebMaxWidthPercentage)
|
||||
|
||||
val storedFontFamily =
|
||||
datastoreRepo.getString(preferredWebFontFamily) ?: WebFont.SYSTEM.rawValue
|
||||
datastoreRepository.getString(preferredWebFontFamily) ?: WebFont.SYSTEM.rawValue
|
||||
val storedThemePreference =
|
||||
datastoreRepo.getString(preferredTheme) ?: "System"
|
||||
datastoreRepository.getString(preferredTheme) ?: "System"
|
||||
val storedWebFont =
|
||||
WebFont.entries.firstOrNull { it.rawValue == storedFontFamily } ?: WebFont.entries
|
||||
.first()
|
||||
|
||||
val prefersHighContrastFont =
|
||||
datastoreRepo.getString(prefersWebHighContrastText) == "true"
|
||||
val prefersJustifyText = datastoreRepo.getString(DatastoreKeys.prefersJustifyText) == "true"
|
||||
val shouldUseVolumeRockerForScroll = datastoreRepo.getString(DatastoreKeys.volumeForScroll) != "false"
|
||||
datastoreRepository.getString(prefersWebHighContrastText) == "true"
|
||||
val prefersJustifyText = datastoreRepository.getString(prefersJustifyText) == "true"
|
||||
|
||||
WebPreferences(
|
||||
textFontSize = storedFontSize ?: 12,
|
||||
@ -488,8 +491,7 @@ class WebReaderViewModel @Inject constructor(
|
||||
storedThemePreference = storedThemePreference,
|
||||
fontFamily = storedWebFont,
|
||||
prefersHighContrastText = prefersHighContrastFont,
|
||||
prefersJustifyText = prefersJustifyText,
|
||||
shouldUseVolumeRockerForScroll = shouldUseVolumeRockerForScroll
|
||||
prefersJustifyText = prefersJustifyText
|
||||
)
|
||||
}
|
||||
|
||||
@ -505,7 +507,7 @@ class WebReaderViewModel @Inject constructor(
|
||||
Log.d("theme", "Setting theme key: $newThemeKey")
|
||||
|
||||
runBlocking {
|
||||
datastoreRepo.putString(preferredTheme, newThemeKey)
|
||||
datastoreRepository.putString(preferredTheme, newThemeKey)
|
||||
}
|
||||
|
||||
val script =
|
||||
@ -515,7 +517,7 @@ class WebReaderViewModel @Inject constructor(
|
||||
|
||||
fun setFontSize(newFontSize: Int) {
|
||||
runBlocking {
|
||||
datastoreRepo.putInt(preferredWebFontSize, newFontSize)
|
||||
datastoreRepository.putInt(preferredWebFontSize, newFontSize)
|
||||
}
|
||||
val script =
|
||||
"var event = new Event('updateFontSize');event.fontSize = '$newFontSize';document.dispatchEvent(event);"
|
||||
@ -524,7 +526,7 @@ class WebReaderViewModel @Inject constructor(
|
||||
|
||||
fun setMaxWidthPercentage(newMaxWidthPercentageValue: Int) {
|
||||
runBlocking {
|
||||
datastoreRepo.putInt(
|
||||
datastoreRepository.putInt(
|
||||
preferredWebMaxWidthPercentage,
|
||||
newMaxWidthPercentageValue
|
||||
)
|
||||
@ -536,7 +538,7 @@ class WebReaderViewModel @Inject constructor(
|
||||
|
||||
fun setLineHeight(newLineHeight: Int) {
|
||||
runBlocking {
|
||||
datastoreRepo.putInt(preferredWebLineHeight, newLineHeight)
|
||||
datastoreRepository.putInt(preferredWebLineHeight, newLineHeight)
|
||||
}
|
||||
val script =
|
||||
"var event = new Event('updateLineHeight');event.lineHeight = '$newLineHeight';document.dispatchEvent(event);"
|
||||
@ -545,7 +547,7 @@ class WebReaderViewModel @Inject constructor(
|
||||
|
||||
fun updateHighContrastTextPreference(prefersHighContrastText: Boolean) {
|
||||
runBlocking {
|
||||
datastoreRepo.putString(
|
||||
datastoreRepository.putString(
|
||||
prefersWebHighContrastText,
|
||||
prefersHighContrastText.toString()
|
||||
)
|
||||
@ -558,23 +560,30 @@ class WebReaderViewModel @Inject constructor(
|
||||
|
||||
fun updateJustifyText(justifyText: Boolean) {
|
||||
runBlocking {
|
||||
datastoreRepo.putString(prefersJustifyText, justifyText.toString())
|
||||
datastoreRepository.putString(prefersJustifyText, justifyText.toString())
|
||||
}
|
||||
val script =
|
||||
"var event = new Event('updateJustifyText');event.justifyText = $justifyText;document.dispatchEvent(event);"
|
||||
enqueueScript(script)
|
||||
}
|
||||
|
||||
fun updateVolumeRockerForScroll(shouldUseVolumeRockerForScroll: Boolean) {
|
||||
runBlocking {
|
||||
datastoreRepo.putString(DatastoreKeys.volumeForScroll, shouldUseVolumeRockerForScroll.toString())
|
||||
val volumeRockerForScrollState: StateFlow<Boolean> = datastoreRepository.getBoolean(
|
||||
volumeForScroll
|
||||
).stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = false
|
||||
)
|
||||
|
||||
fun setVolumeRockerForScrollState(value: Boolean) {
|
||||
viewModelScope.launch {
|
||||
datastoreRepository.putBoolean(volumeForScroll, value)
|
||||
}
|
||||
this.shouldUseVolumeRockerForScroll = shouldUseVolumeRockerForScroll
|
||||
}
|
||||
|
||||
fun applyWebFont(font: WebFont) {
|
||||
runBlocking {
|
||||
datastoreRepo.putString(preferredWebFontFamily, font.rawValue)
|
||||
datastoreRepository.putString(preferredWebFontFamily, font.rawValue)
|
||||
}
|
||||
|
||||
val script =
|
||||
|
||||
@ -22,7 +22,6 @@ import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@ -59,10 +58,12 @@ import app.omnivore.omnivore.navigation.TopLevelDestination
|
||||
fun RootView(
|
||||
loginViewModel: LoginViewModel = hiltViewModel()
|
||||
) {
|
||||
val hasAuthToken: Boolean by loginViewModel.hasAuthTokenLiveData.observeAsState(false)
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val navController = rememberNavController()
|
||||
|
||||
val followingTabActive by loginViewModel.followingTabActiveState.collectAsStateWithLifecycle()
|
||||
val hasAuthToken by loginViewModel.hasAuthTokenState.collectAsStateWithLifecycle()
|
||||
|
||||
val destinations = if (followingTabActive) {
|
||||
TopLevelDestination.entries
|
||||
@ -90,15 +91,13 @@ fun RootView(
|
||||
),
|
||||
)
|
||||
) {
|
||||
if (hasAuthToken) {
|
||||
PrimaryNavigator(
|
||||
navController = navController,
|
||||
snackbarHostState = snackbarHostState
|
||||
)
|
||||
} else {
|
||||
WelcomeScreen(viewModel = loginViewModel)
|
||||
}
|
||||
|
||||
val startDestination = if (hasAuthToken) Routes.Home.route else Routes.Welcome.route
|
||||
PrimaryNavigator(
|
||||
navController = navController,
|
||||
snackbarHostState = snackbarHostState,
|
||||
startDestination = startDestination,
|
||||
loginViewModel = loginViewModel
|
||||
)
|
||||
DisposableEffect(hasAuthToken) {
|
||||
if (hasAuthToken) {
|
||||
loginViewModel.registerUser()
|
||||
@ -114,16 +113,22 @@ private const val INITIAL_OFFSET_FACTOR = 0.10f
|
||||
@Composable
|
||||
fun PrimaryNavigator(
|
||||
navController: NavHostController,
|
||||
snackbarHostState: SnackbarHostState
|
||||
snackbarHostState: SnackbarHostState,
|
||||
startDestination: String,
|
||||
loginViewModel: LoginViewModel
|
||||
) {
|
||||
|
||||
NavHost(navController = navController,
|
||||
startDestination = Routes.Home.route,
|
||||
startDestination = startDestination,
|
||||
enterTransition = { materialSharedAxisXIn(initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }) },
|
||||
exitTransition = { materialSharedAxisXOut(targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }) },
|
||||
popEnterTransition = { materialSharedAxisXIn(initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }) },
|
||||
popExitTransition = { materialSharedAxisXOut(targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }) }) {
|
||||
|
||||
composable(Routes.Welcome.route) {
|
||||
WelcomeScreen(viewModel = loginViewModel)
|
||||
}
|
||||
|
||||
navigation(startDestination = Routes.Inbox.route,
|
||||
route = Routes.Home.route,
|
||||
enterTransition = { EnterTransition.None },
|
||||
|
||||
@ -2,6 +2,7 @@ package app.omnivore.omnivore.navigation
|
||||
|
||||
sealed class Routes(val route: String) {
|
||||
data object Home : Routes("Home")
|
||||
data object Welcome : Routes("Welcome")
|
||||
data object Following : Routes("Following")
|
||||
data object Inbox : Routes("Inbox")
|
||||
data object Settings : Routes("Settings")
|
||||
|
||||
@ -7,25 +7,6 @@ object Constants {
|
||||
const val dataStoreName = "omnivore-datastore"
|
||||
}
|
||||
|
||||
object DatastoreKeys {
|
||||
const val omnivoreSelfHostedAPIServer = "omnivoreSelfHostedAPIServer"
|
||||
const val omnivoreSelfHostedWebServer = "omnivoreSelfHostedWebServer"
|
||||
const val omnivoreAuthToken = "omnivoreAuthToken"
|
||||
const val omnivoreAuthCookieString = "omnivoreAuthCookieString"
|
||||
const val omnivorePendingUserToken = "omnivorePendingUserToken"
|
||||
const val libraryLastSyncTimestamp = "libraryLastSyncTimestamp"
|
||||
const val preferredWebFontSize = "preferredWebFontSize"
|
||||
const val preferredWebLineHeight = "preferredWebLineHeight"
|
||||
const val preferredWebMaxWidthPercentage = "preferredWebMaxWidthPercentage"
|
||||
const val preferredWebFontFamily = "preferredWebFontFamily"
|
||||
const val prefersWebHighContrastText = "prefersWebHighContrastText"
|
||||
const val prefersJustifyText = "prefersJustifyText"
|
||||
const val lastUsedSavedItemFilter = "lastUsedSavedItemFilter"
|
||||
const val lastUsedSavedItemSortFilter = "lastUsedSavedItemSortFilter"
|
||||
const val preferredTheme = "preferredTheme"
|
||||
const val volumeForScroll = "volumeForScroll"
|
||||
}
|
||||
|
||||
object AppleConstants {
|
||||
const val clientId = "app.omnivore"
|
||||
const val redirectURI = BuildConfig.OMNIVORE_API_URL + "/api/mobile-auth/android-apple-redirect"
|
||||
|
||||
Reference in New Issue
Block a user