mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
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:
commit
6e71e964bd
18 changed files with 968 additions and 493 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue