Merge pull request #2180 from omnivore-app/feat/webview-reader-links

Better handling of links in the Android reader view
This commit is contained in:
Jackson Harper 2023-05-16 12:14:10 +08:00 committed by GitHub
commit 6e71e964bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 968 additions and 493 deletions

View file

@ -17,8 +17,8 @@ android {
applicationId "app.omnivore.omnivore"
minSdk 26
targetSdk 33
versionCode 70
versionName "0.0.70"
versionCode 76
versionName "0.0.76"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@ -84,6 +84,7 @@ dependencies {
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.6.1'
implementation 'androidx.appcompat:appcompat:1.5.1'
@ -135,6 +136,7 @@ dependencies {
implementation 'com.google.android.gms:play-services-auth:20.4.0'
implementation "com.google.accompanist:accompanist-systemuicontroller:0.25.1"
implementation "com.google.accompanist:accompanist-flowlayout:0.25.1"
implementation 'io.coil-kt:coil-compose:2.2.0'
@ -150,6 +152,7 @@ dependencies {
kapt "androidx.room:room-compiler:$room_version"
implementation 'com.github.jeziellago:compose-markdown:0.3.3'
implementation "io.github.dokar3:chiptextfield:0.4.6"
}
apollo {

View file

@ -1,9 +1,18 @@
package app.omnivore.omnivore.networking
import android.content.ContentValues
import android.net.Uri
import android.util.Log
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.Constants
import app.omnivore.omnivore.graphql.generated.SaveUrlMutation
import app.omnivore.omnivore.graphql.generated.SetBookmarkArticleMutation
import app.omnivore.omnivore.graphql.generated.SetLinkArchivedMutation
import app.omnivore.omnivore.graphql.generated.type.ArchiveLinkInput
import app.omnivore.omnivore.graphql.generated.type.SetBookmarkArticleInput
import app.omnivore.omnivore.graphql.generated.type.*
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
import kotlinx.coroutines.launch
import java.util.*
suspend fun Networker.deleteSavedItem(itemID: String): Boolean {
return try {
@ -32,3 +41,14 @@ suspend fun Networker.updateArchiveStatusSavedItem(itemID: String, setAsArchived
false
}
}
suspend fun Networker.saveUrl(url: Uri): Boolean {
return try {
val clientRequestId = UUID.randomUUID().toString()
val input = SaveUrlInput(url = url.toString(), clientRequestId = clientRequestId, source = "android")
val result = authenticatedApolloClient().mutation(SaveUrlMutation(input)).execute()
result.data?.saveUrl?.onSaveSuccess?.url != null
} catch (e: java.lang.Exception) {
false
}
}

View file

@ -13,10 +13,10 @@ import app.omnivore.omnivore.ui.components.LabelChipColors
fun LabelChip(
name: String,
colors: LabelChipColors,
onSelectionChanged: (String) -> Unit = {},
modifier: Modifier = Modifier.padding(0.dp),
) {
Surface(
modifier = Modifier.padding(4.dp),
modifier = modifier.padding(2.dp),
shape = MaterialTheme.shapes.medium,
color = colors.containerColor
) {

View file

@ -135,6 +135,10 @@ object LabelSwatchHelper {
return listOf(shuffledSwatches.last()) + webSwatchHexes + shuffledSwatches.dropLast(1)
}
fun random(): String {
return webSwatchHexes.random()
}
private val webSwatchHexes = listOf(
"#FF5D99",
"#7CFF7B",

View file

@ -3,15 +3,18 @@
package app.omnivore.omnivore.ui.components
import LabelChip
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
@ -23,120 +26,203 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.*
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.ui.library.LibraryViewModel
import app.omnivore.omnivore.ui.reader.WebReaderParams
import app.omnivore.omnivore.ui.reader.WebReaderViewModel
import com.dokar.chiptextfield.*
import com.google.accompanist.flowlayout.FlowRow
import kotlinx.coroutines.delay
import java.time.LocalDate
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.*
//@Composable
//fun LabelsSelectionSheet(viewModel: LibraryViewModel) {
// val isActive: Boolean by viewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
// val labels: List<SavedItemLabel> by viewModel.savedItemLabelsLiveData.observeAsState(listOf())
// val currentSavedItemData = viewModel.currentSavedItemUnderEdit()
//
// val modalBottomSheetState = rememberModalBottomSheetState(
// ModalBottomSheetValue.HalfExpanded,
// confirmStateChange = { it != ModalBottomSheetValue.Hidden }
// )
//
// if (isActive) {
// ModalBottomSheetLayout(
// sheetBackgroundColor = Color.Transparent,
// sheetState = modalBottomSheetState,
// sheetContent = {
// BottomSheetUI {
// if (currentSavedItemData != null) {
// LabelsSelectionSheetContent(
// labels = labels,
// initialSelectedLabels = currentSavedItemData.labels,
// onCancel = {
// viewModel.showLabelsSelectionSheetLiveData.value = false
// viewModel.labelsSelectionCurrentItemLiveData.value = null
// },
// isLibraryMode = false,
// onSave = {
// if (it != labels) {
// viewModel.updateSavedItemLabels(
// savedItemID = currentSavedItemData.savedItem.savedItemId,
// labels = it
// )
// }
// viewModel.labelsSelectionCurrentItemLiveData.value = null
// viewModel.showLabelsSelectionSheetLiveData.value = false
// },
// onCreateLabel = { newLabelName, labelHexValue ->
// viewModel.createNewSavedItemLabel(newLabelName, labelHexValue)
// }
// )
// } else { // Is used in library mode
// LabelsSelectionSheetContent(
// labels = labels,
// initialSelectedLabels = viewModel.activeLabelsLiveData.value ?: listOf(),
// onCancel = { viewModel.showLabelsSelectionSheetLiveData.value = false },
// isLibraryMode = true,
// onSave = {
// viewModel.updateAppliedLabels(it)
// viewModel.labelsSelectionCurrentItemLiveData.value = null
// viewModel.showLabelsSelectionSheetLiveData.value = false
// },
// onCreateLabel = { newLabelName, labelHexValue ->
// viewModel.createNewSavedItemLabel(newLabelName, labelHexValue)
// }
// )
// }
// }
// }
// ) {}
// }
//}
@Composable
fun WebReaderLabelsSelectionSheet(viewModel: WebReaderViewModel) {
val isActive: Boolean by viewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
val labels: List<SavedItemLabel> by viewModel.savedItemLabelsLiveData.observeAsState(listOf())
val webReaderParams: WebReaderParams? by viewModel.webReaderParamsLiveData.observeAsState(null)
fun CircleIcon(colorHex: String){
val chipColors = LabelChipColors.fromHex(colorHex)
val viewConfiguration = LocalViewConfiguration.current
val viewConfigurationOverride = remember(viewConfiguration) {
ViewConfigurationOverride(
base = viewConfiguration,
minimumTouchTargetSize = DpSize(24.dp, 24.dp)
)
}
val modalBottomSheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.HalfExpanded,
)
if (isActive) {
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Transparent,
sheetState = modalBottomSheetState,
sheetContent = {
BottomSheetUI {
LabelsSelectionSheetContent(
labels = labels,
initialSelectedLabels = webReaderParams?.labels ?: listOf(),
onCancel = {
viewModel.showLabelsSelectionSheetLiveData.value = false
},
isLibraryMode = false,
onSave = {
if (it != labels) {
viewModel.updateSavedItemLabels(savedItemID = webReaderParams?.item?.savedItemId ?: "", labels = it)
}
viewModel.showLabelsSelectionSheetLiveData.value = false
},
onCreateLabel = { newLabelName, labelHexValue ->
viewModel.createNewSavedItemLabel(newLabelName, labelHexValue)
}
)
}
}
) {}
CompositionLocalProvider(LocalViewConfiguration provides viewConfigurationOverride) {
Row(
modifier = Modifier
.padding(start = 10.dp, end = 2.dp)
.padding(vertical = 7.dp)
) {
Canvas(modifier = Modifier.size(12.dp), onDraw = {
drawCircle(color = chipColors.containerColor)
})
}
}
}
@Composable
fun LabelsSelectionSheet(viewModel: LibraryViewModel) {
val isActive: Boolean by viewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
val labels: List<SavedItemLabel> by viewModel.savedItemLabelsLiveData.observeAsState(listOf())
val currentSavedItemData = viewModel.currentSavedItemUnderEdit()
val modalBottomSheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.HalfExpanded,
confirmStateChange = { it != ModalBottomSheetValue.Hidden }
)
if (isActive) {
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Transparent,
sheetState = modalBottomSheetState,
sheetContent = {
BottomSheetUI {
if (currentSavedItemData != null) {
LabelsSelectionSheetContent(
labels = labels,
initialSelectedLabels = currentSavedItemData.labels,
onCancel = {
viewModel.showLabelsSelectionSheetLiveData.value = false
viewModel.labelsSelectionCurrentItemLiveData.value = null
},
isLibraryMode = false,
onSave = {
if (it != labels) {
viewModel.updateSavedItemLabels(
savedItemID = currentSavedItemData.savedItem.savedItemId,
labels = it
)
}
viewModel.labelsSelectionCurrentItemLiveData.value = null
viewModel.showLabelsSelectionSheetLiveData.value = false
},
onCreateLabel = { newLabelName, labelHexValue ->
viewModel.createNewSavedItemLabel(newLabelName, labelHexValue)
}
)
} else { // Is used in library mode
LabelsSelectionSheetContent(
labels = labels,
initialSelectedLabels = viewModel.activeLabelsLiveData.value ?: listOf(),
onCancel = { viewModel.showLabelsSelectionSheetLiveData.value = false },
isLibraryMode = true,
onSave = {
viewModel.updateAppliedLabels(it)
viewModel.labelsSelectionCurrentItemLiveData.value = null
viewModel.showLabelsSelectionSheetLiveData.value = false
},
onCreateLabel = { newLabelName, labelHexValue ->
viewModel.createNewSavedItemLabel(newLabelName, labelHexValue)
}
)
}
}
}
) {}
fun <T : Chip> CloseButton(
state: ChipTextFieldState<T>,
chip: T,
modifier: Modifier = Modifier,
backgroundColor: Color = Color.Transparent,
strokeColor: Color = Color.White,
startPadding: Dp = 0.dp,
endPadding: Dp = 4.dp
) {
Row(
modifier = modifier
.padding(start = startPadding, end = endPadding)
) {
CloseButtonImpl(
onClick = { state.removeChip(chip) },
backgroundColor = backgroundColor,
strokeColor = strokeColor
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
internal class ViewConfigurationOverride(
base: ViewConfiguration,
override val doubleTapMinTimeMillis: Long = base.doubleTapMinTimeMillis,
override val doubleTapTimeoutMillis: Long = base.doubleTapTimeoutMillis,
override val longPressTimeoutMillis: Long = base.longPressTimeoutMillis,
override val touchSlop: Float = base.touchSlop,
override val minimumTouchTargetSize: DpSize = base.minimumTouchTargetSize
) : ViewConfiguration
@Composable
private fun CloseButtonImpl(
onClick: () -> Unit,
backgroundColor: Color,
strokeColor: Color,
modifier: Modifier = Modifier,
) {
val padding = with(LocalDensity.current) { 6.dp.toPx() }
val strokeWidth = with(LocalDensity.current) { 1.2.dp.toPx() }
val viewConfiguration = LocalViewConfiguration.current
val viewConfigurationOverride = remember(viewConfiguration) {
ViewConfigurationOverride(
base = viewConfiguration,
minimumTouchTargetSize = DpSize(24.dp, 24.dp)
)
}
CompositionLocalProvider(LocalViewConfiguration provides viewConfigurationOverride) {
Canvas(
modifier = modifier
.size(18.dp)
.clip(CircleShape)
.background(backgroundColor)
.clickable(onClick = onClick)
) {
drawLine(
color = strokeColor,
start = Offset(padding, padding),
end = Offset(size.width - padding, size.height - padding),
strokeWidth = strokeWidth
)
drawLine(
color = strokeColor,
start = Offset(padding, size.height - padding),
end = Offset(size.width - padding, padding),
strokeWidth = strokeWidth
)
}
}
}
class LabelChipView(label: SavedItemLabel) : Chip(label.name) {
val label = label
}
@Composable
@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class,
ExperimentalMaterial3Api::class
)
fun LabelsSelectionSheetContent(
isLibraryMode: Boolean,
labels: List<SavedItemLabel>,
@ -145,36 +231,57 @@ fun LabelsSelectionSheetContent(
onSave: (List<SavedItemLabel>) -> Unit,
onCreateLabel: (String, String) -> Unit
) {
val listState = rememberLazyListState()
val selectedLabels = remember { mutableStateOf(initialSelectedLabels) }
var showCreateLabelDialog by remember { mutableStateOf(false ) }
val interactionSource = remember { MutableInteractionSource() }
val state = rememberChipTextFieldState(initialSelectedLabels.map {
LabelChipView(it)
})
val focusRequester = remember { FocusRequester() }
var filterTextValue by remember { mutableStateOf(TextFieldValue()) }
val onFilterTextValueChange: (TextFieldValue) -> Unit = { filterTextValue = it }
val filteredLabels = labels.filter { label ->
val text = filterTextValue.text.toLowerCase(Locale.current)
val result = (text.isEmpty() || label.name.toLowerCase(Locale.current).startsWith(text))
val alreadySelected = state.chips.map { it.label.name }.contains(label.name)
result && !alreadySelected
}
val currentLabel = labels.find {
val text = filterTextValue.text.toLowerCase(Locale.current)
it.name.toLowerCase(Locale.current) == text
}
val titleText = if (isLibraryMode) "Filter by Label" else "Set Labels"
val findOrCreateLabel: (name: TextFieldValue) -> SavedItemLabel = { name ->
val found = labels.find { it.name == name.text }
found
?: SavedItemLabel(
savedItemLabelId = "",
name = name.text,
labelDescription = "",
color = LabelSwatchHelper.random(),
createdAt = LocalDate.now().atStartOfDay().atOffset(ZoneOffset.UTC).format(
DateTimeFormatter.ISO_DATE_TIME),
serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
)
}
Surface(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
) {
if (showCreateLabelDialog) {
LabelCreationDialog(
onDismiss = { showCreateLabelDialog = false },
onSave = { labelName, hexColor ->
onCreateLabel(labelName, hexColor)
showCreateLabelDialog = false
}
)
}
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
// .verticalScroll(rememberScrollState())
.fillMaxSize()
.padding(horizontal = 0.dp)
.padding(horizontal = 5.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
@ -187,87 +294,97 @@ fun LabelsSelectionSheetContent(
Text(titleText, fontWeight = FontWeight.ExtraBold)
TextButton(onClick = { onSave(selectedLabels.value) }) {
TextButton(onClick = { onSave(state.chips.map { it.label }) }) {
Text(text = if (isLibraryMode) "Search" else "Save")
}
}
LazyColumn(
state = listState,
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
) {
items(labels) { label ->
val isLabelSelected = selectedLabels.value.contains(label)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable {
if (isLabelSelected) {
selectedLabels.value =
selectedLabels.value.filter { it.savedItemLabelId != label.savedItemLabelId }
} else {
selectedLabels.value = selectedLabels.value + listOf(label)
}
}
.padding(horizontal = 10.dp, vertical = 6.dp)
) {
ChipTextField(
state = state,
value = filterTextValue,
onValueChange = onFilterTextValueChange,
onSubmit = {
if (isLibraryMode) {
currentLabel?.let {
LabelChipView(it)
} ?: null
} else {
LabelChipView(findOrCreateLabel(it))
}
},
chipLeadingIcon = { chip -> CircleIcon(colorHex = chip.label.color) },
chipTrailingIcon = { chip -> CloseButton(state, chip) },
interactionSource = interactionSource,
chipStyle = ChipTextFieldDefaults.chipStyle(
shape = androidx.compose.material.MaterialTheme.shapes.medium,
unfocusedBorderWidth = 0.dp,
focusedTextColor = Color(0xFFAEAEAF),
focusedBorderColor = Color(0xFF2A2A2A),
focusedBackgroundColor = Color(0xFF2A2A2A)
),
colors = androidx.compose.material.TextFieldDefaults.textFieldColors(
textColor = Color(0xFFAEAEAF),
backgroundColor = Color(0xFF3D3D3D)
),
contentPadding = PaddingValues(10.dp),
modifier = Modifier
.defaultMinSize(minHeight = 45.dp)
.fillMaxWidth()
.padding(horizontal = 10.dp)
.focusRequester(focusRequester)
// .onFocusEvent {
// val text = filterTextValue.text
// if (it.hasFocus) {
// val selection = filterTextValue.text.length
// onFilterTextValueChange(filterTextValue.copy(selection = TextRange(selection)))
// }
// }
)
if (!isLibraryMode && filterTextValue.text.isNotEmpty() && currentLabel == null) {
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable {
val label = findOrCreateLabel(filterTextValue)
state.addChip(LabelChipView(label))
filterTextValue = TextFieldValue()
}
.padding(horizontal = 10.dp)
.padding(top = 10.dp, bottom = 5.dp)
)
{
Icon(
imageVector = Icons.Filled.AddCircle,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp)
)
Text(text = "Create a new label named \"${filterTextValue.text}\"")
}
}
if (filteredLabels.isNotEmpty()) {
FlowRow(modifier = Modifier.fillMaxWidth().padding(10.dp)) {
filteredLabels.forEach { label ->
val chipColors = LabelChipColors.fromHex(label.color)
LabelChip(
name = label.name,
colors = chipColors
)
if (isLabelSelected) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null
)
}
}
Divider(color = MaterialTheme.colorScheme.outlineVariant, thickness = 1.dp)
}
if (!isLibraryMode) {
item {
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
colors = chipColors,
modifier = Modifier
.fillMaxWidth()
.clickable { showCreateLabelDialog = true }
.padding(horizontal = 6.dp)
.padding(vertical = 12.dp)
.clickable {
state.addChip(LabelChipView(label))
filterTextValue = TextFieldValue()
}
)
{
Icon(
imageVector = Icons.Filled.AddCircle,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp)
)
Text(text = "Create a new Label")
}
}
}
}
}
}
}
LaunchedEffect(Unit) {
@Composable
private fun BottomSheetUI(content: @Composable () -> Unit) {
Box(
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth()
.clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp))
.background(Color.White)
.statusBarsPadding()
) {
content()
}
}

View file

@ -17,6 +17,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
@ -77,7 +79,7 @@ fun LibraryFilterBar(viewModel: LibraryViewModel) {
modifier = Modifier.padding(end = 6.dp)
)
}
items(activeLabels.sortedBy { it.name }) { label ->
items(activeLabels.sortedWith(compareBy { it.name.toLowerCase(Locale.current) })) { label ->
val chipColors = LabelChipColors.fromHex(label.color)
AssistChip(

View file

@ -10,17 +10,18 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.*
import androidx.compose.material.DrawerValue
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
@ -33,38 +34,123 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import app.omnivore.omnivore.R
import app.omnivore.omnivore.Routes
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.ui.components.LabelsSelectionSheet
import app.omnivore.omnivore.ui.components.LabelsSelectionSheetContent
import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard
import app.omnivore.omnivore.ui.reader.PDFReaderActivity
import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity
import app.omnivore.omnivore.ui.save.SaveSheetActivityBase
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
fun LibraryView(
libraryViewModel: LibraryViewModel,
navController: NavHostController
) {
Scaffold(
topBar = {
LibraryNavigationBar(
savedItemViewModel = libraryViewModel,
onSearchClicked = { navController.navigate(Routes.Search.route) },
onSettingsIconClick = { navController.navigate(Routes.Settings.route) }
)
val scaffoldState: ScaffoldState = rememberScaffoldState()
val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
},
) { paddingValues ->
val coroutineScope = rememberCoroutineScope()
val modalBottomSheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden,
confirmStateChange = { it != ModalBottomSheetValue.Hidden }
)
if (showLabelsSelectionSheet) {
coroutineScope.launch {
modalBottomSheetState.show()
}
} else {
coroutineScope.launch {
modalBottomSheetState.hide()
}
}
libraryViewModel.snackbarMessage?.let {
coroutineScope.launch {
scaffoldState.snackbarHostState.showSnackbar(it)
libraryViewModel.clearSnackbarMessage()
}
}
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Transparent,
sheetState = modalBottomSheetState,
sheetContent = {
BottomSheetContent(libraryViewModel)
Spacer(modifier = Modifier.weight(1.0F))
}
) {
Scaffold(
scaffoldState = scaffoldState,
topBar = {
LibraryNavigationBar(
savedItemViewModel = libraryViewModel,
onSearchClicked = { navController.navigate(Routes.Search.route) },
onSettingsIconClick = { navController.navigate(Routes.Settings.route) }
)
},
) { paddingValues ->
LibraryViewContent(
libraryViewModel,
modifier = Modifier
.padding(
top = paddingValues.calculateTopPadding()
)
.padding(top = paddingValues.calculateTopPadding())
)
}
}
}
@Composable
fun BottomSheetContent(libraryViewModel: LibraryViewModel) {
val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit()
val labels: List<SavedItemLabel> by libraryViewModel.savedItemLabelsLiveData.observeAsState(listOf())
if (showLabelsSelectionSheet) {
BottomSheetUI {
if (currentSavedItemData != null) {
LabelsSelectionSheetContent(
labels = labels,
initialSelectedLabels = currentSavedItemData.labels,
onCancel = {
libraryViewModel.showLabelsSelectionSheetLiveData.value = false
libraryViewModel.labelsSelectionCurrentItemLiveData.value = null
},
isLibraryMode = false,
onSave = {
if (it != labels) {
libraryViewModel.updateSavedItemLabels(
savedItemID = currentSavedItemData.savedItem.savedItemId,
labels = it
)
}
libraryViewModel.labelsSelectionCurrentItemLiveData.value = null
libraryViewModel.showLabelsSelectionSheetLiveData.value = false
},
onCreateLabel = { newLabelName, labelHexValue ->
libraryViewModel.createNewSavedItemLabel(newLabelName, labelHexValue)
}
)
} else { // Is used in library mode
LabelsSelectionSheetContent(
labels = labels,
initialSelectedLabels = libraryViewModel.activeLabelsLiveData.value ?: listOf(),
onCancel = { libraryViewModel.showLabelsSelectionSheetLiveData.value = false },
isLibraryMode = true,
onSave = {
libraryViewModel.updateAppliedLabels(it)
libraryViewModel.labelsSelectionCurrentItemLiveData.value = null
libraryViewModel.showLabelsSelectionSheetLiveData.value = false
},
onCreateLabel = { newLabelName, labelHexValue ->
libraryViewModel.createNewSavedItemLabel(newLabelName, labelHexValue)
}
)
}
}
}
}
@ -88,6 +174,7 @@ fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) {
.fillMaxSize()
.pullRefresh(pullRefreshState)
) {
LazyColumn(
state = listState,
verticalArrangement = Arrangement.Top,
@ -137,10 +224,25 @@ fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) {
modifier = Modifier.align(Alignment.TopCenter)
)
LabelsSelectionSheet(viewModel = libraryViewModel)
// LabelsSelectionSheet(viewModel = libraryViewModel)
}
}
@Composable
private fun BottomSheetUI(content: @Composable () -> Unit) {
Box(
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth()
.clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp))
.background(Color.White)
.statusBarsPadding()
) {
content()
}
}
@Composable
fun InfiniteListHandler(
listState: LazyListState,

View file

@ -1,17 +1,26 @@
package app.omnivore.omnivore.ui.library
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.widget.Toast
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.*
import app.omnivore.omnivore.*
import app.omnivore.omnivore.dataService.*
import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput
import app.omnivore.omnivore.graphql.generated.type.SetLabelsInput
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.networking.*
import app.omnivore.omnivore.persistence.entities.*
import com.apollographql.apollo3.api.Optional
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
@ -34,6 +43,9 @@ class LibraryViewModel @Inject constructor(
private var searchIdx = 0
private var receivedIdx = 0
var snackbarMessage by mutableStateOf<String?>(null)
private set
// Live Data
private var itemsLiveDataInternal = dataService.db.savedItemDao().filteredLibraryData(
allowedArchiveStates = listOf(0),
@ -75,6 +87,10 @@ class LibraryViewModel @Inject constructor(
}
}
fun clearSnackbarMessage() {
snackbarMessage = null
}
fun refresh() {
cursor = null
librarySearchCursor = null
@ -276,6 +292,9 @@ class LibraryViewModel @Inject constructor(
labelsSelectionCurrentItemLiveData.value = itemID
showLabelsSelectionSheetLiveData.value = true
}
else -> {
}
}
actionsMenuItemLiveData.postValue(null)
}
@ -283,11 +302,39 @@ class LibraryViewModel @Inject constructor(
fun updateSavedItemLabels(savedItemID: String, labels: List<SavedItemLabel>) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val input = SetLabelsInput(labelIds = labels.map { it.savedItemLabelId }, pageId = savedItemID)
val syncedLabels = labels.filter { it.serverSyncStatus == ServerSyncStatus.IS_SYNCED.rawValue }
val unsyncedLabels = labels.filter { it.serverSyncStatus != ServerSyncStatus.IS_SYNCED.rawValue }
var labelCreationError = false
val createdLabels = unsyncedLabels.mapNotNull { label ->
val result = networker.createNewLabel(CreateLabelInput(
name = label.name,
color = presentIfNotNull(label.color),
description = presentIfNotNull(label.labelDescription),
))
result?.let {
SavedItemLabel(
savedItemLabelId = result.id,
name = result.name,
color = result.color,
createdAt = result.createdAt.toString(),
labelDescription = result.description,
serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue
)
} ?: run {
labelCreationError = true
null
}
}
dataService.db.savedItemLabelDao().insertAll(createdLabels)
val allLabels = syncedLabels + createdLabels
val input = SetLabelsInput(labelIds = allLabels.map { it.savedItemLabelId }, pageId = savedItemID)
val networkResult = networker.updateLabelsForSavedItem(input)
// TODO: assign a server sync status to these
val crossRefs = labels.map {
val crossRefs = allLabels.map {
SavedItemAndSavedItemLabelCrossRef(
savedItemLabelId = it.savedItemLabelId,
savedItemId = savedItemID
@ -300,6 +347,12 @@ class LibraryViewModel @Inject constructor(
// Add back the current labels
dataService.db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs)
if (!networkResult || labelCreationError) {
snackbarMessage = "Unable to set labels"
} else {
snackbarMessage = "Labels updated"
}
CoroutineScope(Dispatchers.Main).launch {
handleFilterChanges()
}
@ -353,5 +406,5 @@ enum class SavedItemAction {
Delete,
Archive,
Unarchive,
EditLabels
EditLabels,
}

View file

@ -120,7 +120,6 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel) {
onClick = {
val clip = ClipData.newPlainText("notebook", notebookMD(notes, highlights))
clipboard?.let {
it
clipboard?.setPrimaryClip(clip)
} ?: run {
coroutineScope.launch {
@ -289,7 +288,6 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights) {
onClick = {
val clip = ClipData.newPlainText("highlight", highlight.quote)
clipboard?.let {
it
clipboard?.setPrimaryClip(clip)
} ?: run {
coroutineScope.launch {

View file

@ -11,6 +11,8 @@ 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.ArrowDropDown
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -25,6 +27,7 @@ import androidx.compose.ui.unit.sp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
val isDark = isSystemInDarkTheme()
@ -43,8 +46,6 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
val themeState = remember { mutableStateOf(currentWebPreferences.storedThemePreference) }
val themeListState = rememberLazyListState()
OmnivoreTheme() {
Column(
modifier = Modifier
@ -65,16 +66,17 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
))
Spacer(modifier = Modifier.weight(1.0F))
Box {
OutlinedButton(
shape = RoundedCornerShape(4.dp),
AssistChip(
onClick = { isFontListExpanded.value = true },
colors = ButtonDefaults.buttonColors(
contentColor = Color(red = 137, green = 137, blue = 137),
// containerColor = Color.Transparent,
),
) {
Text(selectedWebFontName.value)
}
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,

View file

@ -11,6 +11,7 @@ import android.view.*
import android.view.View.OnScrollChangeListener
import android.view.ViewTreeObserver.OnScrollChangedListener
import android.webkit.JavascriptInterface
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.foundation.isSystemInDarkTheme
@ -62,6 +63,21 @@ fun WebReader(
viewModel?.showNavBar()
view?.animate()?.alpha(1.0f)?.duration = 200
}
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
var handled: Boolean? = null
request?.let {
if ((request?.isForMainFrame == true) && (request?.hasGesture() == true) && viewModel != null) {
viewModel?.showOpenLinkSheet(context, request.url)
handled = true
}
}
return handled ?: super.shouldOverrideUrlLoading(view, request)
}
}
val javascriptInterface = AndroidWebKitMessenger { actionID, json ->

View file

@ -3,6 +3,7 @@ package app.omnivore.omnivore.ui.reader
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.activity.compose.setContent
@ -14,8 +15,11 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.*
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
@ -34,7 +38,6 @@ import app.omnivore.omnivore.MainActivity
import app.omnivore.omnivore.R
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.ui.components.LabelsSelectionSheetContent
import app.omnivore.omnivore.ui.components.WebReaderLabelsSelectionSheet
import app.omnivore.omnivore.ui.notebook.NotebookView
import app.omnivore.omnivore.ui.notebook.NotebookViewModel
import app.omnivore.omnivore.ui.savedItemViews.SavedItemContextMenu
@ -43,6 +46,8 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
import androidx.compose.material3.Button
import androidx.compose.ui.platform.LocalContext
@AndroidEntryPoint
@ -112,6 +117,7 @@ enum class BottomSheetState(
NOTEBOOK(),
HIGHLIGHTNOTE(),
LABELS(),
LINK()
}
@ -122,18 +128,11 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
webReaderViewModel: WebReaderViewModel,
notebookViewModel: NotebookViewModel) {
val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
var isMenuExpanded by remember { mutableStateOf(false) }
var bottomSheetState by remember { mutableStateOf(BottomSheetState.NONE) }
val isDarkMode = isSystemInDarkTheme()
val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState()
val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value }
val bottomSheetState: BottomSheetState? by webReaderViewModel.bottomSheetStateLiveData.observeAsState(BottomSheetState.NONE)
val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(null)
val annotation: String? by webReaderViewModel.annotationLiveData.observeAsState(null)
val shouldPopView: Boolean by webReaderViewModel.shouldPopViewLiveData.observeAsState(false)
val toolbarHeightPx: Float by webReaderViewModel.currentToolbarHeightLiveData.observeAsState(0.0f)
val labels: List<SavedItemLabel> by webReaderViewModel.savedItemLabelsLiveData.observeAsState(listOf())
@ -154,42 +153,54 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
val modalBottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
confirmStateChange = {
if (it == ModalBottomSheetValue.Hidden) {
webReaderViewModel.resetBottomSheet()
}
true
}
)
val themeBackgroundColor = currentTheme?.let {
if (it.themeKey == "System" && isDarkMode) {
Color(0xFF000000)
} else if (it.themeKey == "System" ) {
Color(0xFFFFFFFF)
} else {
Color(it.backgroundColor ?: 0xFFFFFFFF)
}
} ?: Color(0xFFFFFFFF)
val themeTintColor = currentTheme?.let {
if (it.themeKey == "System" && isDarkMode) {
Color(0xFFFFFFFF)
} else if (it.themeKey == "System" ) {
Color(0xFF000000)
} else {
Color(it.foregroundColor ?: 0xFF000000)
}
} ?: Color(0xFF000000)
annotation?.let {
bottomSheetState = BottomSheetState.HIGHLIGHTNOTE
coroutineScope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
when (bottomSheetState) {
BottomSheetState.PREFERENCES -> {
coroutineScope.launch {
if (!modalBottomSheetState.isVisible) {
modalBottomSheetState.show()
}
}
}
BottomSheetState.NOTEBOOK -> {
coroutineScope.launch {
modalBottomSheetState.show()
}
}
BottomSheetState.HIGHLIGHTNOTE -> {
coroutineScope.launch {
modalBottomSheetState.show()
}
}
BottomSheetState.LABELS -> {
coroutineScope.launch {
modalBottomSheetState.show()
}
}
BottomSheetState.LINK -> {
coroutineScope.launch {
modalBottomSheetState.show()
}
}
BottomSheetState.NONE -> {
coroutineScope.launch {
modalBottomSheetState.hide()
}
}
else -> {
coroutineScope.launch {
modalBottomSheetState.hide()
}
}
}
}
val showLabelsSelector: Boolean by webReaderViewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
if (showLabelsSelector) {
bottomSheetState = BottomSheetState.LABELS
coroutineScope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.HalfExpanded)
}
}
ModalBottomSheetLayout(
modifier = Modifier
@ -211,37 +222,34 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
}
}
BottomSheetState.HIGHLIGHTNOTE -> {
annotation?.let { annotation ->
webReaderViewModel.annotation?.let { annotation ->
BottomSheetUI(title = "Note") {
AnnotationEditView(
initialAnnotation = annotation,
onSave = {
webReaderViewModel.saveAnnotation(it)
coroutineScope.launch {
modalBottomSheetState.hide()
bottomSheetState = BottomSheetState.NONE
webReaderViewModel.resetBottomSheet()
}
},
onCancel = {
webReaderViewModel.cancelAnnotationEdit()
coroutineScope.launch {
modalBottomSheetState.hide()
bottomSheetState = BottomSheetState.NONE
webReaderViewModel.resetBottomSheet()
}
}
)
}
}
}
app.omnivore.omnivore.ui.reader.BottomSheetState.LABELS -> {
BottomSheetState.LABELS -> {
BottomSheetUI(title = "Notebook") {
LabelsSelectionSheetContent(
labels = labels,
initialSelectedLabels = webReaderParams?.labels ?: listOf(),
onCancel = {
coroutineScope.launch {
modalBottomSheetState.hide()
bottomSheetState = BottomSheetState.NONE
webReaderViewModel.resetBottomSheet()
}
},
isLibraryMode = false,
@ -252,8 +260,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
)
}
coroutineScope.launch {
modalBottomSheetState.hide()
bottomSheetState = BottomSheetState.NONE
webReaderViewModel.resetBottomSheet()
}
},
onCreateLabel = { newLabelName, labelHexValue ->
@ -262,8 +269,16 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
)
}
}
BottomSheetState.LINK -> {
BottomSheetUI(title = "Open Link") {
OpenLinkView(webReaderViewModel)
}
}
BottomSheetState.NONE -> {
}
else -> {
}
}
Spacer(modifier = Modifier.weight(1.0F))
@ -271,104 +286,135 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
) {
Scaffold(
topBar = {
TopAppBar(
modifier = Modifier
.height(height = with(LocalDensity.current) {
toolbarHeightPx.roundToInt().toDp()
}),
backgroundColor = themeBackgroundColor,
elevation = 0.dp,
title = {},
navigationIcon = {
IconButton(onClick = {
onBackPressedDispatcher?.onBackPressed()
}) {
Icon(
imageVector = Icons.Filled.ArrowBack,
modifier = Modifier,
contentDescription = "Back",
tint = themeTintColor
)
}
},
actions = {
if (onLibraryIconTap != null) {
IconButton(onClick = { onLibraryIconTap() }) {
Icon(
imageVector = Icons.Default.Home,
contentDescription = null,
tint = themeTintColor,
)
}
}
webReaderParams?.let {
IconButton(onClick = {
coroutineScope.launch {
bottomSheetState = BottomSheetState.NOTEBOOK
modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
}
}) {
Icon(
painter = painterResource(id = R.drawable.notebook),
contentDescription = null,
tint = themeTintColor
)
}
}
IconButton(onClick = {
coroutineScope.launch {
bottomSheetState = BottomSheetState.PREFERENCES
modalBottomSheetState.animateTo(ModalBottomSheetValue.HalfExpanded)
}
}) {
Icon(
painter = painterResource(id = R.drawable.format_letter_case),
contentDescription = null,
tint = themeTintColor
)
}
IconButton(onClick = { isMenuExpanded = true }) {
Icon(
painter = painterResource(id = R.drawable.dots_horizontal),
contentDescription = null,
tint = themeTintColor
)
if (isMenuExpanded) {
webReaderParams?.let { params ->
SavedItemContextMenu(
isExpanded = isMenuExpanded,
isArchived = params.item.isArchived,
onDismiss = { isMenuExpanded = false },
actionHandler = {
webReaderViewModel.handleSavedItemAction(
params.item.savedItemId,
it
)
}
)
}
}
}
},
)
}
) { paddingValues ->
if (styledContent != null) {
WebReader(
styledContent = styledContent,
webReaderViewModel = webReaderViewModel
)
}
ReaderTopAppBar(webReaderViewModel, onLibraryIconTap)
}) { paddingValues ->
if (styledContent != null) {
WebReader(
styledContent = styledContent,
webReaderViewModel = webReaderViewModel
)
}
LaunchedEffect(shouldPopView) {
if (shouldPopView) {
onBackPressedDispatcher?.onBackPressed()
LaunchedEffect(shouldPopView) {
if (shouldPopView) {
onBackPressedDispatcher?.onBackPressed()
}
}
}
}
}
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Composable
fun ReaderTopAppBar(webReaderViewModel: WebReaderViewModel, onLibraryIconTap: (() -> Unit)? = null) {
val context = LocalContext.current
val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
val isDarkMode = isSystemInDarkTheme()
val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState()
val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value }
val toolbarHeightPx: Float by webReaderViewModel.currentToolbarHeightLiveData.observeAsState(0.0f)
val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(null)
var isMenuExpanded by remember { mutableStateOf(false) }
val themeBackgroundColor = currentTheme?.let {
if (it.themeKey == "System" && isDarkMode) {
Color(0xFF000000)
} else if (it.themeKey == "System" ) {
Color(0xFFFFFFFF)
} else {
Color(it.backgroundColor ?: 0xFFFFFFFF)
}
} ?: Color(0xFFFFFFFF)
val themeTintColor = currentTheme?.let {
if (it.themeKey == "System" && isDarkMode) {
Color(0xFFFFFFFF)
} else if (it.themeKey == "System" ) {
Color(0xFF000000)
} else {
Color(it.foregroundColor ?: 0xFF000000)
}
} ?: Color(0xFF000000)
TopAppBar(
modifier = Modifier
.height(height = with(LocalDensity.current) {
toolbarHeightPx.roundToInt().toDp()
}),
backgroundColor = themeBackgroundColor,
elevation = 0.dp,
title = {},
navigationIcon = {
IconButton(onClick = {
onBackPressedDispatcher?.onBackPressed()
}) {
Icon(
imageVector = Icons.Filled.ArrowBack,
modifier = Modifier,
contentDescription = "Back",
tint = themeTintColor
)
}
},
actions = {
if (onLibraryIconTap != null) {
IconButton(onClick = { onLibraryIconTap() }) {
Icon(
imageVector = Icons.Default.Home,
contentDescription = null,
tint = themeTintColor,
)
}
}
webReaderParams?.let {
IconButton(onClick = {
webReaderViewModel.setBottomSheet(BottomSheetState.NOTEBOOK)
}) {
Icon(
painter = painterResource(id = R.drawable.notebook),
contentDescription = null,
tint = themeTintColor
)
}
}
IconButton(onClick = {
webReaderViewModel.setBottomSheet(BottomSheetState.PREFERENCES)
}) {
Icon(
painter = painterResource(id = R.drawable.format_letter_case),
contentDescription = null,
tint = themeTintColor
)
}
IconButton(onClick = { isMenuExpanded = true }) {
Icon(
painter = painterResource(id = R.drawable.dots_horizontal),
contentDescription = null,
tint = themeTintColor
)
if (isMenuExpanded) {
webReaderParams?.let { params ->
SavedItemContextMenu(
context = context,
isExpanded = isMenuExpanded,
isArchived = params.item.isArchived,
onDismiss = { isMenuExpanded = false },
webReaderViewModel = webReaderViewModel,
actionHandler = {
webReaderViewModel.handleSavedItemAction(
params.item.savedItemId,
it
)
}
)
}
}
}
},
)
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
@ -383,10 +429,44 @@ fun BottomSheetUI(title: String?, content: @Composable () -> Unit) {
) {
Scaffold(
) { paddingValues ->
Box(modifier = Modifier
.fillMaxSize()) {
Box(modifier = Modifier.fillMaxSize()) {
content()
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
fun OpenLinkView(webReaderViewModel: WebReaderViewModel) {
val context = LocalContext.current
Column(modifier = Modifier
.padding(top = 50.dp)
.padding(horizontal = 50.dp), verticalArrangement = Arrangement.spacedBy(20.dp)) {
Row {
Button(onClick = { webReaderViewModel.openCurrentLink(context) }, modifier = Modifier.fillMaxWidth()) {
Text(text = "Open in Browser")
}
}
Row() {
Button(onClick = { webReaderViewModel.saveCurrentLink(context) }, modifier = Modifier.fillMaxWidth()) {
Text(text = "Save to Omnivore")
}
}
Row() {
Button(onClick = {webReaderViewModel.copyCurrentLink(context) }, modifier = Modifier.fillMaxWidth()) {
Text(text = "Copy Link")
}
}
Row {
Button(onClick = {webReaderViewModel.resetBottomSheet() }, modifier = Modifier.fillMaxWidth()) {
Text(text = "Cancel")
}
}
}
}

View file

@ -1,23 +1,25 @@
package app.omnivore.omnivore.ui.reader
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.compose.foundation.ScrollState
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import android.widget.Toast
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat.startActivity
import androidx.lifecycle.*
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.dataService.*
import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput
import app.omnivore.omnivore.graphql.generated.type.SetLabelsInput
import app.omnivore.omnivore.persistence.entities.SavedItem
import app.omnivore.omnivore.networking.*
import app.omnivore.omnivore.persistence.entities.SavedItem
import app.omnivore.omnivore.persistence.entities.SavedItemAndSavedItemLabelCrossRef
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.ui.library.SavedItemAction
import com.apollographql.apollo3.api.Optional
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
@ -26,6 +28,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import java.util.*
import javax.inject.Inject
data class WebReaderParams(
val item: SavedItem,
val articleContent: ArticleContent,
@ -56,14 +59,16 @@ class WebReaderViewModel @Inject constructor(
var maxToolbarHeightPx = 0.0f
val webReaderParamsLiveData = MutableLiveData<WebReaderParams?>(null)
val annotationLiveData = MutableLiveData<String?>(null)
var annotation: String? = null
val javascriptActionLoopUUIDLiveData = MutableLiveData(lastJavascriptActionLoopUUID)
val shouldPopViewLiveData = MutableLiveData(false)
val hasFetchError = MutableLiveData(false)
val currentToolbarHeightLiveData = MutableLiveData(0.0f)
val showLabelsSelectionSheetLiveData = MutableLiveData(false)
val savedItemLabelsLiveData = dataService.db.savedItemLabelDao().getSavedItemLabelsLiveData()
var currentLink: Uri? = null
val bottomSheetStateLiveData = MutableLiveData<BottomSheetState>(BottomSheetState.NONE)
var hasTappedExistingHighlight = false
var lastTapCoordinates: TapCoordinates? = null
private var isLoading = false
@ -85,6 +90,72 @@ class WebReaderViewModel @Inject constructor(
onScrollChange(maxToolbarHeightPx)
}
fun setBottomSheet(state: BottomSheetState) {
bottomSheetStateLiveData.postValue(state)
}
fun resetBottomSheet() {
bottomSheetStateLiveData.postValue(BottomSheetState.NONE)
}
fun showOpenLinkSheet(context: Context, uri: Uri) {
webReaderParamsLiveData.value?.let {
if (it.item.pageURLString == uri.toString()) {
openLink(context, uri)
} else {
currentLink = uri
bottomSheetStateLiveData.postValue(BottomSheetState.LINK)
}
}
}
fun showShareLinkSheet(context: Context) {
webReaderParamsLiveData.value?.let {
val browserIntent = Intent(Intent.ACTION_SEND)
browserIntent.setType("text/plain")
browserIntent.putExtra(Intent.EXTRA_TEXT, it.item.pageURLString)
browserIntent.putExtra(Intent.EXTRA_SUBJECT, it.item.pageURLString)
context.startActivity(browserIntent)
}
}
fun openCurrentLink(context: Context) {
currentLink?.let {
openLink(context, it)
}
bottomSheetStateLiveData.postValue(BottomSheetState.NONE)
}
fun openLink(context: Context, uri: Uri) {
val browserIntent = Intent(Intent.ACTION_VIEW, uri)
startActivity(context, browserIntent, null)
}
fun saveCurrentLink(context: Context) {
currentLink?.let {
viewModelScope.launch {
val success = networker.saveUrl(it)
Toast.makeText(context, if (success) "Link saved" else "Error saving link" , Toast.LENGTH_SHORT).show()
}
}
bottomSheetStateLiveData.postValue(BottomSheetState.NONE)
}
fun copyCurrentLink(context: Context) {
currentLink?.let {
val clip = ClipData.newPlainText("link", it.toString())
val clipboard =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(clip)
clipboard?.let {
clipboard?.setPrimaryClip(clip)
Toast.makeText(context, "Link Copied", Toast.LENGTH_SHORT).show()
}
}
bottomSheetStateLiveData.postValue(BottomSheetState.NONE)
}
fun onScrollChange(delta: Float) {
val newHeight = (currentToolbarHeightLiveData.value ?: 0.0f) + delta
currentToolbarHeightLiveData.value = newHeight.coerceIn(0f, maxToolbarHeightPx)
@ -184,7 +255,7 @@ class WebReaderViewModel @Inject constructor(
}
}
SavedItemAction.EditLabels -> {
showLabelsSelectionSheetLiveData.value = true
bottomSheetStateLiveData.postValue(BottomSheetState.LABELS)
}
}
}
@ -221,10 +292,11 @@ class WebReaderViewModel @Inject constructor(
}
"annotate" -> {
viewModelScope.launch {
val annotation = Gson()
val annotationStr = Gson()
.fromJson(jsonString, AnnotationWebViewMessage::class.java)
.annotation ?: ""
annotationLiveData.value = annotation
annotation = annotationStr
bottomSheetStateLiveData.postValue(BottomSheetState.HIGHLIGHTNOTE)
}
}
"shareHighlight" -> {
@ -253,7 +325,8 @@ class WebReaderViewModel @Inject constructor(
}
fun cancelAnnotationEdit() {
annotationLiveData.value = null
annotation = null
resetBottomSheet()
}
private fun enqueueScript(javascript: String) {
@ -396,7 +469,7 @@ class WebReaderViewModel @Inject constructor(
viewModelScope.launch {
withContext(Dispatchers.IO) {
val newLabel = networker.createNewLabel(CreateLabelInput(color = Optional.presentIfNotNull(hexColorValue), name = labelName))
val newLabel = networker.createNewLabel(CreateLabelInput(color = presentIfNotNull(hexColorValue), name = labelName))
newLabel?.let {
val savedItemLabel = SavedItemLabel(

View file

@ -4,23 +4,47 @@ import android.content.ContentValues
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.TextView.SavedState
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.rounded.AddCircle
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material3.BottomAppBarDefaults
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment.Companion.TopCenter
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.library.SavedItemAction
import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds
// Not sure why we need this class, but directly opening SaveSheetActivity
// causes the app to crash.
@ -56,36 +80,49 @@ abstract class SaveSheetActivityBase: AppCompatActivity() {
}
setContent {
val coroutineScope = rememberCoroutineScope()
val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val isSheetOpened = remember { mutableStateOf(false) }
val saveState: SaveState by viewModel.saveState.observeAsState(SaveState.NONE)
val scaffoldState: ScaffoldState = rememberScaffoldState()
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Transparent,
sheetState = modalBottomSheetState,
sheetContent = {
BottomSheetUI {
ScreenContent(viewModel, modalBottomSheetState)
}
}
) {}
BackHandler {
onFinish(coroutineScope, modalBottomSheetState)
val message = when (saveState) {
SaveState.NONE -> ""
SaveState.SAVING -> "Saved to Omnivore"
SaveState.ERROR -> "Error Saving Article"
SaveState.SAVED -> "Saved to Omnivore"
}
// Take action based on hidden state
LaunchedEffect(modalBottomSheetState.currentValue) {
when (modalBottomSheetState.currentValue) {
ModalBottomSheetValue.Hidden -> {
handleBottomSheetAtHiddenState(
isSheetOpened,
modalBottomSheetState
)
}
else -> {
Log.i(TAG, "Bottom sheet ${modalBottomSheetState.currentValue} state")
}
Scaffold(
modifier = Modifier.clickable {
Log.d("debug", "DISMISS SCAFFOLD")
exit()
},
scaffoldState = scaffoldState,
backgroundColor = Color.Transparent,
// TODO: In future versions we can present Label, Note, Highlight options here
bottomBar = {
androidx.compose.material3.BottomAppBar(
modifier = Modifier
.height(55.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(topEnd = 5.dp, topStart = 5.dp)),
containerColor = MaterialTheme.colors.background,
actions = {
Spacer(modifier = Modifier.width(25.dp))
Text(message, style = androidx.compose.material3.MaterialTheme.typography.titleMedium)
},
)
},
) {
}
LaunchedEffect(saveState) {
if (saveState == SaveState.SAVED) {
delay(1.5.seconds)
exit()
}
}
}

View file

@ -5,6 +5,7 @@ import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.Constants
@ -17,13 +18,21 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.*
import java.util.regex.Pattern
import javax.inject.Inject
enum class SaveState {
NONE(),
SAVING(),
ERROR(),
SAVED()
}
@HiltViewModel
class SaveViewModel @Inject constructor(
private val datastoreRepo: DatastoreRepository
): ViewModel() {
val saveState = MutableLiveData(SaveState.NONE)
var isLoading by mutableStateOf(false)
private set
@ -46,6 +55,7 @@ class SaveViewModel @Inject constructor(
viewModelScope.launch {
isLoading = true
message = "Saving to Omnivore..."
saveState.postValue(SaveState.SAVING)
val authToken = getAuthToken()
@ -85,6 +95,7 @@ class SaveViewModel @Inject constructor(
"There was an error saving your page"
}
saveState.postValue(SaveState.SAVED)
Log.d(ContentValues.TAG, "Saved URL?: $success")
} catch (e: java.lang.Exception) {
message = "There was an error saving your page"

View file

@ -16,21 +16,22 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.unit.*
import app.omnivore.omnivore.R
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.ui.components.LabelChipColors
import app.omnivore.omnivore.ui.library.LibraryViewModel
import app.omnivore.omnivore.ui.library.SavedItemAction
import app.omnivore.omnivore.ui.library.SavedItemViewModel
import coil.compose.rememberAsyncImagePainter
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class,
)
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
@Composable
fun SavedItemCard(savedItemViewModel: SavedItemViewModel, savedItem: SavedItemWithLabelsAndHighlights, onClickHandler: () -> Unit, actionHandler: (SavedItemAction) -> Unit) {
val listState = rememberLazyListState()
@ -64,6 +65,7 @@ fun SavedItemCard(savedItemViewModel: SavedItemViewModel, savedItem: SavedItemWi
text = savedItem.savedItem.title,
style = TextStyle(
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onBackground,
fontWeight = FontWeight.SemiBold
),
maxLines = 2,
@ -95,23 +97,14 @@ fun SavedItemCard(savedItemViewModel: SavedItemViewModel, savedItem: SavedItemWi
)
}
LazyRow(
state = listState,
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(start = 5.dp, bottom = 5.dp, end = 10.dp)
) {
items(savedItem.labels.sortedBy { it.name }) { label ->
FlowRow(modifier = Modifier.fillMaxWidth().padding(10.dp)) {
savedItem.labels.sortedWith(compareBy { it.name.toLowerCase(Locale.current) }).forEach { label ->
val chipColors = LabelChipColors.fromHex(label.color)
LabelChip(
// onClick = onClickHandler,
name = label.name,
colors = chipColors,
// modifier = Modifier.padding(end = 5.dp)
)
}
}

View file

@ -1,22 +1,28 @@
package app.omnivore.omnivore.ui.savedItemViews
import android.content.Context
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.List
import androidx.compose.material.icons.outlined.Share
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.library.SavedItemAction
import app.omnivore.omnivore.ui.reader.WebReaderViewModel
@Composable
fun SavedItemContextMenu(
isExpanded: Boolean,
isArchived: Boolean,
context: Context,
webReaderViewModel: WebReaderViewModel,
onDismiss: () -> Unit,
actionHandler: (SavedItemAction) -> Unit
) {
@ -24,19 +30,19 @@ fun SavedItemContextMenu(
expanded = isExpanded,
onDismissRequest = onDismiss
) {
// DropdownMenuItem(
// text = { Text("Edit Labels") },
// onClick = {
// actionHandler(SavedItemAction.EditLabels)
// onDismiss()
// },
// leadingIcon = {
// Icon(
// painter = painterResource(id = R.drawable.tag),
// contentDescription = null
// )
// }
// )
DropdownMenuItem(
text = { Text("Edit Labels") },
onClick = {
actionHandler(SavedItemAction.EditLabels)
onDismiss()
},
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.tag),
contentDescription = null
)
}
)
DropdownMenuItem(
text = { Text(if (isArchived) "Unarchive" else "Archive") },
onClick = {
@ -51,6 +57,19 @@ fun SavedItemContextMenu(
)
}
)
DropdownMenuItem(
text = { Text("Share Original") },
onClick = {
webReaderViewModel.showShareLinkSheet(context)
onDismiss()
},
leadingIcon = {
Icon(
Icons.Outlined.Share,
contentDescription = null
)
}
)
DropdownMenuItem(
text = { Text("Remove Item") },
onClick = {

View file

@ -9,62 +9,12 @@ import androidx.compose.ui.platform.LocalContext
private val LightColors = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
)
private val DarkColors = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
)
@Composable
@ -73,12 +23,7 @@ fun OmnivoreTheme(
useDynamicTheme: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S,
content: @Composable () -> Unit
) {
val colorScheme = if (useDynamicTheme) {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
} else {
if (darkTheme) darkColorScheme() else lightColorScheme()
}
val colorScheme = if (darkTheme) DarkColors else LightColors
MaterialTheme(
colorScheme = colorScheme,