Merge pull request #3333 from omnivore-app/fix/android-library-labels

Open the labels sheet from the library on Android
This commit is contained in:
Jackson Harper 2024-01-09 19:24:13 +08:00 committed by GitHub
commit 535a5cbd9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 422 additions and 229 deletions

View file

@ -19,8 +19,8 @@ android {
applicationId "app.omnivore.omnivore"
minSdk 26
targetSdk 33
versionCode 170
versionName "0.0.170"
versionCode 178
versionName "0.0.178"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

File diff suppressed because one or more lines are too long

View file

@ -1,12 +1,17 @@
package app.omnivore.omnivore.dataService
import android.util.Log
import app.omnivore.omnivore.graphql.generated.type.HighlightType
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.networking.*
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.persistence.entities.SavedItemAndHighlightCrossRef
import app.omnivore.omnivore.persistence.entities.saveHighlightChange
import com.apollographql.apollo3.api.Optional
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.withContext
import java.util.*
@ -33,6 +38,8 @@ suspend fun DataService.createWebHighlight(jsonString: String, colorName: String
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
val highlightChange = saveHighlightChange(db.highlightChangesDao(), createHighlightInput.articleId, highlight)
val crossRef = SavedItemAndHighlightCrossRef(
highlightId = createHighlightInput.id,
savedItemId = createHighlightInput.articleId
@ -41,11 +48,7 @@ suspend fun DataService.createWebHighlight(jsonString: String, colorName: String
db.highlightDao().insertAll(listOf(highlight))
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
val newHighlight = networker.createHighlight(createHighlightInput)
newHighlight?.let {
db.highlightDao().update(it)
}
performHighlightChange(highlightChange)
}
}
@ -73,6 +76,8 @@ suspend fun DataService.createNoteHighlight(savedItemId: String, note: String):
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
val highlightChange = saveHighlightChange(db.highlightChangesDao(), savedItemId, highlight)
val crossRef = SavedItemAndHighlightCrossRef(
highlightId = createHighlightId,
savedItemId = savedItemId
@ -81,21 +86,7 @@ suspend fun DataService.createNoteHighlight(savedItemId: String, note: String):
db.highlightDao().insertAll(listOf(highlight))
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
val newHighlight = networker.createHighlight(input = CreateHighlightParams(
type = HighlightType.NOTE,
articleId = savedItemId,
id = createHighlightId,
shortId = shortId,
quote = null,
patch = null,
annotation = note,
highlightPositionAnchorIndex = 0,
highlightPositionPercent = 0.0
).asCreateHighlightInput())
newHighlight?.let {
db.highlightDao().update(it)
}
performHighlightChange(highlightChange)
}
return createHighlightId
@ -103,18 +94,33 @@ suspend fun DataService.createNoteHighlight(savedItemId: String, note: String):
suspend fun DataService.mergeWebHighlights(jsonString: String) {
val mergeHighlightInput = Gson().fromJson(jsonString, MergeHighlightsParams::class.java).asMergeHighlightInput()
Log.d("sync", "mergeHighlightInput: " + mergeHighlightInput.id + ": " + mergeHighlightInput)
withContext(Dispatchers.IO) {
val highlight = db.highlightDao().findById(highlightId = mergeHighlightInput.id) ?: return@withContext
highlight.shortId = mergeHighlightInput.shortId
highlight.quote = mergeHighlightInput.quote
highlight.patch = mergeHighlightInput.patch
highlight.prefix = mergeHighlightInput.prefix.getOrNull()
highlight.annotation = mergeHighlightInput.annotation.getOrNull()
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_UPDATE.rawValue
val highlight = Highlight(
type = "HIGHLIGHT",
highlightId = mergeHighlightInput.id,
shortId = mergeHighlightInput.shortId,
quote = mergeHighlightInput.quote,
prefix = null,
suffix = null,
patch = mergeHighlightInput.patch,
annotation = mergeHighlightInput.annotation.getOrNull(),
createdAt = null,
updatedAt = null,
createdByMe = false,
color = mergeHighlightInput.color.getOrNull(),
highlightPositionPercent = mergeHighlightInput.highlightPositionPercent.getOrNull() ?: 0.0,
highlightPositionAnchorIndex = mergeHighlightInput.highlightPositionAnchorIndex.getOrNull() ?: 0
)
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
saveHighlightChange(db.highlightChangesDao(), mergeHighlightInput.articleId, highlight)
Log.d("sync", "overlapHighlightIdList: " + mergeHighlightInput.overlapHighlightIdList)
for (highlightID in mergeHighlightInput.overlapHighlightIdList) {
deleteHighlight(highlightID)
deleteHighlight(mergeHighlightInput.articleId, highlightID)
}
val crossRef = SavedItemAndHighlightCrossRef(
@ -122,11 +128,10 @@ suspend fun DataService.mergeWebHighlights(jsonString: String) {
savedItemId = mergeHighlightInput.articleId
)
db.highlightDao().insertAll(listOf(highlight))
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
db.highlightDao().update(highlight)
val isUpdatedOnServer = networker.mergeHighlights(mergeHighlightInput)
if (isUpdatedOnServer) {
highlight.serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue
db.highlightDao().update(highlight)
@ -135,42 +140,40 @@ suspend fun DataService.mergeWebHighlights(jsonString: String) {
}
suspend fun DataService.updateWebHighlight(jsonString: String) {
val updateHighlightParams = Gson().fromJson(jsonString, UpdateHighlightParams::class.java).asUpdateHighlightInput()
val updateHighlightParams = Gson().fromJson(jsonString, UpdateHighlightParams::class.java)
if (updateHighlightParams.highlightId == null || updateHighlightParams.libraryItemId == null) {
Log.d("error","ERROR INVALID HIGHLIGHT DATA")
return
}
withContext(Dispatchers.IO) {
val highlight = db.highlightDao().findById(highlightId = updateHighlightParams.highlightId) ?: return@withContext
val highlight = db.highlightDao().findById(highlightId = updateHighlightParams.highlightId ?: "") ?: return@withContext
highlight.annotation = updateHighlightParams.annotation.getOrNull()
highlight.annotation = updateHighlightParams.annotation
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_UPDATE.rawValue
db.highlightDao().update(highlight)
val isUpdatedOnServer = networker.updateHighlight(updateHighlightParams)
if (isUpdatedOnServer) {
highlight.serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue
db.highlightDao().update(highlight)
}
val highlightChange = saveHighlightChange(db.highlightChangesDao(), updateHighlightParams.libraryItemId ?: "", highlight)
performHighlightChange(highlightChange)
}
}
suspend fun DataService.deleteHighlights(jsonString: String) {
val highlightIDs = Gson().fromJson(jsonString, DeleteHighlightParams::class.java).asIdList()
for (highlightID in highlightIDs) {
deleteHighlight(highlightID)
}
suspend fun DataService.deleteHighlightFromJSON(jsonString: String) {
val deleteHighlightParams = Gson().fromJson(jsonString, DeleteHighlightParams::class.java)
deleteHighlight(deleteHighlightParams.libraryItemId, deleteHighlightParams.highlightId)
}
private suspend fun DataService.deleteHighlight(highlightID: String) {
private suspend fun DataService.deleteHighlight(savedItemId: String, highlightID: String) {
withContext(Dispatchers.IO) {
val highlight = db.highlightDao().findById(highlightId = highlightID) ?: return@withContext
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_DELETION.rawValue
db.highlightDao().update(highlight)
val highlight = db.highlightDao().findById(highlightId = highlightID)
val isUpdatedOnServer = networker.deleteHighlights(listOf(highlightID))
highlight?.let {
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_DELETION.rawValue
db.highlightDao().update(highlight)
if (isUpdatedOnServer) {
db.highlightDao().deleteById(highlightId = highlightID)
val highlightChange = saveHighlightChange(db.highlightChangesDao(), savedItemId, highlight)
performHighlightChange(highlightChange)
}
}
}

View file

@ -1,36 +1,44 @@
package app.omnivore.omnivore.dataService
import android.util.Log
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.networking.*
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.persistence.entities.HighlightChange
import app.omnivore.omnivore.persistence.entities.SavedItem
import app.omnivore.omnivore.persistence.entities.highlightChangeToHighlight
import com.apollographql.apollo3.api.Optional
import kotlinx.coroutines.delay
import kotlin.math.log
suspend fun DataService.startSyncChannels() {
Log.d("sync", "Starting sync channels")
for (savedItem in savedItemSyncChannel) {
syncSavedItem(savedItem)
}
}
for (highlight in highlightSyncChannel) {
syncHighlight(highlight)
suspend fun DataService.performHighlightChange(highlightChange: HighlightChange) {
val highlight = highlightChangeToHighlight(highlightChange)
if (syncHighlightChange(highlightChange)) {
db.highlightChangesDao().deleteById(highlight.highlightId)
}
}
suspend fun DataService.syncOfflineItemsWithServerIfNeeded() {
val unSyncedSavedItems = db.savedItemDao().getUnSynced()
val unSyncedHighlights = db.highlightDao().getUnSynced()
val unSyncedHighlights = db.highlightChangesDao().getUnSynced()
for (savedItem in unSyncedSavedItems) {
delay(250)
savedItemSyncChannel.send(savedItem)
}
for (highlight in unSyncedHighlights) {
delay(250)
highlightSyncChannel.send(highlight)
for (change in unSyncedHighlights) {
performHighlightChange(change)
}
}
@ -82,7 +90,9 @@ private suspend fun DataService.syncSavedItem(item: SavedItem) {
}
}
private suspend fun DataService.syncHighlight(highlight: Highlight) {
private suspend fun DataService.syncHighlightChange(highlightChange: HighlightChange): Boolean {
val highlight = highlightChangeToHighlight(highlightChange)
fun updateSyncStatus(status: ServerSyncStatus) {
highlight.serverSyncStatus = status.rawValue
db.highlightDao().update(highlight)
@ -91,7 +101,6 @@ private suspend fun DataService.syncHighlight(highlight: Highlight) {
when (highlight.serverSyncStatus) {
ServerSyncStatus.NEEDS_DELETION.rawValue -> {
updateSyncStatus(ServerSyncStatus.IS_SYNCING)
val isDeletedOnServer = networker.deleteHighlights(listOf(highlight.highlightId))
if (isDeletedOnServer) {
@ -99,8 +108,12 @@ private suspend fun DataService.syncHighlight(highlight: Highlight) {
} else {
updateSyncStatus(ServerSyncStatus.NEEDS_DELETION)
}
return isDeletedOnServer != null
}
ServerSyncStatus.NEEDS_UPDATE.rawValue -> {
Log.d("sync", "creating highlight update change: ${highlightChange}")
updateSyncStatus(ServerSyncStatus.IS_SYNCING)
val isUpdatedOnServer = networker.updateHighlight(
@ -110,36 +123,40 @@ private suspend fun DataService.syncHighlight(highlight: Highlight) {
sharedAt = Optional.absent()
)
)
Log.d("sync", "sycn.updateHighlight result: ${isUpdatedOnServer}")
if (isUpdatedOnServer) {
updateSyncStatus(ServerSyncStatus.IS_SYNCED)
} else {
updateSyncStatus(ServerSyncStatus.NEEDS_UPDATE)
}
return isUpdatedOnServer != null
}
ServerSyncStatus.NEEDS_CREATION.rawValue -> {
Log.d("sync", "creating highlight create change: ${highlightChange}")
updateSyncStatus(ServerSyncStatus.IS_SYNCING)
val savedItemID = db.savedItemAndHighlightCrossRefDao()
.associatedSavedItemID(highlightId = highlight.highlightId)
val isCreatedOnServer = networker.createHighlight(
val createResult = networker.createHighlight(
CreateHighlightInput(
annotation = Optional.presentIfNotNull(highlight.annotation),
articleId = savedItemID ?: "",
articleId = highlightChange.savedItemId,
id = highlight.highlightId,
patch = Optional.presentIfNotNull(highlight.patch),
quote = Optional.presentIfNotNull(highlight.quote),
shortId = highlight.shortId
)
)
Log.d("sync", "sycn.createResult: " + createResult)
if (isCreatedOnServer != null) {
if (createResult.newHighlight != null || createResult.alreadyExists) {
updateSyncStatus(ServerSyncStatus.IS_SYNCED)
return true
} else {
updateSyncStatus(ServerSyncStatus.NEEDS_UPDATE)
return false
}
}
else -> return
else -> return false
}
}

View file

@ -5,6 +5,7 @@ import app.omnivore.omnivore.graphql.generated.CreateHighlightMutation
import app.omnivore.omnivore.graphql.generated.DeleteHighlightMutation
import app.omnivore.omnivore.graphql.generated.MergeHighlightMutation
import app.omnivore.omnivore.graphql.generated.UpdateHighlightMutation
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightErrorCode
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
import app.omnivore.omnivore.graphql.generated.type.HighlightType
import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput
@ -39,6 +40,7 @@ data class CreateHighlightParams(
data class UpdateHighlightParams(
val highlightId: String?,
val libraryItemId: String?,
val `annotation`: String?,
val sharedAt: String?,
) {
@ -73,9 +75,10 @@ data class MergeHighlightsParams(
}
data class DeleteHighlightParams(
val highlightId: String?
val highlightId: String,
val libraryItemId: String
) {
fun asIdList() = listOf(highlightId ?: "")
fun asIdList() = listOf(highlightId)
}
suspend fun Networker.deleteHighlight(jsonString: String): Boolean {
@ -134,18 +137,26 @@ suspend fun Networker.createWebHighlight(jsonString: String): Boolean {
return createHighlight(input) != null
}
suspend fun Networker.createHighlight(input: CreateHighlightInput): Highlight? {
Log.d("Loggo", "created highlight input: $input")
data class CreateHighlightResult(
val failedToCreate: Boolean,
val alreadyExists: Boolean,
val newHighlight: Highlight?
)
suspend fun Networker.createHighlight(input: CreateHighlightInput): CreateHighlightResult {
Log.d("sync", "creating highlight with input: ${input}")
try {
val result = authenticatedApolloClient().mutation(CreateHighlightMutation(input)).execute()
Log.d("Loggo", "result: ${result.data}")
Log.d("sync", "result: ${result.data}")
val createdHighlight = result.data?.createHighlight?.onCreateHighlightSuccess?.highlight
if (createdHighlight != null) {
return Highlight(
return CreateHighlightResult(
failedToCreate = false,
alreadyExists = false,
newHighlight = Highlight(
type = createdHighlight.highlightFields.type.toString(),
highlightId = createdHighlight.highlightFields.id,
shortId = createdHighlight.highlightFields.shortId,
@ -161,10 +172,22 @@ suspend fun Networker.createHighlight(input: CreateHighlightInput): Highlight? {
highlightPositionPercent = createdHighlight.highlightFields.highlightPositionPercent,
highlightPositionAnchorIndex = createdHighlight.highlightFields.highlightPositionAnchorIndex
)
)
} else {
return null
if (result.data?.createHighlight?.onCreateHighlightError?.errorCodes?.first() == CreateHighlightErrorCode.ALREADY_EXISTS) {
return CreateHighlightResult(
failedToCreate = false,
alreadyExists = true,
newHighlight = null
)
}
}
} catch (e: java.lang.Exception) {
return null
Log.d("sync", "error creating highlight: " +e)
}
return CreateHighlightResult(
failedToCreate = true,
alreadyExists = false,
newHighlight = null
)
}

View file

@ -10,15 +10,17 @@ import app.omnivore.omnivore.persistence.entities.*
SavedItem::class,
SavedItemLabel::class,
Highlight::class,
HighlightChange::class,
SavedItemAndSavedItemLabelCrossRef::class,
SavedItemAndHighlightCrossRef::class
],
version = 15
version = 20
)
abstract class AppDatabase : RoomDatabase() {
abstract fun viewerDao(): ViewerDao
abstract fun savedItemDao(): SavedItemDao
abstract fun highlightDao(): HighlightDao
abstract fun highlightChangesDao(): HighlightChangesDao
abstract fun savedItemLabelDao(): SavedItemLabelDao
abstract fun savedItemWithLabelsAndHighlightsDao(): SavedItemWithLabelsAndHighlightsDao
abstract fun savedItemAndSavedItemLabelCrossRefDao(): SavedItemAndSavedItemLabelCrossRefDao

View file

@ -0,0 +1,90 @@
package app.omnivore.omnivore.persistence.entities
import android.util.Log
import androidx.room.Dao
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import app.omnivore.omnivore.models.ServerSyncStatus
import com.google.gson.annotations.SerializedName
@Entity
data class HighlightChange(
@PrimaryKey val highlightId: String,
val savedItemId: String,
val type: String,
var annotation: String?,
val createdAt: String?,
val createdByMe: Boolean = true,
val markedForDeletion: Boolean = false,
var patch: String?,
var prefix: String?,
var quote: String?,
var serverSyncStatus: Int = ServerSyncStatus.IS_SYNCED.rawValue,
var shortId: String,
val suffix: String?,
val updatedAt: String?,
val color: String?,
val highlightPositionPercent: Double?,
val highlightPositionAnchorIndex: Int?
)
fun saveHighlightChange(dao: HighlightChangesDao, savedItemId: String, highlight: Highlight): HighlightChange {
Log.d("sync", "saving highlight change: " + savedItemId + ", " + highlight)
val change = HighlightChange(
savedItemId = savedItemId,
highlightId = highlight.highlightId,
type = highlight.type,
shortId = highlight.shortId,
quote = highlight.quote,
prefix = highlight.prefix,
suffix = highlight.suffix,
patch = highlight.patch,
annotation = highlight.annotation,
createdAt = highlight.createdAt,
updatedAt = highlight.updatedAt,
createdByMe = highlight.createdByMe,
color =highlight.color,
highlightPositionPercent = highlight.highlightPositionPercent,
highlightPositionAnchorIndex = highlight.highlightPositionAnchorIndex,
serverSyncStatus = highlight.serverSyncStatus
)
dao.insertAll(listOf(change))
return change
}
fun highlightChangeToHighlight(change: HighlightChange): Highlight {
return Highlight(
highlightId = change.highlightId,
type = change.type,
shortId = change.shortId,
quote = change.quote,
prefix = change.prefix,
suffix = change.suffix,
patch = change.patch,
annotation = change.annotation,
createdAt = change.createdAt,
updatedAt = change.updatedAt,
createdByMe = change.createdByMe,
color = change.color,
highlightPositionPercent = change.highlightPositionPercent,
highlightPositionAnchorIndex = change.highlightPositionAnchorIndex,
serverSyncStatus = change.serverSyncStatus
)
}
@Dao
interface HighlightChangesDao {
@Query("SELECT * FROM highlightChange WHERE serverSyncStatus != 0 ORDER BY updatedAt ASC")
fun getUnSynced(): List<HighlightChange>
@Query("DELETE FROM highlightChange WHERE highlightId = :highlightId")
fun deleteById(highlightId: String)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(items: List<HighlightChange>)
}

View file

@ -27,6 +27,7 @@ import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.save.SaveState
import app.omnivore.omnivore.ui.save.SaveViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddLinkSheetContent(
viewModel: SaveViewModel,
@ -84,42 +85,47 @@ fun AddLinkSheetContent(
viewModel.saveURL(url)
}
Surface(
androidx.compose.material.Scaffold(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
) {
.background(MaterialTheme.colorScheme.primaryContainer),
topBar = {
CenterAlignedTopAppBar(
title = {
Text(stringResource(R.string.add_link_sheet_title))
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
),
navigationIcon = {
TextButton(onClick = onCancel) {
Text(text = stringResource(R.string.label_selection_sheet_action_cancel))
}
},
actions = {
TextButton(onClick = { addLink(textFieldValue.text) }) {
Text(stringResource(R.string.add_link_sheet_action_add_link))
}
}
)
}
) { paddingValues ->
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 5.dp)
.background(MaterialTheme.colorScheme.background)
.padding(horizontal = 10.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
) {
TextButton(onClick = onCancel) {
Text(text = stringResource(R.string.add_link_sheet_action_cancel))
}
Text(stringResource(R.string.add_link_sheet_title), fontWeight = FontWeight.ExtraBold)
TextButton(onClick = { addLink(textFieldValue.text) }) {
Text(stringResource(R.string.add_link_sheet_action_add_link))
}
}
if (isSaving.value == true) {
Spacer(modifier = Modifier.width(16.dp))
CircularProgressIndicator(
modifier = Modifier
.height(16.dp)
.width(16.dp),
strokeWidth = 2.dp,
color = MaterialTheme.colorScheme.primary
)
@ -129,18 +135,29 @@ fun AddLinkSheetContent(
value = textFieldValue,
placeholder = { Text(stringResource(R.string.add_link_sheet_text_field_placeholder)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
leadingIcon = { Icon(imageVector = Icons.Default.Link, contentDescription = "linkIcon") },
leadingIcon = {
Icon(
imageVector = Icons.Default.Link,
contentDescription = "linkIcon"
)
},
onValueChange = { textFieldValue = it },
modifier = Modifier.focusRequester(focusRequester).padding(top = 24.dp).fillMaxWidth()
modifier = Modifier
.focusRequester(focusRequester)
.padding(top = 24.dp)
.padding(horizontal = 10.dp)
.fillMaxWidth()
)
if (clipboardText != null) {
Button(
modifier = Modifier.padding(top = 10.dp),
modifier = Modifier.padding(top = 10.dp) .padding(horizontal = 10.dp)
,
onClick = {
textFieldValue = TextFieldValue(
text = clipboardText,
selection = TextRange(clipboardText.length))
selection = TextRange(clipboardText.length)
)
}
) {
Text(stringResource(R.string.add_link_sheet_action_paste_from_clipboard))

View file

@ -8,6 +8,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
@ -20,15 +21,22 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Scaffold
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddCircle
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
@ -49,6 +57,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.intl.Locale
@ -56,6 +65,7 @@ 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.unit.sp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import com.dokar.chiptextfield.Chip
@ -279,40 +289,43 @@ fun LabelsSelectionSheetContent(
stringResource(R.string.label_selection_sheet_title) else
stringResource(R.string.label_selection_sheet_title_alt)
Surface(
Scaffold(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
) {
.background(MaterialTheme.colorScheme.primaryContainer),
topBar = {
CenterAlignedTopAppBar(
title = {
Text(titleText)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
),
navigationIcon = {
TextButton(onClick = onCancel) {
Text(text = stringResource(R.string.label_selection_sheet_action_cancel))
}
},
actions = {
TextButton(onClick = { onSave(state.chips.map { it.label }) }) {
Text(
text = if (isLibraryMode)
stringResource(R.string.label_selection_sheet_action_search) else
stringResource(R.string.label_selection_sheet_action_save)
)
}
}
)
}
) { paddingValues ->
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 5.dp)
.background(MaterialTheme.colorScheme.background)
.padding(horizontal = 10.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
) {
TextButton(onClick = onCancel) {
Text(text = stringResource(R.string.label_selection_sheet_action_cancel))
}
Text(titleText, fontWeight = FontWeight.ExtraBold)
TextButton(onClick = { onSave(state.chips.map { it.label }) }) {
Text(
text = if (isLibraryMode)
stringResource(R.string.label_selection_sheet_action_search) else
stringResource(R.string.label_selection_sheet_action_save)
)
}
}
ChipTextField(
state = state,
value = filterTextValue,
@ -344,12 +357,13 @@ fun LabelsSelectionSheetContent(
),
colors = TextFieldDefaults.textFieldColors(
textColor = MaterialTheme.colorScheme.onBackground,
backgroundColor = MaterialTheme.colorScheme.background
backgroundColor = MaterialTheme.colorScheme.surface
),
contentPadding = PaddingValues(10.dp),
modifier = Modifier
.defaultMinSize(minHeight = 45.dp)
.fillMaxWidth()
.padding(top = 24.dp)
.padding(horizontal = 10.dp)
.focusRequester(focusRequester)
// .onFocusEvent {
@ -397,19 +411,23 @@ fun LabelsSelectionSheetContent(
}
}
.padding(horizontal = 10.dp)
.padding(top = 10.dp, bottom = 5.dp)
.padding(top = 24.dp, bottom = 5.dp)
)
{
Icon(
imageVector = Icons.Filled.AddCircle,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp)
modifier = Modifier.padding(end = 8.dp),
tint = MaterialTheme.colorScheme.onSurface
)
Text(
text = stringResource(
R.string.label_selection_sheet_text_create,
filterTextValue.text.trim()
)
),
style = TextStyle(
color = MaterialTheme.colorScheme.onSurface
),
)
}
}

View file

@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData
import app.omnivore.omnivore.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditInfoSheetContent(
savedItemId: String?,
@ -68,44 +69,47 @@ fun EditInfoSheetContent(
}
}
Surface(
androidx.compose.material.Scaffold(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
) {
.background(MaterialTheme.colorScheme.primaryContainer),
topBar = {
CenterAlignedTopAppBar(
title = {
Text(stringResource(R.string.edit_info_sheet_title))
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
),
navigationIcon = {
TextButton(onClick = onCancel) {
Text(text = stringResource(R.string.edit_info_sheet_action_cancel))
}
},
actions = {
TextButton(onClick = {
val newTitle = titleTextFieldValue.text
val newAuthor = authorTextFieldValue.text.ifEmpty { null }
val newDescription = descriptionTextFieldValue.text.ifEmpty { null }
savedItemId?.let {
viewModel.editInfo(it, newTitle, newAuthor, newDescription)
}
}) {
Text(stringResource(R.string.edit_info_sheet_action_save))
}
}
)
}
) { paddingValues ->
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
.background(MaterialTheme.colorScheme.background)
.padding(horizontal = 10.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
) {
TextButton(onClick = onCancel) {
Text(text = stringResource(R.string.edit_info_sheet_action_cancel))
}
Text(stringResource(R.string.edit_info_sheet_title), fontWeight = FontWeight.ExtraBold)
TextButton(onClick = {
val newTitle = titleTextFieldValue.text
val newAuthor = authorTextFieldValue.text.ifEmpty { null }
val newDescription = descriptionTextFieldValue.text.ifEmpty { null }
savedItemId?.let {
viewModel.editInfo(it, newTitle, newAuthor, newDescription)
}
}) {
Text(stringResource(R.string.edit_info_sheet_action_save))
}
}
if (isUpdating.value == true) {
Spacer(modifier = Modifier.width(16.dp))
CircularProgressIndicator(

View file

@ -20,6 +20,7 @@ import androidx.compose.material.DismissValue
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.FractionalThreshold
import androidx.compose.material.Icon
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldState
import androidx.compose.material.SwipeToDismiss
@ -31,6 +32,7 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material.rememberDismissState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material.rememberScaffoldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
@ -144,14 +146,21 @@ fun showAddLinkBottomSheet(libraryViewModel: LibraryViewModel) {
libraryViewModel.bottomSheetState.value = LibraryBottomSheetState.ADD_LINK
}
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
fun LabelBottomSheet(
libraryViewModel: LibraryViewModel,
labelsViewModel: LabelsViewModel,
onDismiss: () -> Unit = {}
) {
ModalBottomSheet(onDismissRequest = { onDismiss() }) {
ModalBottomSheet(
onDismissRequest = { onDismiss() },
containerColor = MaterialTheme.colorScheme.background,
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
) {
val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit()
val labels: List<SavedItemLabel> by libraryViewModel.savedItemLabelsLiveData.observeAsState(
listOf()
@ -206,7 +215,14 @@ fun AddLinkBottomSheet(
saveViewModel: SaveViewModel,
onDismiss: () -> Unit = {}
) {
ModalBottomSheet(onDismissRequest = { onDismiss() }) {
ModalBottomSheet(
onDismissRequest = { onDismiss() },
containerColor = MaterialTheme.colorScheme.background,
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
) {
AddLinkSheetContent(
viewModel = saveViewModel,
onCancel = {
@ -228,7 +244,13 @@ fun EditBottomSheet(
libraryViewModel: LibraryViewModel,
onDismiss: () -> Unit = {}
) {
ModalBottomSheet(onDismissRequest = { onDismiss() }) {
ModalBottomSheet(
onDismissRequest = { onDismiss() },
containerColor = MaterialTheme.colorScheme.background,
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
) {
val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit()
EditInfoSheetContent(
savedItemId = currentSavedItemData?.savedItem?.savedItemId,

View file

@ -300,7 +300,7 @@ class LibraryViewModel @Inject constructor(
SavedItemAction.EditLabels -> {
currentItemLiveData.value = itemID
bottomSheetState.value = LibraryBottomSheetState.EDIT
bottomSheetState.value = LibraryBottomSheetState.LABEL
}
SavedItemAction.EditInfo -> {

View file

@ -37,7 +37,6 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
val selectedWebFontName = remember { mutableStateOf(currentWebPreferences.fontFamily.displayText) }
var fontSizeSliderValue by remember { mutableStateOf(currentWebPreferences.textFontSize.toFloat()) }
var marginSliderValue by remember { mutableStateOf(currentWebPreferences.maxWidthPercentage.toFloat()) }
var lineSpacingSliderValue by remember { mutableStateOf(currentWebPreferences.lineHeight.toFloat()) }
@ -112,8 +111,8 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
fontSizeSliderValue = it
webReaderViewModel.setFontSize(it.toInt())
},
steps = 10,
valueRange = 10f..48f,
steps = 40,
valueRange = 10f..50f,
)
Text(stringResource(R.string.reader_preferences_view_margin), style = TextStyle(
@ -127,7 +126,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
marginSliderValue = it
webReaderViewModel.setMaxWidthPercentage(it.toInt())
},
steps = 4,
steps = 40,
valueRange = 60f..100f,
)
@ -142,7 +141,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
lineSpacingSliderValue = it
webReaderViewModel.setLineHeight(it.toInt())
},
steps = 8,
steps = 50,
valueRange = 100f..300f,
)

View file

@ -12,6 +12,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Home

View file

@ -333,7 +333,7 @@ class WebReaderViewModel @Inject constructor(
"deleteHighlight" -> {
Log.d("Loggo", "receive delete highlight action: $jsonString")
viewModelScope.launch {
dataService.deleteHighlights(jsonString)
dataService.deleteHighlightFromJSON(jsonString)
}
}
"updateHighlight" -> {

View file

@ -14,7 +14,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import app.omnivore.omnivore.BuildConfig
import app.omnivore.omnivore.R
import app.omnivore.omnivore.Routes
import app.omnivore.omnivore.ui.auth.LoginViewModel
@ -64,9 +66,12 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti
Box(
modifier = modifier.fillMaxSize()
) {
val version = "Omnivore Version: " + BuildConfig.VERSION_NAME
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
horizontalAlignment = Alignment.Start,
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
@ -74,23 +79,6 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti
.verticalScroll(rememberScrollState())
) {
// profile pic and name
// SettingRow(text = "Labels") { Log.d("settings", "labels button tapped") }
// RowDivider()
// SettingRow(text = "Emails") { Log.d("settings", "emails button tapped") }
// RowDivider()
// SettingRow(text = "Subscriptions") { Log.d("settings", "subscriptions button tapped") }
// RowDivider()
// SettingRow(text = "Clubs") { Log.d("settings", "clubs button tapped") }
// SectionSpacer()
// SettingRow(text = "Push Notifications") { Log.d("settings", "pn button tapped") }
// RowDivider()
// SettingRow(text = "Text to Speech") { Log.d("settings", "tts button tapped") }
//
// SectionSpacer()
SettingRow(text = stringResource(R.string.settings_view_setting_row_documentation)) {
navController.navigate(Routes.Documentation.route)
@ -118,6 +106,13 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti
showLogoutDialog.value = true
}
RowDivider()
Text(
text = version,
fontSize = 12.sp,
modifier = Modifier
.padding(15.dp)
)
}
if (showLogoutDialog.value) {

View file

@ -34,12 +34,12 @@ val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFFEFC125)
val md_theme_dark_onPrimary = Color(0xFF3D2F00)
val md_theme_dark_primaryContainer = Color(0xFF633F00)
val md_theme_dark_onPrimary = Color(0xFF212121)
val md_theme_dark_primaryContainer = Color(0xFF212121)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
val md_theme_dark_secondary = Color(0xFFDDC2A1)
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
val md_theme_dark_onSecondary = Color(0xFF212121)
val md_theme_dark_secondaryContainer = Color(0xFF283237)
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
val md_theme_dark_onTertiary = Color(0xFF243515)
@ -49,20 +49,20 @@ val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1F1B16)
val md_theme_dark_background = Color(0xFF262626)
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
val md_theme_dark_surface = Color(0xFF1F1B16)
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
val md_theme_dark_surfaceVariant = Color(0xFF212121)
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
val md_theme_dark_outline = Color(0xFF9C8F80)
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
val md_theme_dark_inversePrimary = Color(0xFF825500)
val md_theme_dark_inversePrimary = Color(0xFF212121)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
val md_theme_dark_outlineVariant = Color(0xFF424242)
val md_theme_dark_scrim = Color(0xFF000000)
val seed = Color(0xFF825500)
// val seed = Color(0xFF825500)

View file

@ -1,17 +0,0 @@
package app.omnivore.omnivore
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

File diff suppressed because one or more lines are too long

View file

@ -83,8 +83,8 @@ const App = () => {
articleMutations={{
createHighlightMutation: (input) =>
mutation('createHighlight', input),
deleteHighlightMutation: (highlightId) =>
mutation('deleteHighlight', { highlightId }),
deleteHighlightMutation: (libraryItemId, highlightId) =>
mutation('deleteHighlight', { libraryItemId, highlightId }),
mergeHighlightMutation: (input) =>
mutation('mergeHighlight', input),
updateHighlightMutation: (input) =>

View file

@ -82,6 +82,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
;(async () => {
const success = await updateHighlightMutation({
annotation: text,
libraryItemId: props.targetId,
highlightId: props.highlight?.id,
})
if (success) {

View file

@ -52,6 +52,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
;(async () => {
const success = await updateHighlightMutation({
annotation: text,
libraryItemId: props.targetId,
highlightId: props.highlight?.id,
})
if (success) {

View file

@ -310,6 +310,7 @@ export default function EpubContainer(props: EpubContainerProps): JSX.Element {
{noteTarget && (
<HighlightNoteModal
highlight={noteTarget}
libraryItemId={props.article.id}
author={props.article.author ?? ''}
title={props.article.title}
onUpdate={(highlight: Highlight) => {

View file

@ -16,6 +16,7 @@ type HighlightNoteModalProps = {
author: string
title: string
highlight?: Highlight
libraryItemId: string
onUpdate: (updatedHighlight: Highlight) => void
onOpenChange: (open: boolean) => void
createHighlightForNote?: (note?: string) => Promise<Highlight | undefined>
@ -38,6 +39,7 @@ export function HighlightNoteModal(
const saveNoteChanges = useCallback(async () => {
if (noteContent != props.highlight?.annotation && props.highlight?.id) {
const result = await updateHighlightMutation({
libraryItemId: props.libraryItemId,
highlightId: props.highlight?.id,
annotation: noteContent,
color: props.highlight?.color,

View file

@ -187,7 +187,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
}
const didDeleteHighlight =
await props.articleMutations.deleteHighlightMutation(highlightId)
await props.articleMutations.deleteHighlightMutation(
props.articleId,
highlightId
)
if (didDeleteHighlight) {
removeHighlights(
@ -222,6 +225,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
updateHighlightsCallback(highlight)
;(async () => {
const update = await props.articleMutations.updateHighlightMutation({
libraryItemId: props.articleId,
highlightId: highlight.id,
color: color,
})
@ -705,6 +709,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
const annotation = event.annotation ?? ''
const result = await props.articleMutations.updateHighlightMutation({
libraryItemId: props.articleId,
highlightId: focusedHighlight.id,
annotation: event.annotation ?? '',
})
@ -788,6 +793,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
highlight={highlightModalAction.highlight}
author={props.articleAuthor}
title={props.articleTitle}
libraryItemId={props.articleId}
onUpdate={updateHighlightsCallback}
onOpenChange={() =>
setHighlightModalAction({ highlightModalAction: 'none' })

View file

@ -76,6 +76,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
(note: Highlight, text: string, startTime: Date) => {
;(async () => {
const result = await updateHighlightMutation({
libraryItemId: props.item.id,
highlightId: note.id,
annotation: text,
})
@ -195,7 +196,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
highlights
?.filter((h) => h.type === 'NOTE')
.forEach(async (h) => {
const result = await deleteHighlightMutation(h.id)
const result = await deleteHighlightMutation(props.item.id, h.id)
if (!result) {
showErrorToast('Error deleting note')
}
@ -325,6 +326,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
;(async () => {
const highlightId = showConfirmDeleteHighlightId
const success = await deleteHighlightMutation(
props.item.id,
showConfirmDeleteHighlightId
)
mutate()

View file

@ -113,7 +113,7 @@ export default function PdfArticleContainer(
.delete(annotation)
.then(() => {
if (annotationId) {
return deleteHighlightMutation(annotationId)
return deleteHighlightMutation(props.article.id, annotationId)
}
})
.then(() => {
@ -229,7 +229,7 @@ export default function PdfArticleContainer(
}
const annotationId = annotationOmnivoreId(annotation)
if (annotationId) {
await deleteHighlightMutation(annotationId)
await deleteHighlightMutation(props.article.id, annotationId)
}
})
@ -512,7 +512,7 @@ export default function PdfArticleContainer(
const storedId = annotationOmnivoreId(annotation)
if (storedId == annotationId) {
await instance.delete(annotation)
await deleteHighlightMutation(annotationId)
await deleteHighlightMutation(props.article.id, annotationId)
const highlightIdx = highlightsRef.current.findIndex((value) => {
return value.id == annotationId
@ -576,6 +576,7 @@ export default function PdfArticleContainer(
{noteTarget && (
<HighlightNoteModal
highlight={noteTarget}
libraryItemId={props.article.id}
author={props.article.author ?? ''}
title={props.article.title}
onUpdate={(highlight: Highlight) => {

View file

@ -8,7 +8,10 @@ export type ArticleMutations = {
createHighlightMutation: (
input: CreateHighlightInput
) => Promise<Highlight | undefined>
deleteHighlightMutation: (highlightId: string) => Promise<boolean>
deleteHighlightMutation: (
libraryItemId: string,
highlightId: string
) => Promise<boolean>
mergeHighlightMutation: (
input: MergeHighlightInput
) => Promise<Highlight | undefined>

View file

@ -2,6 +2,7 @@ import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
export async function deleteHighlightMutation(
libraryItemId: string,
highlightId: string
): Promise<boolean> {
const mutation = gql`

View file

@ -3,6 +3,7 @@ import { gqlFetcher } from '../networkHelpers'
export type UpdateHighlightInput = {
highlightId: string
libraryItemId?: string
annotation?: string
sharedAt?: string
color?: string