mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #3095 from omnivore-app/fix/android-labels-and-notes-creation
Fix issues with creating notes and labels on Android
This commit is contained in:
commit
6d11aea5a7
11 changed files with 303 additions and 160 deletions
|
|
@ -17,8 +17,8 @@ android {
|
||||||
applicationId "app.omnivore.omnivore"
|
applicationId "app.omnivore.omnivore"
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 126
|
versionCode 130
|
||||||
versionName "0.0.126"
|
versionName "0.0.130"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@ package app.omnivore.omnivore
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import io.intercom.android.sdk.Intercom
|
import io.intercom.android.sdk.Intercom
|
||||||
|
import com.google.android.material.color.DynamicColors;
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class OmnivoreApplication: Application() {
|
class OmnivoreApplication: Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
|
DynamicColors.applyToActivitiesIfAvailable(this);
|
||||||
|
|
||||||
Intercom.initialize(
|
Intercom.initialize(
|
||||||
this,
|
this,
|
||||||
this.getString(R.string.intercom_api_key),
|
this.getString(R.string.intercom_api_key),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package app.omnivore.omnivore.dataService
|
package app.omnivore.omnivore.dataService
|
||||||
|
|
||||||
|
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
|
||||||
import app.omnivore.omnivore.graphql.generated.type.HighlightType
|
import app.omnivore.omnivore.graphql.generated.type.HighlightType
|
||||||
import app.omnivore.omnivore.models.ServerSyncStatus
|
import app.omnivore.omnivore.models.ServerSyncStatus
|
||||||
import app.omnivore.omnivore.networking.*
|
import app.omnivore.omnivore.networking.*
|
||||||
|
|
@ -50,7 +51,7 @@ suspend fun DataService.createWebHighlight(jsonString: String, colorName: String
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun DataService.createNoteHighlight(savedItemId: String, note: String): String {
|
suspend fun DataService.createNoteHighlight(savedItemId: String, note: String): String {
|
||||||
val shortId = UUID.randomUUID().toString()
|
val shortId = NanoId.generate(size=14)
|
||||||
val createHighlightId = UUID.randomUUID().toString()
|
val createHighlightId = UUID.randomUUID().toString()
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
package app.omnivore.omnivore.dataService
|
||||||
|
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.ceil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NanoId is a utility object providing functions for generating secure, URL-friendly, unique identifiers.
|
||||||
|
*
|
||||||
|
* The object offers methods for generating random strings with adjustable parameters like size, alphabet,
|
||||||
|
* overhead factor, and a custom random number generator.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* ```
|
||||||
|
* val id = NanoId.generate()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
object NanoId {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random string based on specified or default parameters.
|
||||||
|
*
|
||||||
|
* @param size The desired length of the generated string. Default is 21.
|
||||||
|
* @param alphabet The set of characters to choose from for generating the string. Default includes alphanumeric characters along with "_" and "-".
|
||||||
|
* @param additionalBytesFactor The additional bytes factor used for calculating the step size. Default is 1.6.
|
||||||
|
* @param random The random number generator to use. Default is `SecureRandom`.
|
||||||
|
* @return The generated random string.
|
||||||
|
* @throws IllegalArgumentException if the alphabet is empty or larger than 255 characters, or if the size is not greater than zero.
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun generate(
|
||||||
|
@NotNull
|
||||||
|
size: Int = 21,
|
||||||
|
@NotNull
|
||||||
|
alphabet: String = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||||
|
@NotNull
|
||||||
|
additionalBytesFactor: Double = 1.6,
|
||||||
|
@NotNull
|
||||||
|
random: Random = SecureRandom()
|
||||||
|
): String {
|
||||||
|
require(!(alphabet.isEmpty() || alphabet.length >= 256)) { "alphabet must contain between 1 and 255 symbols." }
|
||||||
|
require(size > 0) { "size must be greater than zero." }
|
||||||
|
require(additionalBytesFactor >= 1) { "additionalBytesFactor must be greater or equal 1." }
|
||||||
|
|
||||||
|
val mask = calculateMask(alphabet)
|
||||||
|
val step = calculateStep(size, alphabet, additionalBytesFactor)
|
||||||
|
|
||||||
|
return generateOptimized(size, alphabet, mask, step, random)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an optimized random string of a specified size using the given alphabet, mask, and step.
|
||||||
|
* Optionally, you can specify a custom random number generator. This optimized version is designed for
|
||||||
|
* higher performance and lower memory overhead.
|
||||||
|
*
|
||||||
|
* @param size The desired length of the generated string.
|
||||||
|
* @param alphabet The set of characters to choose from for generating the string.
|
||||||
|
* @param mask The mask used for mapping random bytes to alphabet indices. Should be `(2^n) - 1` where `n` is a power of 2 less than or equal to the alphabet size.
|
||||||
|
* @param step The number of random bytes to generate in each iteration. A larger value may speed up the function but increase memory usage.
|
||||||
|
* @param random The random number generator. Default is `SecureRandom`.
|
||||||
|
* @return The generated optimized string.
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun generateOptimized(@NotNull size: Int, @NotNull alphabet: String, @NotNull mask: Int, @NotNull step: Int, @NotNull random: Random = SecureRandom()): String {
|
||||||
|
val idBuilder = StringBuilder(size)
|
||||||
|
val bytes = ByteArray(step)
|
||||||
|
while (true) {
|
||||||
|
random.nextBytes(bytes)
|
||||||
|
for (i in 0 until step) {
|
||||||
|
val alphabetIndex = bytes[i].toInt() and mask
|
||||||
|
if (alphabetIndex < alphabet.length) {
|
||||||
|
idBuilder.append(alphabet[alphabetIndex])
|
||||||
|
if (idBuilder.length == size) {
|
||||||
|
return idBuilder.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the optimal additional bytes factor needed for the generation of the step size, which is used to generate random bytes in each iteration.
|
||||||
|
*
|
||||||
|
* @param alphabet The set of characters to use for generating the string.
|
||||||
|
* @return The additional bytes factor, rounded to two decimal places.
|
||||||
|
*/
|
||||||
|
fun calculateAdditionalBytesFactor(@NotNull alphabet: String): Double {
|
||||||
|
val mask = calculateMask(alphabet)
|
||||||
|
return (1 + abs((mask - alphabet.length.toDouble()) / alphabet.length)).round(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the mask used to map random bytes to indices in the alphabet.
|
||||||
|
*
|
||||||
|
* @param alphabet The set of characters to use for generating the string.
|
||||||
|
* @return The calculated mask value.
|
||||||
|
*/
|
||||||
|
fun calculateMask(@NotNull alphabet: String) = (2 shl (Integer.SIZE - 1 - Integer.numberOfLeadingZeros(alphabet.length - 1))) - 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the number of random bytes to generate in each iteration for a given size and alphabet.
|
||||||
|
*
|
||||||
|
* @param size The length of the generated string.
|
||||||
|
* @param alphabet The set of characters to use for generating the string.
|
||||||
|
* @param additionalBytesFactor The additional bytes factor. Default value is calculated using `calculateAdditionalBytesFactor()`.
|
||||||
|
* @return The number of random bytes to generate in each iteration.
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun calculateStep(@NotNull size: Int, @NotNull alphabet: String, @NotNull additionalBytesFactor: Double = calculateAdditionalBytesFactor(alphabet)) =
|
||||||
|
ceil(additionalBytesFactor * calculateMask(alphabet) * size / alphabet.length).toInt()
|
||||||
|
|
||||||
|
@JvmSynthetic
|
||||||
|
internal fun Double.round(decimals: Int): Double {
|
||||||
|
var multiplier = 1.0
|
||||||
|
repeat(decimals) { multiplier *= 10 }
|
||||||
|
return kotlin.math.round(this * multiplier) / multiplier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -139,6 +139,8 @@ suspend fun Networker.createHighlight(input: CreateHighlightInput): Highlight? {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val result = authenticatedApolloClient().mutation(CreateHighlightMutation(input)).execute()
|
val result = authenticatedApolloClient().mutation(CreateHighlightMutation(input)).execute()
|
||||||
|
Log.d("Loggo", "result: ${result.data}")
|
||||||
|
|
||||||
|
|
||||||
val createdHighlight = result.data?.createHighlight?.onCreateHighlightSuccess?.highlight
|
val createdHighlight = result.data?.createHighlight?.onCreateHighlightSuccess?.highlight
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package app.omnivore.omnivore.ui
|
||||||
|
|
||||||
|
import app.omnivore.omnivore.dataService.DataService
|
||||||
|
import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput
|
||||||
|
import app.omnivore.omnivore.graphql.generated.type.SetLabelsInput
|
||||||
|
import app.omnivore.omnivore.networking.Networker
|
||||||
|
import app.omnivore.omnivore.networking.updateLabelsForSavedItem
|
||||||
|
import app.omnivore.omnivore.persistence.entities.SavedItemAndSavedItemLabelCrossRef
|
||||||
|
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
|
||||||
|
import com.apollographql.apollo3.api.Optional
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun setSavedItemLabels(
|
||||||
|
networker: Networker,
|
||||||
|
dataService: DataService,
|
||||||
|
savedItemID: String,
|
||||||
|
labels: List<SavedItemLabel>
|
||||||
|
): Boolean {
|
||||||
|
val input = SetLabelsInput(
|
||||||
|
pageId = savedItemID,
|
||||||
|
labels = Optional.presentIfNotNull(labels.map { CreateLabelInput(color = Optional.presentIfNotNull(it.color), name = it.name) }),
|
||||||
|
)
|
||||||
|
|
||||||
|
val updatedLabels = networker.updateLabelsForSavedItem(input)
|
||||||
|
|
||||||
|
// Figure out which of the labels are new
|
||||||
|
updatedLabels?.let { updatedLabels ->
|
||||||
|
val existingNamedLabels = dataService.db.savedItemLabelDao()
|
||||||
|
.namedLabels(updatedLabels.map { it.labelFields.name })
|
||||||
|
val existingNames = existingNamedLabels.map { it.name }
|
||||||
|
val newNamedLabels = updatedLabels.filter { !existingNames.contains(it.labelFields.name) }
|
||||||
|
|
||||||
|
dataService.db.savedItemLabelDao().insertAll(newNamedLabels.map {
|
||||||
|
SavedItemLabel(
|
||||||
|
savedItemLabelId = it.labelFields.id,
|
||||||
|
name = it.labelFields.name,
|
||||||
|
color = it.labelFields.color,
|
||||||
|
createdAt = null,
|
||||||
|
labelDescription = null
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
val allNamedLabels = dataService.db.savedItemLabelDao()
|
||||||
|
.namedLabels(updatedLabels.map { it.labelFields.name })
|
||||||
|
val crossRefs = allNamedLabels.map {
|
||||||
|
SavedItemAndSavedItemLabelCrossRef(
|
||||||
|
savedItemLabelId = it.savedItemLabelId,
|
||||||
|
savedItemId = savedItemID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dataService.db.savedItemAndSavedItemLabelCrossRefDao().deleteRefsBySavedItemId(savedItemID)
|
||||||
|
dataService.db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs)
|
||||||
|
|
||||||
|
return true
|
||||||
|
} ?: run {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,8 @@ import app.omnivore.omnivore.models.ServerSyncStatus
|
||||||
import app.omnivore.omnivore.networking.*
|
import app.omnivore.omnivore.networking.*
|
||||||
import app.omnivore.omnivore.persistence.entities.*
|
import app.omnivore.omnivore.persistence.entities.*
|
||||||
import app.omnivore.omnivore.ui.ResourceProvider
|
import app.omnivore.omnivore.ui.ResourceProvider
|
||||||
|
import app.omnivore.omnivore.ui.setSavedItemLabels
|
||||||
|
import coil.util.CoilUtils.result
|
||||||
import com.apollographql.apollo3.api.Optional
|
import com.apollographql.apollo3.api.Optional
|
||||||
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
|
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
|
@ -305,43 +307,16 @@ class LibraryViewModel @Inject constructor(
|
||||||
fun updateSavedItemLabels(savedItemID: String, labels: List<SavedItemLabel>) {
|
fun updateSavedItemLabels(savedItemID: String, labels: List<SavedItemLabel>) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val input = SetLabelsInput(
|
val result = setSavedItemLabels(
|
||||||
pageId = savedItemID,
|
networker = networker,
|
||||||
labels = Optional.presentIfNotNull(labels.map { CreateLabelInput(color = Optional.presentIfNotNull(it.color), name = it.name) }),
|
dataService = dataService,
|
||||||
|
savedItemID = savedItemID,
|
||||||
|
labels = labels
|
||||||
)
|
)
|
||||||
|
|
||||||
val updatedLabels = networker.updateLabelsForSavedItem(input)
|
if (result) {
|
||||||
|
|
||||||
// Figure out which of the labels are new
|
|
||||||
updatedLabels?.let { updatedLabels ->
|
|
||||||
val existingNamedLabels = dataService.db.savedItemLabelDao()
|
|
||||||
.namedLabels(updatedLabels.map { it.labelFields.name })
|
|
||||||
val existingNames = existingNamedLabels.map { it.name }
|
|
||||||
val newNamedLabels = updatedLabels.filter { !existingNames.contains(it.labelFields.name) }
|
|
||||||
|
|
||||||
dataService.db.savedItemLabelDao().insertAll(newNamedLabels.map {
|
|
||||||
SavedItemLabel(
|
|
||||||
savedItemLabelId = it.labelFields.id,
|
|
||||||
name = it.labelFields.name,
|
|
||||||
color = it.labelFields.color,
|
|
||||||
createdAt = null,
|
|
||||||
labelDescription = null
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
val allNamedLabels = dataService.db.savedItemLabelDao()
|
|
||||||
.namedLabels(updatedLabels.map { it.labelFields.name })
|
|
||||||
val crossRefs = allNamedLabels.map {
|
|
||||||
SavedItemAndSavedItemLabelCrossRef(
|
|
||||||
savedItemLabelId = it.savedItemLabelId,
|
|
||||||
savedItemId = savedItemID
|
|
||||||
)
|
|
||||||
}
|
|
||||||
dataService.db.savedItemAndSavedItemLabelCrossRefDao().deleteRefsBySavedItemId(savedItemID)
|
|
||||||
dataService.db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs)
|
|
||||||
|
|
||||||
snackbarMessage = resourceProvider.getString(R.string.library_view_model_snackbar_success)
|
snackbarMessage = resourceProvider.getString(R.string.library_view_model_snackbar_success)
|
||||||
} ?: run {
|
} else {
|
||||||
snackbarMessage = resourceProvider.getString(R.string.library_view_model_snackbar_error)
|
snackbarMessage = resourceProvider.getString(R.string.library_view_model_snackbar_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -155,42 +155,45 @@ fun EditNoteModal(initialValue: String?, onDismiss: (save: Boolean, text: String
|
||||||
val annotation = rememberSaveable { mutableStateOf(initialValue ?: "") }
|
val annotation = rememberSaveable { mutableStateOf(initialValue ?: "") }
|
||||||
|
|
||||||
BottomSheetUI() {
|
BottomSheetUI() {
|
||||||
Scaffold(
|
MaterialTheme {
|
||||||
topBar = {
|
Scaffold(
|
||||||
CenterAlignedTopAppBar(
|
topBar = {
|
||||||
title = { Text(stringResource(R.string.edit_note_modal_title)) },
|
CenterAlignedTopAppBar(
|
||||||
modifier = Modifier.statusBarsPadding(),
|
title = { Text(stringResource(R.string.edit_note_modal_title)) },
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
modifier = Modifier.statusBarsPadding(),
|
||||||
containerColor = MaterialTheme.colorScheme.background
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
),
|
containerColor = MaterialTheme.colorScheme.background
|
||||||
navigationIcon = {
|
),
|
||||||
TextButton(onClick = {
|
navigationIcon = {
|
||||||
onDismiss(false, initialValue)
|
TextButton(onClick = {
|
||||||
}) {
|
onDismiss(false, initialValue)
|
||||||
Text(text = stringResource(R.string.edit_note_modal_action_cancel))
|
}) {
|
||||||
|
Text(text = stringResource(R.string.edit_note_modal_action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
onDismiss(true, annotation.value)
|
||||||
|
}) {
|
||||||
|
Text(text = stringResource(R.string.edit_note_modal_action_save))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
actions = {
|
}
|
||||||
TextButton(onClick = {
|
) { paddingValues ->
|
||||||
onDismiss(true, annotation.value)
|
|
||||||
}) {
|
TextField(
|
||||||
Text(text = stringResource(R.string.edit_note_modal_action_save))
|
modifier = Modifier
|
||||||
}
|
.padding(top = paddingValues.calculateTopPadding())
|
||||||
}
|
.focusRequester(focusRequester)
|
||||||
|
.fillMaxSize(),
|
||||||
|
value = annotation.value, onValueChange = { annotation.value = it },
|
||||||
|
colors = TextFieldDefaults.textFieldColors(
|
||||||
|
focusedTextColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
unfocusedTextColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
|
||||||
TextField(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = paddingValues.calculateTopPadding())
|
|
||||||
.focusRequester(focusRequester)
|
|
||||||
.fillMaxSize(),
|
|
||||||
value = annotation.value, onValueChange = { annotation.value = it },
|
|
||||||
colors = TextFieldDefaults.textFieldColors(
|
|
||||||
focusedTextColor = Color.White,
|
|
||||||
unfocusedTextColor = Color.White,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.omnivore.omnivore.DatastoreRepository
|
import app.omnivore.omnivore.DatastoreRepository
|
||||||
import app.omnivore.omnivore.dataService.DataService
|
import app.omnivore.omnivore.dataService.DataService
|
||||||
|
import app.omnivore.omnivore.dataService.NanoId
|
||||||
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
|
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
|
||||||
import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput
|
import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput
|
||||||
import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput
|
import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput
|
||||||
|
|
@ -146,11 +147,11 @@ class PDFReaderViewModel @Inject constructor(
|
||||||
fun syncHighlightUpdates(newAnnotation: Annotation, quote: String, overlapIds: List<String>, note: String? = null) {
|
fun syncHighlightUpdates(newAnnotation: Annotation, quote: String, overlapIds: List<String>, note: String? = null) {
|
||||||
val itemID = pdfReaderParamsLiveData.value?.item?.savedItemId ?: return
|
val itemID = pdfReaderParamsLiveData.value?.item?.savedItemId ?: return
|
||||||
val highlightID = UUID.randomUUID().toString()
|
val highlightID = UUID.randomUUID().toString()
|
||||||
val shortID = UUID.randomUUID().toString().replace("-","").substring(0,8)
|
val shortId = NanoId.generate(size=14)
|
||||||
|
|
||||||
val jsonValues = JSONObject()
|
val jsonValues = JSONObject()
|
||||||
.put("id", highlightID)
|
.put("id", highlightID)
|
||||||
.put("shortId", shortID)
|
.put("shortId", shortId)
|
||||||
.put("quote", quote)
|
.put("quote", quote)
|
||||||
.put("articleId", itemID)
|
.put("articleId", itemID)
|
||||||
|
|
||||||
|
|
@ -164,7 +165,7 @@ class PDFReaderViewModel @Inject constructor(
|
||||||
overlapHighlightIdList = overlapIds,
|
overlapHighlightIdList = overlapIds,
|
||||||
patch = newAnnotation.toInstantJson(),
|
patch = newAnnotation.toInstantJson(),
|
||||||
quote = quote,
|
quote = quote,
|
||||||
shortId = shortID
|
shortId = shortId
|
||||||
)
|
)
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|
@ -177,7 +178,7 @@ class PDFReaderViewModel @Inject constructor(
|
||||||
id = highlightID,
|
id = highlightID,
|
||||||
patch = Optional.presentIfNotNull(newAnnotation.toInstantJson()),
|
patch = Optional.presentIfNotNull(newAnnotation.toInstantJson()),
|
||||||
quote = Optional.presentIfNotNull(quote),
|
quote = Optional.presentIfNotNull(quote),
|
||||||
shortId = shortID,
|
shortId = shortId,
|
||||||
)
|
)
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import app.omnivore.omnivore.persistence.entities.SavedItemAndSavedItemLabelCros
|
||||||
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
|
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
|
||||||
import app.omnivore.omnivore.ui.components.HighlightColor
|
import app.omnivore.omnivore.ui.components.HighlightColor
|
||||||
import app.omnivore.omnivore.ui.library.SavedItemAction
|
import app.omnivore.omnivore.ui.library.SavedItemAction
|
||||||
|
import app.omnivore.omnivore.ui.setSavedItemLabels
|
||||||
import com.apollographql.apollo3.api.Optional
|
import com.apollographql.apollo3.api.Optional
|
||||||
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
|
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
|
@ -500,38 +501,13 @@ class WebReaderViewModel @Inject constructor(
|
||||||
fun updateSavedItemLabels(savedItemID: String, labels: List<SavedItemLabel>) {
|
fun updateSavedItemLabels(savedItemID: String, labels: List<SavedItemLabel>) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val namedLabels = dataService.db.savedItemLabelDao().namedLabels(labels.map { it.name })
|
|
||||||
|
|
||||||
namedLabels.filter { it.serverSyncStatus != ServerSyncStatus.IS_SYNCED.rawValue }.mapNotNull {
|
setSavedItemLabels(
|
||||||
val result = networker.createNewLabel(CreateLabelInput(color = presentIfNotNull(it.color), name = it.name))
|
networker = networker,
|
||||||
result?.let { it1 ->
|
dataService = dataService,
|
||||||
SavedItemLabel(
|
savedItemID = savedItemID,
|
||||||
savedItemLabelId = it1.id,
|
labels = labels
|
||||||
name = result.name,
|
)
|
||||||
color = result.color,
|
|
||||||
createdAt = result.createdAt.toString(),
|
|
||||||
labelDescription = result.description,
|
|
||||||
serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val input = SetLabelsInput(labelIds = Optional.presentIfNotNull(namedLabels.map { it.savedItemLabelId }), pageId = savedItemID)
|
|
||||||
val networkResult = networker.updateLabelsForSavedItem(input)
|
|
||||||
|
|
||||||
// TODO: assign a server sync status to these
|
|
||||||
val crossRefs = namedLabels.map {
|
|
||||||
SavedItemAndSavedItemLabelCrossRef(
|
|
||||||
savedItemLabelId = it.savedItemLabelId,
|
|
||||||
savedItemId = savedItemID
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all labels first
|
|
||||||
dataService.db.savedItemAndSavedItemLabelCrossRefDao().deleteRefsBySavedItemId(savedItemID)
|
|
||||||
|
|
||||||
// Add back the current labels
|
|
||||||
dataService.db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs)
|
|
||||||
|
|
||||||
slug?.let {
|
slug?.let {
|
||||||
loadItemFromDB(it)
|
loadItemFromDB(it)
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,64 @@
|
||||||
package app.omnivore.omnivore.ui.theme
|
package app.omnivore.omnivore.ui.theme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
//
|
||||||
val md_theme_light_primary = Color(0xFF745B00)
|
val md_theme_light_primary = Color(0xFF745B00)
|
||||||
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||||
val md_theme_light_primaryContainer = Color(0xFFFFE08C)
|
//val md_theme_light_primaryContainer = Color(0xFFFFE08C)
|
||||||
val md_theme_light_onPrimaryContainer = Color(0xFF241A00)
|
//val md_theme_light_onPrimaryContainer = Color(0xFF241A00)
|
||||||
val md_theme_light_secondary = Color(0xFF6D5E00)
|
//val md_theme_light_secondary = Color(0xFF6D5E00)
|
||||||
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
//val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
||||||
val md_theme_light_secondaryContainer = Color(0xFFFCE365)
|
//val md_theme_light_secondaryContainer = Color(0xFFFCE365)
|
||||||
val md_theme_light_onSecondaryContainer = Color(0xFF211B00)
|
//val md_theme_light_onSecondaryContainer = Color(0xFF211B00)
|
||||||
val md_theme_light_tertiary = Color(0xFF4B670A)
|
//val md_theme_light_tertiary = Color(0xFF4B670A)
|
||||||
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
//val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
||||||
val md_theme_light_tertiaryContainer = Color(0xFFCBEF86)
|
//val md_theme_light_tertiaryContainer = Color(0xFFCBEF86)
|
||||||
val md_theme_light_onTertiaryContainer = Color(0xFF141F00)
|
//val md_theme_light_onTertiaryContainer = Color(0xFF141F00)
|
||||||
val md_theme_light_error = Color(0xFFBA1A1A)
|
//val md_theme_light_error = Color(0xFFBA1A1A)
|
||||||
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
|
//val md_theme_light_errorContainer = Color(0xFFFFDAD6)
|
||||||
val md_theme_light_onError = Color(0xFFFFFFFF)
|
//val md_theme_light_onError = Color(0xFFFFFFFF)
|
||||||
val md_theme_light_onErrorContainer = Color(0xFF410002)
|
//val md_theme_light_onErrorContainer = Color(0xFF410002)
|
||||||
val md_theme_light_background = Color(0xFFF7FFED)
|
//val md_theme_light_background = Color(0xFFF7FFED)
|
||||||
val md_theme_light_onBackground = Color(0xFF032100)
|
//val md_theme_light_onBackground = Color(0xFF032100)
|
||||||
val md_theme_light_surface = Color(0xFFF7FFED)
|
//val md_theme_light_surface = Color(0xFFF7FFED)
|
||||||
val md_theme_light_onSurface = Color(0xFF032100)
|
//val md_theme_light_onSurface = Color(0xFF032100)
|
||||||
val md_theme_light_surfaceVariant = Color(0xFFEBE1CF)
|
//val md_theme_light_surfaceVariant = Color(0xFFEBE1CF)
|
||||||
val md_theme_light_onSurfaceVariant = Color(0xFF4C4639)
|
//val md_theme_light_onSurfaceVariant = Color(0xFF4C4639)
|
||||||
val md_theme_light_outline = Color(0xFF7E7667)
|
//val md_theme_light_outline = Color(0xFF7E7667)
|
||||||
val md_theme_light_inverseOnSurface = Color(0xFFCBFFB5)
|
//val md_theme_light_inverseOnSurface = Color(0xFFCBFFB5)
|
||||||
val md_theme_light_inverseSurface = Color(0xFF083900)
|
//val md_theme_light_inverseSurface = Color(0xFF083900)
|
||||||
val md_theme_light_inversePrimary = Color(0xFFEFC125)
|
//val md_theme_light_inversePrimary = Color(0xFFEFC125)
|
||||||
val md_theme_light_shadow = Color(0xFF000000)
|
//val md_theme_light_shadow = Color(0xFF000000)
|
||||||
val md_theme_light_surfaceTint = Color(0xFF745B00)
|
//val md_theme_light_surfaceTint = Color(0xFF745B00)
|
||||||
|
//
|
||||||
val md_theme_dark_primary = Color(0xFFEFC125)
|
val md_theme_dark_primary = Color(0xFFEFC125)
|
||||||
val md_theme_dark_onPrimary = Color(0xFF3D2F00)
|
val md_theme_dark_onPrimary = Color(0xFF3D2F00)
|
||||||
val md_theme_dark_primaryContainer = Color(0xFF584400)
|
//val md_theme_dark_primaryContainer = Color(0xFF584400)
|
||||||
val md_theme_dark_onPrimaryContainer = Color(0xFFFFE08C)
|
//val md_theme_dark_onPrimaryContainer = Color(0xFFFFE08C)
|
||||||
val md_theme_dark_secondary = Color(0xFFDEC64C)
|
//val md_theme_dark_secondary = Color(0xFFDEC64C)
|
||||||
val md_theme_dark_onSecondary = Color(0xFF393000)
|
//val md_theme_dark_onSecondary = Color(0xFF393000)
|
||||||
val md_theme_dark_secondaryContainer = Color(0xFF524600)
|
//val md_theme_dark_secondaryContainer = Color(0xFF524600)
|
||||||
val md_theme_dark_onSecondaryContainer = Color(0xFFFCE365)
|
//val md_theme_dark_onSecondaryContainer = Color(0xFFFCE365)
|
||||||
val md_theme_dark_tertiary = Color(0xFFB0D36D)
|
//val md_theme_dark_tertiary = Color(0xFFB0D36D)
|
||||||
val md_theme_dark_onTertiary = Color(0xFF243600)
|
//val md_theme_dark_onTertiary = Color(0xFF243600)
|
||||||
val md_theme_dark_tertiaryContainer = Color(0xFF364E00)
|
//val md_theme_dark_tertiaryContainer = Color(0xFF364E00)
|
||||||
val md_theme_dark_onTertiaryContainer = Color(0xFFCBEF86)
|
//val md_theme_dark_onTertiaryContainer = Color(0xFFCBEF86)
|
||||||
val md_theme_dark_error = Color(0xFFFFB4AB)
|
//val md_theme_dark_error = Color(0xFFFFB4AB)
|
||||||
val md_theme_dark_errorContainer = Color(0xFF93000A)
|
//val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||||
val md_theme_dark_onError = Color(0xFF690005)
|
//val md_theme_dark_onError = Color(0xFF690005)
|
||||||
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
//val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||||
val md_theme_dark_background = Color(0xFF032100)
|
//val md_theme_dark_background = Color(0xFF032100)
|
||||||
val md_theme_dark_onBackground = Color(0xFFB4F39B)
|
//val md_theme_dark_onBackground = Color(0xFFB4F39B)
|
||||||
val md_theme_dark_surface = Color(0xFF032100)
|
//val md_theme_dark_surface = Color(0xFF032100)
|
||||||
val md_theme_dark_onSurface = Color(0xFFB4F39B)
|
//val md_theme_dark_onSurface = Color(0xFFB4F39B)
|
||||||
val md_theme_dark_surfaceVariant = Color(0xFF4C4639)
|
//val md_theme_dark_surfaceVariant = Color(0xFF4C4639)
|
||||||
val md_theme_dark_onSurfaceVariant = Color(0xFFCFC5B4)
|
//val md_theme_dark_onSurfaceVariant = Color(0xFFCFC5B4)
|
||||||
val md_theme_dark_outline = Color(0xFF989080)
|
//val md_theme_dark_outline = Color(0xFF989080)
|
||||||
val md_theme_dark_inverseOnSurface = Color(0xFF032100)
|
//val md_theme_dark_inverseOnSurface = Color(0xFF032100)
|
||||||
val md_theme_dark_inverseSurface = Color(0xFFB4F39B)
|
//val md_theme_dark_inverseSurface = Color(0xFFB4F39B)
|
||||||
val md_theme_dark_inversePrimary = Color(0xFF745B00)
|
//val md_theme_dark_inversePrimary = Color(0xFF745B00)
|
||||||
val md_theme_dark_shadow = Color(0xFF000000)
|
//val md_theme_dark_shadow = Color(0xFF000000)
|
||||||
val md_theme_dark_surfaceTint = Color(0xFFEFC125)
|
//val md_theme_dark_surfaceTint = Color(0xFFEFC125)
|
||||||
|
//
|
||||||
val seed = Color(0xFFE2B513)
|
//val seed = Color(0xFFE2B513)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue