Syncing improvements for highlight changes on android
This commit is contained in:
@ -19,8 +19,8 @@ android {
|
||||
applicationId "app.omnivore.omnivore"
|
||||
minSdk 26
|
||||
targetSdk 33
|
||||
versionCode 178
|
||||
versionName "0.0.178"
|
||||
versionCode 180
|
||||
versionName "0.0.180"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -114,14 +114,15 @@ suspend fun DataService.mergeWebHighlights(jsonString: String) {
|
||||
highlightPositionAnchorIndex = mergeHighlightInput.highlightPositionAnchorIndex.getOrNull() ?: 0
|
||||
)
|
||||
|
||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
|
||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_MERGE.rawValue
|
||||
|
||||
saveHighlightChange(db.highlightChangesDao(), mergeHighlightInput.articleId, highlight)
|
||||
|
||||
Log.d("sync", "overlapHighlightIdList: " + mergeHighlightInput.overlapHighlightIdList)
|
||||
for (highlightID in mergeHighlightInput.overlapHighlightIdList) {
|
||||
deleteHighlight(mergeHighlightInput.articleId, highlightID)
|
||||
}
|
||||
val highlightChange = saveHighlightChange(
|
||||
db.highlightChangesDao(),
|
||||
mergeHighlightInput.articleId,
|
||||
highlight,
|
||||
html = mergeHighlightInput.html.getOrNull(),
|
||||
overlappingIDs = mergeHighlightInput.overlapHighlightIdList
|
||||
)
|
||||
|
||||
val crossRef = SavedItemAndHighlightCrossRef(
|
||||
highlightId = mergeHighlightInput.id,
|
||||
@ -131,11 +132,8 @@ suspend fun DataService.mergeWebHighlights(jsonString: String) {
|
||||
db.highlightDao().insertAll(listOf(highlight))
|
||||
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
|
||||
|
||||
val isUpdatedOnServer = networker.mergeHighlights(mergeHighlightInput)
|
||||
if (isUpdatedOnServer) {
|
||||
highlight.serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue
|
||||
db.highlightDao().update(highlight)
|
||||
}
|
||||
Log.d("sync", "Setting up highlight merge")
|
||||
performHighlightChange(highlightChange)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package app.omnivore.omnivore.dataService
|
||||
|
||||
import android.util.Log
|
||||
import androidx.room.PrimaryKey
|
||||
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
|
||||
import app.omnivore.omnivore.graphql.generated.type.HighlightType
|
||||
import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput
|
||||
import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput
|
||||
import app.omnivore.omnivore.models.ServerSyncStatus
|
||||
import app.omnivore.omnivore.networking.*
|
||||
@ -9,6 +12,7 @@ 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 app.omnivore.omnivore.persistence.entities.saveHighlightChange
|
||||
import com.apollographql.apollo3.api.Optional
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.math.log
|
||||
@ -112,8 +116,6 @@ private suspend fun DataService.syncHighlightChange(highlightChange: HighlightCh
|
||||
}
|
||||
|
||||
ServerSyncStatus.NEEDS_UPDATE.rawValue -> {
|
||||
Log.d("sync", "creating highlight update change: ${highlightChange}")
|
||||
|
||||
updateSyncStatus(ServerSyncStatus.IS_SYNCING)
|
||||
|
||||
val isUpdatedOnServer = networker.updateHighlight(
|
||||
@ -123,7 +125,6 @@ private suspend fun DataService.syncHighlightChange(highlightChange: HighlightCh
|
||||
sharedAt = Optional.absent()
|
||||
)
|
||||
)
|
||||
Log.d("sync", "sycn.updateHighlight result: ${isUpdatedOnServer}")
|
||||
|
||||
if (isUpdatedOnServer) {
|
||||
updateSyncStatus(ServerSyncStatus.IS_SYNCED)
|
||||
@ -134,21 +135,21 @@ private suspend fun DataService.syncHighlightChange(highlightChange: HighlightCh
|
||||
}
|
||||
|
||||
ServerSyncStatus.NEEDS_CREATION.rawValue -> {
|
||||
Log.d("sync", "creating highlight create change: ${highlightChange}")
|
||||
updateSyncStatus(ServerSyncStatus.IS_SYNCING)
|
||||
|
||||
val createResult = networker.createHighlight(
|
||||
CreateHighlightInput(
|
||||
annotation = Optional.presentIfNotNull(highlight.annotation),
|
||||
articleId = highlightChange.savedItemId,
|
||||
id = highlight.highlightId,
|
||||
patch = Optional.presentIfNotNull(highlight.patch),
|
||||
quote = Optional.presentIfNotNull(highlight.quote),
|
||||
shortId = highlight.shortId
|
||||
)
|
||||
val input = CreateHighlightInput(
|
||||
id = highlight.highlightId,
|
||||
shortId = highlight.shortId,
|
||||
articleId = highlightChange.savedItemId,
|
||||
type = Optional.presentIfNotNull(HighlightType.safeValueOf(highlight.type)),
|
||||
annotation = Optional.presentIfNotNull(highlight.annotation),
|
||||
patch = Optional.presentIfNotNull(highlight.patch),
|
||||
quote = Optional.presentIfNotNull(highlight.quote),
|
||||
)
|
||||
Log.d("sync", "Creating highlight from input: ${input}")
|
||||
val createResult = networker.createHighlight(
|
||||
input
|
||||
)
|
||||
Log.d("sync", "sycn.createResult: " + createResult)
|
||||
|
||||
if (createResult.newHighlight != null || createResult.alreadyExists) {
|
||||
updateSyncStatus(ServerSyncStatus.IS_SYNCED)
|
||||
return true
|
||||
@ -157,6 +158,58 @@ private suspend fun DataService.syncHighlightChange(highlightChange: HighlightCh
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
ServerSyncStatus.NEEDS_MERGE.rawValue -> {
|
||||
Log.d("sync", "NEEDS MERGE: ${highlightChange}")
|
||||
|
||||
val mergeHighlightInput = MergeHighlightInput(
|
||||
id = highlight.highlightId,
|
||||
shortId = highlight.shortId,
|
||||
articleId = highlightChange.savedItemId,
|
||||
annotation = Optional.presentIfNotNull(highlight.annotation),
|
||||
color = Optional.presentIfNotNull(highlight.color),
|
||||
highlightPositionAnchorIndex = Optional.presentIfNotNull(highlight.highlightPositionAnchorIndex),
|
||||
highlightPositionPercent = Optional.presentIfNotNull(highlight.highlightPositionPercent),
|
||||
html = Optional.presentIfNotNull(highlightChange.html),
|
||||
overlapHighlightIdList = highlightChange.overlappingIDs ?: emptyList(),
|
||||
patch = highlight.patch ?: "",
|
||||
prefix = Optional.presentIfNotNull(highlight.prefix),
|
||||
quote = highlight.quote ?: "",
|
||||
suffix = Optional.presentIfNotNull(highlight.suffix)
|
||||
)
|
||||
|
||||
val isUpdatedOnServer = networker.mergeHighlights(mergeHighlightInput)
|
||||
if (!isUpdatedOnServer) {
|
||||
Log.d("sync", "FAILED TO MERGE HIGHLIGHT")
|
||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_MERGE.rawValue
|
||||
return false
|
||||
}
|
||||
|
||||
for (highlightID in mergeHighlightInput.overlapHighlightIdList) {
|
||||
Log.d("sync", "DELETING MERGED HIGHLIGHT: ${highlightID}")
|
||||
val deleteChange = HighlightChange(
|
||||
highlightId = highlightID,
|
||||
savedItemId = highlightChange.savedItemId,
|
||||
type = "",
|
||||
shortId = "",
|
||||
annotation = null,
|
||||
createdAt = null,
|
||||
patch = null,
|
||||
prefix = null,
|
||||
quote = null,
|
||||
serverSyncStatus = ServerSyncStatus.NEEDS_DELETION.rawValue,
|
||||
html = null,
|
||||
suffix = null,
|
||||
updatedAt = null,
|
||||
color = null,
|
||||
highlightPositionPercent = null,
|
||||
highlightPositionAnchorIndex = null,
|
||||
overlappingIDs = null
|
||||
)
|
||||
performHighlightChange(deleteChange)
|
||||
}
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,5 +5,6 @@ enum class ServerSyncStatus(val rawValue: Int) {
|
||||
IS_SYNCING(1),
|
||||
NEEDS_DELETION(2),
|
||||
NEEDS_CREATION(3),
|
||||
NEEDS_UPDATE(4)
|
||||
NEEDS_UPDATE(4),
|
||||
NEEDS_MERGE(5)
|
||||
}
|
||||
|
||||
@ -110,7 +110,6 @@ suspend fun Networker.updateWebHighlight(jsonString: String): Boolean {
|
||||
suspend fun Networker.updateHighlight(input: UpdateHighlightInput): Boolean {
|
||||
return try {
|
||||
val result = authenticatedApolloClient().mutation(UpdateHighlightMutation(input)).execute()
|
||||
Log.d("Network", "update highlight result: $result")
|
||||
result.data?.updateHighlight?.onUpdateHighlightSuccess?.highlight != null
|
||||
} catch (e: java.lang.Exception) {
|
||||
false
|
||||
@ -144,12 +143,8 @@ data class CreateHighlightResult(
|
||||
)
|
||||
|
||||
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("sync", "result: ${result.data}")
|
||||
|
||||
val createdHighlight = result.data?.createHighlight?.onCreateHighlightSuccess?.highlight
|
||||
|
||||
if (createdHighlight != null) {
|
||||
|
||||
@ -14,7 +14,7 @@ import app.omnivore.omnivore.persistence.entities.*
|
||||
SavedItemAndSavedItemLabelCrossRef::class,
|
||||
SavedItemAndHighlightCrossRef::class
|
||||
],
|
||||
version = 20
|
||||
version = 24
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun viewerDao(): ViewerDao
|
||||
|
||||
@ -7,10 +7,16 @@ import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Query
|
||||
import androidx.room.TypeConverter
|
||||
import androidx.room.TypeConverters
|
||||
import app.omnivore.omnivore.models.ServerSyncStatus
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@Entity
|
||||
@TypeConverters(StringListTypeConverter::class)
|
||||
data class HighlightChange(
|
||||
@PrimaryKey val highlightId: String,
|
||||
val savedItemId: String,
|
||||
@ -24,16 +30,45 @@ data class HighlightChange(
|
||||
var prefix: String?,
|
||||
var quote: String?,
|
||||
var serverSyncStatus: Int = ServerSyncStatus.IS_SYNCED.rawValue,
|
||||
val html: String?,
|
||||
var shortId: String,
|
||||
val suffix: String?,
|
||||
val updatedAt: String?,
|
||||
val color: String?,
|
||||
val highlightPositionPercent: Double?,
|
||||
val highlightPositionAnchorIndex: Int?
|
||||
val highlightPositionAnchorIndex: Int?,
|
||||
val overlappingIDs: List<String>?
|
||||
)
|
||||
|
||||
fun saveHighlightChange(dao: HighlightChangesDao, savedItemId: String, highlight: Highlight): HighlightChange {
|
||||
Log.d("sync", "saving highlight change: " + savedItemId + ", " + highlight)
|
||||
class StringListTypeConverter {
|
||||
@TypeConverter
|
||||
fun listToString(data: List<String>?): String? {
|
||||
data?.let {
|
||||
return Gson().toJson(data)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringToList(jsonString: String?): List<String>? {
|
||||
return if (jsonString.isNullOrEmpty()) {
|
||||
null
|
||||
} else {
|
||||
val itemType = object : TypeToken<List<String>>() {}.type
|
||||
return Gson().fromJson<List<String>>(jsonString, itemType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun saveHighlightChange(
|
||||
dao: HighlightChangesDao,
|
||||
savedItemId: String,
|
||||
highlight: Highlight,
|
||||
html: String? = null,
|
||||
overlappingIDs: List<String>? = null): HighlightChange {
|
||||
|
||||
Log.d("sync", "saving highlight change: " + highlight.serverSyncStatus + ", " + highlight.type)
|
||||
val change = HighlightChange(
|
||||
savedItemId = savedItemId,
|
||||
highlightId = highlight.highlightId,
|
||||
@ -43,6 +78,7 @@ fun saveHighlightChange(dao: HighlightChangesDao, savedItemId: String, highlight
|
||||
prefix = highlight.prefix,
|
||||
suffix = highlight.suffix,
|
||||
patch = highlight.patch,
|
||||
html = html,
|
||||
annotation = highlight.annotation,
|
||||
createdAt = highlight.createdAt,
|
||||
updatedAt = highlight.updatedAt,
|
||||
@ -50,7 +86,8 @@ fun saveHighlightChange(dao: HighlightChangesDao, savedItemId: String, highlight
|
||||
color =highlight.color,
|
||||
highlightPositionPercent = highlight.highlightPositionPercent,
|
||||
highlightPositionAnchorIndex = highlight.highlightPositionAnchorIndex,
|
||||
serverSyncStatus = highlight.serverSyncStatus
|
||||
serverSyncStatus = highlight.serverSyncStatus,
|
||||
overlappingIDs = overlappingIDs
|
||||
)
|
||||
dao.insertAll(listOf(change))
|
||||
return change
|
||||
|
||||
@ -61,6 +61,8 @@ data class WebReaderContent(
|
||||
|
||||
Log.d("theme", "current theme is: ${preferences.themeKey}")
|
||||
|
||||
Log.d("sync", "HIGHLIGHTS JSON: ${articleContent.highlightsJSONString()}")
|
||||
|
||||
return """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
@ -324,6 +324,7 @@ class WebReaderViewModel @Inject constructor(
|
||||
// }
|
||||
|
||||
fun handleIncomingWebMessage(actionID: String, jsonString: String) {
|
||||
Log.d("sync", "incoming change: ${actionID}: ${jsonString}")
|
||||
when (actionID) {
|
||||
"createHighlight" -> {
|
||||
viewModelScope.launch {
|
||||
@ -331,13 +332,11 @@ class WebReaderViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
"deleteHighlight" -> {
|
||||
Log.d("Loggo", "receive delete highlight action: $jsonString")
|
||||
viewModelScope.launch {
|
||||
dataService.deleteHighlightFromJSON(jsonString)
|
||||
}
|
||||
}
|
||||
"updateHighlight" -> {
|
||||
Log.d("Loggo", "receive update highlight action: $jsonString")
|
||||
viewModelScope.launch {
|
||||
dataService.updateWebHighlight(jsonString)
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -14,7 +14,6 @@ const mutation = async (name, input) => {
|
||||
actionID: name,
|
||||
...input,
|
||||
})
|
||||
console.log('action result', name, result, result.result)
|
||||
return result.result
|
||||
} else {
|
||||
// Send android a message
|
||||
@ -33,6 +32,7 @@ const mutation = async (name, input) => {
|
||||
case 'mergeHighlight':
|
||||
return {
|
||||
id: input['id'],
|
||||
type: input['type'],
|
||||
shortID: input['shortId'],
|
||||
quote: input['quote'],
|
||||
patch: input['patch'],
|
||||
|
||||
@ -8,7 +8,10 @@ import {
|
||||
} from './highlightGenerator'
|
||||
import type { HighlightLocation } from './highlightGenerator'
|
||||
import { extendRangeToWordBoundaries } from './normalizeHighlightRange'
|
||||
import type { Highlight } from '../networking/fragments/highlightFragment'
|
||||
import type {
|
||||
Highlight,
|
||||
HighlightType,
|
||||
} from '../networking/fragments/highlightFragment'
|
||||
import { removeHighlights } from './deleteHighlight'
|
||||
import { ArticleMutations } from '../articleActions'
|
||||
import { NodeHtmlMarkdown } from 'node-html-markdown'
|
||||
@ -103,6 +106,7 @@ export async function createHighlight(
|
||||
id,
|
||||
shortId: nanoid(8),
|
||||
patch,
|
||||
type: 'HIGHLIGHT' as HighlightType,
|
||||
|
||||
color: input.color,
|
||||
prefix: highlightAttributes.prefix,
|
||||
|
||||
Reference in New Issue
Block a user