Syncing improvements for highlight changes on android

This commit is contained in:
Jackson Harper
2024-01-10 11:47:30 +08:00
parent ab196df0f2
commit 4a08c1f195
13 changed files with 135 additions and 46 deletions

View File

@ -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

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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'],

View File

@ -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,