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:
Jackson Harper
2024-05-01 13:28:59 +08:00
committed by GitHub
9 changed files with 325 additions and 340 deletions

View File

@ -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 {

View File

@ -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"

View File

@ -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
)

View File

@ -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
)

View File

@ -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)

View File

@ -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 =

View File

@ -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 },

View File

@ -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")

View File

@ -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"