Merge pull request #4100 from stefanosansone/feature/android-rtl-text

Android - Right-to-left text support
This commit is contained in:
Jackson Harper
2024-06-25 15:28:59 +08:00
committed by GitHub
10 changed files with 72 additions and 51 deletions

View File

@ -18,3 +18,4 @@ const val lastUsedSavedItemSortFilter = "lastUsedSavedItemSortFilter"
const val preferredTheme = "preferredTheme"
const val followingTabActive = "followingTabActive"
const val volumeForScroll = "volumeForScroll"
const val rtlText = "rtlText"

View File

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

View File

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

View File

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

View File

@ -168,6 +168,7 @@ fun WebReader(
"utf-8",
null
)
Log.d("HTMLContent", styledContent)
requestFocus()
setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN) {

View File

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

View File

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

View File

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

View File

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

View File

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