mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #1300 from omnivore-app/feature/web-display-options-android
Web Prefs - Android
This commit is contained in:
commit
c77c570b05
8 changed files with 425 additions and 51 deletions
|
|
@ -17,8 +17,8 @@ android {
|
|||
applicationId "app.omnivore.omnivore"
|
||||
minSdk 23
|
||||
targetSdk 32
|
||||
versionCode 7
|
||||
versionName "0.0.7"
|
||||
versionCode 9
|
||||
versionName "0.0.9"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package app.omnivore.omnivore
|
||||
|
||||
import app.omnivore.omnivore.ui.reader.WebFont
|
||||
|
||||
object Constants {
|
||||
const val apiURL = BuildConfig.OMNIVORE_API_URL
|
||||
const val webURL = BuildConfig.OMNIVORE_WEB_URL
|
||||
|
|
@ -10,6 +12,11 @@ object DatastoreKeys {
|
|||
const val omnivoreAuthToken = "omnivoreAuthToken"
|
||||
const val omnivoreAuthCookieString = "omnivoreAuthCookieString"
|
||||
const val omnivorePendingUserToken = "omnivorePendingUserToken"
|
||||
const val preferredWebFontSize = "preferredWebFontSize"
|
||||
const val preferredWebLineHeight = "preferredWebLineHeight"
|
||||
const val preferredWebMaxWidthPercentage = "preferredWebMaxWidthPercentage"
|
||||
const val preferredWebFontFamily = "preferredWebFontFamily"
|
||||
const val prefersWebHighContrastText = "prefersWebHighContrastText"
|
||||
}
|
||||
|
||||
object AppleConstants {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
package app.omnivore.omnivore.ui.reader
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.Check
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import app.omnivore.omnivore.R
|
||||
|
||||
@Composable
|
||||
fun WebPreferencesDialog(onDismiss: () -> Unit, webReaderViewModel: WebReaderViewModel) {
|
||||
Dialog(onDismissRequest = { onDismiss() }) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
color = Color.White,
|
||||
modifier = Modifier
|
||||
.height(300.dp)
|
||||
) {
|
||||
WebPreferencesView(webReaderViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WebPreferencesView(webReaderViewModel: WebReaderViewModel) {
|
||||
val currentWebPreferences = webReaderViewModel.storedWebPreferences()
|
||||
val isFontListExpanded = remember { mutableStateOf(false) }
|
||||
val highContrastTextSwitchState = remember { mutableStateOf(currentWebPreferences.prefersHighContrastText) }
|
||||
val selectedWebFontRawValue = remember { mutableStateOf(currentWebPreferences.fontFamily.rawValue) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(top = 6.dp, start = 6.dp, end = 6.dp, bottom = 6.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 12.dp, bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text("Web Preferences")
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Stepper(
|
||||
label = "Font Size:",
|
||||
onIncrease = { webReaderViewModel.updateFontSize(isIncrease = true) },
|
||||
onDecrease = { webReaderViewModel.updateFontSize(isIncrease = false) }
|
||||
)
|
||||
|
||||
Stepper(
|
||||
label = "Margin:",
|
||||
onIncrease = { webReaderViewModel.updateMaxWidthPercentage(isIncrease = false) },
|
||||
onDecrease = { webReaderViewModel.updateMaxWidthPercentage(isIncrease = true) }
|
||||
)
|
||||
|
||||
Stepper(
|
||||
label = "Line Spacing:",
|
||||
onIncrease = { webReaderViewModel.updateLineSpacing(isIncrease = true) },
|
||||
onDecrease = { webReaderViewModel.updateLineSpacing(isIncrease = false) }
|
||||
)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("High Contrast Text")
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
Switch(
|
||||
checked = highContrastTextSwitchState.value,
|
||||
onCheckedChange = {
|
||||
highContrastTextSwitchState.value = it
|
||||
webReaderViewModel.updateHighContrastTextPreference(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clickable(onClick = { isFontListExpanded.value = !isFontListExpanded.value })
|
||||
) {
|
||||
Text("Font Family")
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
Icon(
|
||||
imageVector =
|
||||
if (isFontListExpanded.value)
|
||||
Icons.Filled.KeyboardArrowDown
|
||||
else
|
||||
Icons.Filled.KeyboardArrowRight,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
if (isFontListExpanded.value) {
|
||||
WebFont.values().forEach {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clickable(onClick = {
|
||||
webReaderViewModel.applyWebFont(it)
|
||||
selectedWebFontRawValue.value = it.rawValue
|
||||
})
|
||||
) {
|
||||
Text(
|
||||
it.displayText,
|
||||
modifier = Modifier
|
||||
.padding(top = 6.dp, start = 6.dp, end = 6.dp, bottom = 6.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
if (it.rawValue == selectedWebFontRawValue.value) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Check,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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 fontFamily: WebFont,
|
||||
val prefersHighContrastText: Boolean
|
||||
)
|
||||
|
|
@ -8,28 +8,115 @@ import android.view.*
|
|||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import app.omnivore.omnivore.R
|
||||
import com.google.gson.Gson
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
@Composable
|
||||
fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewModel) {
|
||||
var showWebPreferencesDialog by remember { mutableStateOf(false ) }
|
||||
|
||||
val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(null)
|
||||
val annotation: String? by webReaderViewModel.annotationLiveData.observeAsState(null)
|
||||
|
||||
val maxToolbarHeight = 48.dp
|
||||
val maxToolbarHeightPx = with(LocalDensity.current) { maxToolbarHeight.roundToPx().toFloat() }
|
||||
val toolbarHeightPx = remember { mutableStateOf(maxToolbarHeightPx) }
|
||||
|
||||
// Create a connection to the nested scroll system and listen to the scroll happening inside child Column
|
||||
val nestedScrollConnection = remember {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
val delta = available.y
|
||||
val newHeight = toolbarHeightPx.value + delta
|
||||
toolbarHeightPx.value = newHeight.coerceIn(0f, maxToolbarHeightPx)
|
||||
return Offset.Zero
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (webReaderParams == null) {
|
||||
webReaderViewModel.loadItem(slug = slug)
|
||||
}
|
||||
|
||||
if (webReaderParams != null) {
|
||||
WebReader(webReaderParams!!, webReaderViewModel)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(nestedScrollConnection)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(webReaderViewModel.scrollState)
|
||||
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.requiredHeight(height = maxToolbarHeight)
|
||||
) {
|
||||
}
|
||||
WebReader(webReaderParams!!, webReaderViewModel.storedWebPreferences(), webReaderViewModel)
|
||||
}
|
||||
|
||||
TopAppBar(
|
||||
modifier = Modifier
|
||||
.height(height = with(LocalDensity.current) {
|
||||
toolbarHeightPx.value.roundToInt().toDp()
|
||||
} ),
|
||||
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
title = {},
|
||||
actions = {
|
||||
IconButton(onClick = { showWebPreferencesDialog = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Settings,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (showWebPreferencesDialog) {
|
||||
WebPreferencesDialog(
|
||||
onDismiss = {
|
||||
showWebPreferencesDialog = false
|
||||
},
|
||||
webReaderViewModel = webReaderViewModel
|
||||
)
|
||||
}
|
||||
|
||||
if (annotation != null) {
|
||||
AnnotationEditView(
|
||||
initialAnnotation = annotation!!,
|
||||
onSave = {
|
||||
webReaderViewModel.saveAnnotation(it)
|
||||
},
|
||||
onCancel = {
|
||||
webReaderViewModel.cancelAnnotationEdit()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: add a proper loading view
|
||||
Text("Loading...")
|
||||
|
|
@ -38,23 +125,22 @@ fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewMod
|
|||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Composable
|
||||
fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) {
|
||||
// TODO: maybe handle cases where js can be queued up?
|
||||
val javascriptToExecute = remember { mutableStateOf<String?>(null) }
|
||||
|
||||
val annotation: String? by webReaderViewModel.annotationLiveData.observeAsState(null)
|
||||
fun WebReader(
|
||||
params: WebReaderParams,
|
||||
preferences: WebPreferences,
|
||||
webReaderViewModel: WebReaderViewModel
|
||||
) {
|
||||
val javascriptActionLoopUUID: UUID by webReaderViewModel
|
||||
.javascriptActionLoopUUIDLiveData
|
||||
.observeAsState(UUID.randomUUID())
|
||||
|
||||
WebView.setWebContentsDebuggingEnabled(true)
|
||||
|
||||
val webReaderContent = WebReaderContent(
|
||||
textFontSize = 12,
|
||||
lineHeight = 150,
|
||||
maxWidthPercentage = 100,
|
||||
preferences = preferences,
|
||||
item = params.item,
|
||||
themeKey = "LightGray",
|
||||
fontFamily = WebFont.SYSTEM ,
|
||||
articleContent = params.articleContent,
|
||||
prefersHighContrastText = false,
|
||||
)
|
||||
|
||||
val styledContent = webReaderContent.styledContent()
|
||||
|
|
@ -100,24 +186,14 @@ fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) {
|
|||
)
|
||||
}
|
||||
}, update = {
|
||||
if (javascriptToExecute.value != null) {
|
||||
it.evaluateJavascript(javascriptToExecute.value!!, null)
|
||||
if (javascriptActionLoopUUID != webReaderViewModel.lastJavascriptActionLoopUUID) {
|
||||
for (script in webReaderViewModel.javascriptDispatchQueue) {
|
||||
Log.d("js", "executing script: $script")
|
||||
it.evaluateJavascript(script, null)
|
||||
}
|
||||
webReaderViewModel.resetJavascriptDispatchQueue()
|
||||
}
|
||||
})
|
||||
|
||||
if (annotation != null) {
|
||||
AnnotationEditView(
|
||||
initialAnnotation = annotation!!,
|
||||
onSave = {
|
||||
val script = "var event = new Event('saveAnnotation');event.annotation = '$it';document.dispatchEvent(event);"
|
||||
javascriptToExecute.value = script
|
||||
webReaderViewModel.cancelAnnotationEdit()
|
||||
},
|
||||
onCancel = {
|
||||
webReaderViewModel.cancelAnnotationEdit()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package app.omnivore.omnivore.ui.reader
|
||||
|
||||
import android.util.Log
|
||||
import app.omnivore.omnivore.models.Highlight
|
||||
import app.omnivore.omnivore.models.LinkedItem
|
||||
import com.google.gson.Gson
|
||||
|
|
@ -15,7 +14,6 @@ enum class WebFont(val displayText: String, val rawValue: String) {
|
|||
ROBOTO("Roboto", "Roboto"),
|
||||
CRIMSON_TEXT("Crimson Text", "Crimson Text"),
|
||||
SOURCE_SERIF_PRO("Source Serif Pro", "Source Serif Pro"),
|
||||
Inter("Inter", "Inter"),
|
||||
}
|
||||
|
||||
enum class ArticleContentStatus(val rawValue: String) {
|
||||
|
|
@ -39,29 +37,28 @@ data class ArticleContent(
|
|||
}
|
||||
|
||||
data class WebReaderContent(
|
||||
val textFontSize: Int,
|
||||
val lineHeight: Int,
|
||||
val maxWidthPercentage: Int,
|
||||
val preferences: WebPreferences,
|
||||
val item: LinkedItem,
|
||||
val themeKey: String,
|
||||
val fontFamily: WebFont,
|
||||
val articleContent: ArticleContent,
|
||||
val prefersHighContrastText: Boolean
|
||||
) {
|
||||
fun styledContent(): String {
|
||||
// TODO: Kotlinize these three values (pasted from Swift)
|
||||
val savedAt = "new Date(1662571290735.0).toISOString()"
|
||||
val createdAt = "new Date().toISOString()"
|
||||
val publishedAt = "new Date().toISOString()" //if (item.publishDate != null) "new Date((item.publishDate!.timeIntervalSince1970 * 1000)).toISOString()" else "undefined"
|
||||
val publishedAt =
|
||||
"new Date().toISOString()" //if (item.publishDate != null) "new Date((item.publishDate!.timeIntervalSince1970 * 1000)).toISOString()" else "undefined"
|
||||
val textFontSize = preferences.textFontSize
|
||||
val highlightCssFilePath = "highlight${if (themeKey == "Gray") "-dark" else ""}.css"
|
||||
|
||||
val content = """
|
||||
return """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no' />
|
||||
<style>
|
||||
@import url("highlight${if (themeKey == "Gray") "-dark" else ""}.css");
|
||||
@import url("highlightCssFilePath");
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -96,11 +93,11 @@ data class WebReaderContent(
|
|||
}
|
||||
|
||||
window.fontSize = $textFontSize
|
||||
window.fontFamily = "${fontFamily.rawValue}"
|
||||
window.maxWidthPercentage = $maxWidthPercentage
|
||||
window.lineHeight = $lineHeight
|
||||
window.fontFamily = "${preferences.fontFamily.rawValue}"
|
||||
window.maxWidthPercentage = $preferences.maxWidthPercentage
|
||||
window.lineHeight = $preferences.lineHeight
|
||||
window.localStorage.setItem("theme", "$themeKey")
|
||||
window.prefersHighContrastFont = $prefersHighContrastText
|
||||
window.prefersHighContrastFont = $preferences.prefersHighContrastText
|
||||
window.enableHighlightBar = false
|
||||
</script>
|
||||
<script src="bundle.js"></script>
|
||||
|
|
@ -109,7 +106,5 @@ data class WebReaderContent(
|
|||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
package app.omnivore.omnivore.ui.reader
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.omnivore.omnivore.DatastoreKeys
|
||||
import app.omnivore.omnivore.DatastoreRepository
|
||||
import app.omnivore.omnivore.models.LinkedItem
|
||||
import app.omnivore.omnivore.networking.*
|
||||
import com.google.gson.Gson
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.json.JSONObject
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
data class WebReaderParams(
|
||||
|
|
@ -27,8 +31,13 @@ class WebReaderViewModel @Inject constructor(
|
|||
private val datastoreRepo: DatastoreRepository,
|
||||
private val networker: Networker
|
||||
): ViewModel() {
|
||||
var lastJavascriptActionLoopUUID: UUID = UUID.randomUUID()
|
||||
var javascriptDispatchQueue: MutableList<String> = mutableListOf()
|
||||
var scrollState = ScrollState(0)
|
||||
|
||||
val webReaderParamsLiveData = MutableLiveData<WebReaderParams?>(null)
|
||||
val annotationLiveData = MutableLiveData<String?>(null)
|
||||
val javascriptActionLoopUUIDLiveData = MutableLiveData(lastJavascriptActionLoopUUID)
|
||||
|
||||
fun loadItem(slug: String) {
|
||||
viewModelScope.launch {
|
||||
|
|
@ -90,9 +99,110 @@ class WebReaderViewModel @Inject constructor(
|
|||
fun reset() {
|
||||
webReaderParamsLiveData.value = null
|
||||
annotationLiveData.value = null
|
||||
scrollState = ScrollState(0)
|
||||
javascriptDispatchQueue = mutableListOf()
|
||||
}
|
||||
|
||||
fun resetJavascriptDispatchQueue() {
|
||||
lastJavascriptActionLoopUUID = javascriptActionLoopUUIDLiveData.value ?: UUID.randomUUID()
|
||||
javascriptDispatchQueue = mutableListOf()
|
||||
}
|
||||
|
||||
fun saveAnnotation(annotation: String) {
|
||||
val script = "var event = new Event('saveAnnotation');event.annotation = '$annotation';document.dispatchEvent(event);"
|
||||
enqueueScript(script)
|
||||
cancelAnnotationEdit()
|
||||
}
|
||||
|
||||
fun cancelAnnotationEdit() {
|
||||
annotationLiveData.value = null
|
||||
}
|
||||
|
||||
private fun enqueueScript(javascript: String) {
|
||||
javascriptDispatchQueue.add(javascript)
|
||||
javascriptActionLoopUUIDLiveData.value = UUID.randomUUID()
|
||||
}
|
||||
|
||||
fun storedWebPreferences(): WebPreferences = runBlocking {
|
||||
val storedFontSize = datastoreRepo.getInt(DatastoreKeys.preferredWebFontSize)
|
||||
val storedLineHeight = datastoreRepo.getInt(DatastoreKeys.preferredWebLineHeight)
|
||||
val storedMaxWidth = datastoreRepo.getInt(DatastoreKeys.preferredWebMaxWidthPercentage)
|
||||
|
||||
val storedFontFamily = datastoreRepo.getString(DatastoreKeys.preferredWebFontFamily) ?: WebFont.SYSTEM.rawValue
|
||||
val storedWebFont = WebFont.values().first { it.rawValue == storedFontFamily }
|
||||
|
||||
val prefersHighContrastFont = datastoreRepo.getString(DatastoreKeys.prefersWebHighContrastText) == "true"
|
||||
|
||||
WebPreferences(
|
||||
textFontSize = storedFontSize ?: 12,
|
||||
lineHeight = storedLineHeight ?: 150,
|
||||
maxWidthPercentage = storedMaxWidth ?: 100,
|
||||
themeKey = "LightGray", // TODO: match system value
|
||||
fontFamily = storedWebFont,
|
||||
prefersHighContrastText = prefersHighContrastFont
|
||||
)
|
||||
}
|
||||
|
||||
fun updateFontSize(isIncrease: Boolean) {
|
||||
val delta = if (isIncrease) 2 else -2
|
||||
var newFontSize: Int
|
||||
|
||||
runBlocking {
|
||||
val storedFontSize = datastoreRepo.getInt(DatastoreKeys.preferredWebFontSize)
|
||||
newFontSize = ((storedFontSize ?: 12) + delta).coerceIn(8, 28)
|
||||
datastoreRepo.putInt(DatastoreKeys.preferredWebFontSize, newFontSize)
|
||||
}
|
||||
|
||||
// Get value from data store and then update it
|
||||
val script = "var event = new Event('updateFontSize');event.fontSize = '$newFontSize';document.dispatchEvent(event);"
|
||||
enqueueScript(script)
|
||||
}
|
||||
|
||||
fun updateMaxWidthPercentage(isIncrease: Boolean) {
|
||||
val delta = if (isIncrease) 10 else -10
|
||||
var newMaxWidthPercentageValue: Int
|
||||
|
||||
runBlocking {
|
||||
val storedWidth = datastoreRepo.getInt(DatastoreKeys.preferredWebMaxWidthPercentage)
|
||||
newMaxWidthPercentageValue = ((storedWidth ?: 100) + delta).coerceIn(40, 100)
|
||||
datastoreRepo.putInt(DatastoreKeys.preferredWebMaxWidthPercentage, newMaxWidthPercentageValue)
|
||||
}
|
||||
|
||||
// Get value from data store and then update it
|
||||
val script = "var event = new Event('updateMaxWidthPercentage');event.maxWidthPercentage = '$newMaxWidthPercentageValue';document.dispatchEvent(event);"
|
||||
enqueueScript(script)
|
||||
}
|
||||
|
||||
fun updateLineSpacing(isIncrease: Boolean) {
|
||||
val delta = if (isIncrease) 25 else -25
|
||||
var newLineHeight: Int
|
||||
|
||||
runBlocking {
|
||||
val storedHeight = datastoreRepo.getInt(DatastoreKeys.preferredWebLineHeight)
|
||||
newLineHeight = ((storedHeight ?: 150) + delta).coerceIn(100, 300)
|
||||
datastoreRepo.putInt(DatastoreKeys.preferredWebLineHeight, newLineHeight)
|
||||
}
|
||||
|
||||
// Get value from data store and then update it
|
||||
val script = "var event = new Event('updateLineHeight');event.lineHeight = '$newLineHeight';document.dispatchEvent(event);"
|
||||
enqueueScript(script)
|
||||
}
|
||||
|
||||
fun updateHighContrastTextPreference(prefersHighContrastText: Boolean) {
|
||||
runBlocking {
|
||||
datastoreRepo.putString(DatastoreKeys.prefersWebHighContrastText, prefersHighContrastText.toString())
|
||||
}
|
||||
val fontContrastValue = if (prefersHighContrastText) "high" else "normal"
|
||||
val script = "var event = new Event('handleFontContrastChange');event.fontContrast = '$fontContrastValue';document.dispatchEvent(event);"
|
||||
enqueueScript(script)
|
||||
}
|
||||
|
||||
fun applyWebFont(font: WebFont) {
|
||||
runBlocking {
|
||||
datastoreRepo.putString(DatastoreKeys.preferredWebFontFamily, font.rawValue)
|
||||
}
|
||||
|
||||
val script = "var event = new Event('updateFontFamily');event.fontFamily = '${font.rawValue}';document.dispatchEvent(event);"
|
||||
enqueueScript(script)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
android/Omnivore/app/src/main/res/drawable-v24/minus.xml
Normal file
1
android/Omnivore/app/src/main/res/drawable-v24/minus.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000" android:pathData="M19,13H5V11H19V13Z"/></vector>
|
||||
1
android/Omnivore/app/src/main/res/drawable-v24/plus.xml
Normal file
1
android/Omnivore/app/src/main/res/drawable-v24/plus.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000" android:pathData="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"/></vector>
|
||||
Loading…
Reference in a new issue