Merge pull request #4100 from stefanosansone/feature/android-rtl-text
Android - Right-to-left text support
This commit is contained in:
@ -18,3 +18,4 @@ const val lastUsedSavedItemSortFilter = "lastUsedSavedItemSortFilter"
|
||||
const val preferredTheme = "preferredTheme"
|
||||
const val followingTabActive = "followingTabActive"
|
||||
const val volumeForScroll = "volumeForScroll"
|
||||
const val rtlText = "rtlText"
|
||||
|
||||
@ -473,11 +473,6 @@ fun LibraryViewContent(
|
||||
val intent = Intent(context, activityClass)
|
||||
intent.putExtra("SAVED_ITEM_SLUG", currentItem.slug)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
actionHandler = {
|
||||
onSavedItemAction(
|
||||
currentItem.savedItemId, it
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
@ -214,12 +214,6 @@ fun SearchViewContent(viewModel: SearchViewModel, modifier: Modifier) {
|
||||
val intent = Intent(context, activityClass)
|
||||
intent.putExtra("SAVED_ITEM_SLUG", cardDataWithLabels.savedItem.slug)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
actionHandler = {
|
||||
viewModel.handleSavedItemAction(
|
||||
cardDataWithLabels.savedItem.savedItemId,
|
||||
it
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ import app.omnivore.omnivore.feature.components.SliderWithPlusMinus
|
||||
import app.omnivore.omnivore.feature.theme.OmnivoreTheme
|
||||
|
||||
@Composable
|
||||
fun ReaderPreferencesView(
|
||||
fun ReaderPreferencesSheet(
|
||||
webReaderViewModel: WebReaderViewModel
|
||||
) {
|
||||
val isDark = isSystemInDarkTheme()
|
||||
@ -74,6 +74,8 @@ fun ReaderPreferencesView(
|
||||
|
||||
val volumeForScrollState by webReaderViewModel.volumeRockerForScrollState.collectAsStateWithLifecycle()
|
||||
|
||||
val rtlTextState by webReaderViewModel.rtlTextState.collectAsStateWithLifecycle()
|
||||
|
||||
OmnivoreTheme {
|
||||
// Temporary wrapping for margin while migrating components to design system
|
||||
Column(
|
||||
@ -282,6 +284,11 @@ fun ReaderPreferencesView(
|
||||
checked = volumeForScrollState,
|
||||
onCheckedChanged = { webReaderViewModel.setVolumeRockerForScrollState(it) },
|
||||
)
|
||||
SwitchPreferenceWidget(
|
||||
title = stringResource(R.string.reader_preferences_view_use_rtl),
|
||||
checked = rtlTextState,
|
||||
onCheckedChanged = { webReaderViewModel.setRtlTextState(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -168,6 +168,7 @@ fun WebReader(
|
||||
"utf-8",
|
||||
null
|
||||
)
|
||||
Log.d("HTMLContent", styledContent)
|
||||
requestFocus()
|
||||
setOnKeyListener { _, keyCode, event ->
|
||||
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||
|
||||
@ -7,32 +7,25 @@ import com.google.gson.Gson
|
||||
|
||||
enum class WebFont(val displayText: String, val rawValue: String) {
|
||||
INTER("Inter", "Inter"), SYSTEM("System Default", "system-ui"), OPEN_DYSLEXIC(
|
||||
"Open Dyslexic",
|
||||
"OpenDyslexic"
|
||||
"Open Dyslexic", "OpenDyslexic"
|
||||
),
|
||||
MERRIWEATHER("Merriweather", "Merriweather"), LORA("Lora", "Lora"), OPEN_SANS(
|
||||
"Open Sans",
|
||||
"Open Sans"
|
||||
"Open Sans", "Open Sans"
|
||||
),
|
||||
ROBOTO("Roboto", "Roboto"), CRIMSON_TEXT(
|
||||
"Crimson Text",
|
||||
"Crimson Text"
|
||||
"Crimson Text", "Crimson Text"
|
||||
),
|
||||
SOURCE_SERIF_PRO("Source Serif Pro", "Source Serif Pro"), NEWSREADER(
|
||||
"Newsreader",
|
||||
"Newsreader"
|
||||
"Newsreader", "Newsreader"
|
||||
),
|
||||
LEXEND("Lexend", "Lexend"), LXGWWENKAI(
|
||||
"LXGW WenKai",
|
||||
"LXGWWenKai"
|
||||
"LXGW WenKai", "LXGWWenKai"
|
||||
),
|
||||
ATKINSON_HYPERLEGIBLE(
|
||||
"Atkinson Hyperlegible",
|
||||
"AtkinsonHyperlegible"
|
||||
"Atkinson Hyperlegible", "AtkinsonHyperlegible"
|
||||
),
|
||||
SOURCE_SANS_PRO("Source Sans Pro", "SourceSansPro"), IBM_PLEX_SANS(
|
||||
"IBM Plex Sans",
|
||||
"IBMPlexSans"
|
||||
"IBM Plex Sans", "IBMPlexSans"
|
||||
),
|
||||
LITERATA("Literata", "Literata"), FRAUNCES("Fraunces", "Fraunces"),
|
||||
}
|
||||
@ -55,6 +48,7 @@ data class ArticleContent(
|
||||
|
||||
data class WebReaderContent(
|
||||
val preferences: WebPreferences,
|
||||
val rtlText: Boolean,
|
||||
val item: SavedItem,
|
||||
val articleContent: ArticleContent,
|
||||
) {
|
||||
@ -68,6 +62,17 @@ data class WebReaderContent(
|
||||
val highlightCssFilePath =
|
||||
"highlight${if (preferences.themeKey == "Dark" || preferences.themeKey == "Black") "-dark" else ""}.css"
|
||||
|
||||
val rtlCss = if (rtlText) {
|
||||
"""
|
||||
body, html, #_omnivore-htmlContent, p, a, div, span {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
"""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
Log.d("theme", "current theme is: ${preferences.themeKey}")
|
||||
|
||||
Log.d("sync", "HIGHLIGHTS JSON: ${articleContent.highlightsJSONString()}")
|
||||
@ -80,6 +85,7 @@ data class WebReaderContent(
|
||||
<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("$highlightCssFilePath");
|
||||
$rtlCss
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -161,20 +161,22 @@ fun WebReaderLoadingContainer(
|
||||
|
||||
val darkTheme = isSystemInDarkTheme()
|
||||
|
||||
val rtlTextState by webReaderViewModel.rtlTextState.collectAsStateWithLifecycle()
|
||||
|
||||
val styledContent by remember {
|
||||
derivedStateOf {
|
||||
webReaderParams?.let {
|
||||
val webReaderContent = WebReaderContent(
|
||||
preferences = webReaderViewModel.storedWebPreferences(darkTheme),
|
||||
rtlText = rtlTextState,
|
||||
item = it.item,
|
||||
articleContent = it.articleContent,
|
||||
articleContent = it.articleContent
|
||||
)
|
||||
webReaderContent.styledContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val modalBottomSheetState = rememberModalBottomSheetState(
|
||||
initialValue = ModalBottomSheetValue.Hidden,
|
||||
skipHalfExpanded = bottomSheetState == BottomSheetState.EDITNOTE || bottomSheetState == BottomSheetState.HIGHLIGHTNOTE,
|
||||
@ -217,7 +219,7 @@ fun WebReaderLoadingContainer(
|
||||
when (bottomSheetState) {
|
||||
BottomSheetState.PREFERENCES -> {
|
||||
BottomSheetUI {
|
||||
ReaderPreferencesView(webReaderViewModel)
|
||||
ReaderPreferencesSheet(webReaderViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -32,6 +32,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.rtlText
|
||||
import app.omnivore.omnivore.core.datastore.volumeForScroll
|
||||
import app.omnivore.omnivore.core.network.Networker
|
||||
import app.omnivore.omnivore.core.network.createNewLabel
|
||||
@ -132,6 +133,14 @@ class WebReaderViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val rtlTextState: StateFlow<Boolean> = datastoreRepository.getBoolean(
|
||||
rtlText
|
||||
).stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = false
|
||||
)
|
||||
|
||||
fun showNavBar() {
|
||||
onScrollChange(maxToolbarHeightPx)
|
||||
}
|
||||
@ -581,6 +590,12 @@ class WebReaderViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun setRtlTextState(value: Boolean) {
|
||||
viewModelScope.launch {
|
||||
datastoreRepository.putBoolean(rtlText, value)
|
||||
}
|
||||
}
|
||||
|
||||
fun applyWebFont(font: WebFont) {
|
||||
runBlocking {
|
||||
datastoreRepository.putString(preferredWebFontFamily, font.rawValue)
|
||||
|
||||
@ -38,7 +38,6 @@ import app.omnivore.omnivore.core.database.entities.SavedItemLabel
|
||||
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
|
||||
import app.omnivore.omnivore.feature.components.LabelChip
|
||||
import app.omnivore.omnivore.feature.components.LabelChipColors
|
||||
import app.omnivore.omnivore.feature.library.SavedItemAction
|
||||
import app.omnivore.omnivore.feature.library.SavedItemViewModel
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
|
||||
@ -48,10 +47,10 @@ fun SavedItemCard(
|
||||
selected: Boolean,
|
||||
savedItemViewModel: SavedItemViewModel,
|
||||
savedItem: SavedItemWithLabelsAndHighlights,
|
||||
onClickHandler: () -> Unit,
|
||||
actionHandler: (SavedItemAction) -> Unit
|
||||
onClickHandler: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.combinedClickable(onClick = onClickHandler, onLongClick = {
|
||||
savedItemViewModel.actionsMenuItemLiveData.postValue(savedItem)
|
||||
@ -61,18 +60,17 @@ fun SavedItemCard(
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Top,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
.padding(16.dp)
|
||||
.background(Color.Transparent)
|
||||
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterVertically),
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.padding(end = 20.dp)
|
||||
.defaultMinSize(minHeight = 50.dp)
|
||||
) {
|
||||
ReadInfo(item = savedItem)
|
||||
@ -100,26 +98,27 @@ fun SavedItemCard(
|
||||
painter = rememberAsyncImagePainter(savedItem.savedItem.imageURLString),
|
||||
contentDescription = "Image associated with saved item",
|
||||
modifier = Modifier
|
||||
.size(55.dp, 73.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.defaultMinSize(minWidth = 55.dp, minHeight = 73.dp)
|
||||
.size(55.dp, 55.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.defaultMinSize(minWidth = 55.dp, minHeight = 55.dp)
|
||||
)
|
||||
}
|
||||
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
) {
|
||||
savedItem.labels.filter { !isFlairLabel(it) }
|
||||
.sortedWith(compareBy { it.name.toLowerCase(Locale.current) }).forEach { label ->
|
||||
val chipColors = LabelChipColors.fromHex(label.color)
|
||||
if (savedItem.labels.any { !isFlairLabel(it) }) {
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, bottom = 16.dp, end = 16.dp)
|
||||
) {
|
||||
savedItem.labels.filter { !isFlairLabel(it) }
|
||||
.sortedWith(compareBy { it.name.toLowerCase(Locale.current) }).forEach { label ->
|
||||
val chipColors = LabelChipColors.fromHex(label.color)
|
||||
|
||||
LabelChip(
|
||||
modifier = Modifier.clickable { }, name = label.name, colors = chipColors
|
||||
)
|
||||
}
|
||||
LabelChip(
|
||||
modifier = Modifier.clickable { }, name = label.name, colors = chipColors
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(thickness = 1.dp, color = MaterialTheme.colorScheme.outlineVariant)
|
||||
|
||||
@ -154,6 +154,7 @@
|
||||
<string name="reader_preferences_view_high_constrast_text">High Contrast Text</string>
|
||||
<string name="reader_preferences_view_justify_text">Justify Text</string>
|
||||
<string name="reader_preferences_view_volume_scroll">Use Volume Rocker to scroll</string>
|
||||
<string name="reader_preferences_view_use_rtl">Right-to-left text</string>
|
||||
|
||||
<!-- WebReaderLoadingContainer -->
|
||||
<string name="web_reader_loading_container_error_msg">We were unable to fetch your content.</string>
|
||||
|
||||
Reference in New Issue
Block a user