add updatesSince API to Android
This commit is contained in:
56
android/Omnivore/app/src/main/graphql/UpdatesSince.graphql
Normal file
56
android/Omnivore/app/src/main/graphql/UpdatesSince.graphql
Normal file
@ -0,0 +1,56 @@
|
||||
query UpdatesSince($after: String, $first: Int, $since: Date!) {
|
||||
updatesSince(after: $after, first: $first, since: $since) {
|
||||
... on UpdatesSinceSuccess {
|
||||
edges {
|
||||
cursor
|
||||
itemID
|
||||
updateReason
|
||||
node {
|
||||
id
|
||||
title
|
||||
slug
|
||||
url
|
||||
pageType
|
||||
contentReader
|
||||
createdAt
|
||||
isArchived
|
||||
readingProgressPercent
|
||||
readingProgressAnchorIndex
|
||||
author
|
||||
image
|
||||
description
|
||||
publishedAt
|
||||
ownedByViewer
|
||||
originalArticleUrl
|
||||
uploadFileId
|
||||
labels {
|
||||
id
|
||||
name
|
||||
color
|
||||
}
|
||||
pageId
|
||||
shortId
|
||||
quote
|
||||
annotation
|
||||
state
|
||||
siteName
|
||||
subscription
|
||||
readAt
|
||||
savedAt
|
||||
updatedAt
|
||||
language
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
... on UpdatesSinceError {
|
||||
errorCodes
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,7 @@ object DatastoreKeys {
|
||||
const val omnivoreAuthToken = "omnivoreAuthToken"
|
||||
const val omnivoreAuthCookieString = "omnivoreAuthCookieString"
|
||||
const val omnivorePendingUserToken = "omnivorePendingUserToken"
|
||||
const val libraryLastSyncTimestamp = "libraryLastSyncTimestamp"
|
||||
const val preferredWebFontSize = "preferredWebFontSize"
|
||||
const val preferredWebLineHeight = "preferredWebLineHeight"
|
||||
const val preferredWebMaxWidthPercentage = "preferredWebMaxWidthPercentage"
|
||||
|
||||
@ -15,3 +15,7 @@ class DataService @Inject constructor(
|
||||
AppDatabase::class.java, "omnivore-database"
|
||||
).build()
|
||||
}
|
||||
|
||||
//suspend fun DataService.sync(): Boolean {
|
||||
//
|
||||
//}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
package app.omnivore.omnivore.models
|
||||
|
||||
public enum class ServerSyncStatus(
|
||||
public val rawValue: Int,
|
||||
) {
|
||||
IS_SYNCED(0),
|
||||
IS_SYNCING(1),
|
||||
NEEDS_DELETION(2),
|
||||
NEEDS_CREATION(3),
|
||||
NEEDS_UPDATE(4)
|
||||
}
|
||||
@ -8,6 +8,7 @@ import app.omnivore.omnivore.graphql.generated.UpdateHighlightMutation
|
||||
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
|
||||
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.persistence.entities.Highlight
|
||||
import com.apollographql.apollo3.api.Optional
|
||||
import com.google.gson.Gson
|
||||
@ -150,8 +151,7 @@ suspend fun Networker.createHighlight(input: CreateHighlightInput): Highlight? {
|
||||
createdAt = null, // TODO: update gql query to get this
|
||||
updatedAt = null, // TODO: fix updatedAtString?.let { LocalDate.parse(it) },
|
||||
createdByMe = createdHighlight.highlightFields.createdByMe,
|
||||
markedForDeletion = false,
|
||||
serverSyncStatus = 1 // TODO: create enum for this
|
||||
markedForDeletion = false
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
|
||||
@ -53,7 +53,6 @@ suspend fun Networker.savedItem(slug: String): SavedItemQueryResponse {
|
||||
updatedAt = null, //updatedAtString?.let { str -> LocalDate.parse(str) }, TODO: fix date parsing
|
||||
createdByMe = it.highlightFields.createdByMe,
|
||||
markedForDeletion = false,
|
||||
serverSyncStatus = 1 // TODO: create enum for this
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@ package app.omnivore.omnivore.networking
|
||||
|
||||
import app.omnivore.omnivore.graphql.generated.SearchQuery
|
||||
import app.omnivore.omnivore.graphql.generated.TypeaheadSearchQuery
|
||||
import app.omnivore.omnivore.graphql.generated.UpdatesSinceQuery
|
||||
import app.omnivore.omnivore.graphql.generated.type.UpdateReason
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItem
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemCardData
|
||||
import com.apollographql.apollo3.api.Optional
|
||||
@ -11,6 +13,14 @@ data class SearchQueryResponse(
|
||||
val cardsData: List<SavedItemCardData>
|
||||
)
|
||||
|
||||
data class SavedItemUpdatesQueryResponse(
|
||||
val cursor: String?,
|
||||
val hasMoreItems: Boolean,
|
||||
val totalCount: Int,
|
||||
val deletedItemIDs: List<String>,
|
||||
val items: List<SavedItem>
|
||||
)
|
||||
|
||||
suspend fun Networker.typeaheadSearch(
|
||||
query: String
|
||||
): SearchQueryResponse {
|
||||
@ -55,7 +65,6 @@ suspend fun Networker.search(
|
||||
)
|
||||
).execute()
|
||||
|
||||
|
||||
val newCursor = result.data?.search?.onSearchSuccess?.pageInfo?.endCursor
|
||||
val itemList = result.data?.search?.onSearchSuccess?.edges ?: listOf()
|
||||
|
||||
@ -78,3 +87,65 @@ suspend fun Networker.search(
|
||||
return SearchQueryResponse(null, listOf())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun Networker.savedItemUpdates(
|
||||
cursor: String? = null,
|
||||
limit: Int = 15,
|
||||
since: String
|
||||
): SavedItemUpdatesQueryResponse? {
|
||||
try {
|
||||
val result = authenticatedApolloClient().query(
|
||||
UpdatesSinceQuery(
|
||||
after = Optional.presentIfNotNull(cursor),
|
||||
first = Optional.presentIfNotNull(limit),
|
||||
since = since
|
||||
)
|
||||
).execute()
|
||||
|
||||
val payload = result.data?.updatesSince?.onUpdatesSinceSuccess ?: return null
|
||||
val itemNodes: MutableList<UpdatesSinceQuery.Node> = mutableListOf()
|
||||
val deletedItemIDs: MutableList<String> = mutableListOf()
|
||||
|
||||
for (edge in payload.edges) {
|
||||
if (edge.updateReason == UpdateReason.DELETED) {
|
||||
deletedItemIDs.add(edge.itemID)
|
||||
} else if (edge.node != null) {
|
||||
itemNodes.add(edge.node)
|
||||
}
|
||||
}
|
||||
|
||||
val savedItems = itemNodes.map {
|
||||
SavedItem(
|
||||
id = it.id,
|
||||
title = it.title,
|
||||
createdAt = it.createdAt as String,
|
||||
savedAt = it.savedAt as String,
|
||||
readAt = it.readAt as String?,
|
||||
updatedAt = it.updatedAt as String?,
|
||||
readingProgress = it.readingProgressPercent,
|
||||
readingProgressAnchor = it.readingProgressAnchorIndex,
|
||||
imageURLString = it.image,
|
||||
pageURLString = it.url,
|
||||
descriptionText = it.description,
|
||||
publisherURLString = it.originalArticleUrl,
|
||||
siteName = it.siteName,
|
||||
author = it.author,
|
||||
publishDate = it.publishedAt as String?,
|
||||
slug = it.slug,
|
||||
isArchived = it.isArchived,
|
||||
contentReader = it.contentReader.rawValue,
|
||||
content = null
|
||||
)
|
||||
}
|
||||
|
||||
return SavedItemUpdatesQueryResponse(
|
||||
cursor = payload.pageInfo.endCursor,
|
||||
hasMoreItems = payload.pageInfo.hasNextPage,
|
||||
totalCount = savedItems.size,
|
||||
deletedItemIDs = deletedItemIDs,
|
||||
items = savedItems
|
||||
)
|
||||
} catch (e: java.lang.Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package app.omnivore.omnivore.persistence.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import app.omnivore.omnivore.models.ServerSyncStatus
|
||||
import java.time.LocalDate
|
||||
import java.util.Date
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package app.omnivore.omnivore.persistence.entities
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import androidx.room.*
|
||||
import app.omnivore.omnivore.models.ServerSyncStatus
|
||||
import app.omnivore.omnivore.persistence.BaseDao
|
||||
|
||||
@Entity
|
||||
@ -35,7 +36,7 @@ data class SavedItem(
|
||||
val onDeviceImageURLString: String? = null,
|
||||
val originalHtml: String? = null,
|
||||
@ColumnInfo(typeAffinity = ColumnInfo.BLOB) val pdfData: ByteArray? = null,
|
||||
val serverSyncStatus: Int = 0, // TODO: implement,
|
||||
val serverSyncStatus: Int = 0,
|
||||
val tempPDFURL: String? = null
|
||||
|
||||
// hasMany highlights
|
||||
|
||||
@ -2,6 +2,7 @@ package app.omnivore.omnivore.persistence.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import app.omnivore.omnivore.models.ServerSyncStatus
|
||||
|
||||
@Entity
|
||||
data class SavedItemLabel(
|
||||
|
||||
@ -8,19 +8,20 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.omnivore.omnivore.DataService
|
||||
import app.omnivore.omnivore.DatastoreKeys
|
||||
import app.omnivore.omnivore.DatastoreRepository
|
||||
import app.omnivore.omnivore.networking.*
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemCardData
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class LibraryViewModel @Inject constructor(
|
||||
private val networker: Networker,
|
||||
private val dataService: DataService
|
||||
private val dataService: DataService,
|
||||
private val datastoreRepo: DatastoreRepository
|
||||
): ViewModel() {
|
||||
private var cursor: String? = null
|
||||
private var items: List<SavedItemCardData> = listOf()
|
||||
@ -51,6 +52,19 @@ class LibraryViewModel @Inject constructor(
|
||||
load(true)
|
||||
}
|
||||
|
||||
fun getLastSyncTime(): LocalDateTime? = runBlocking {
|
||||
datastoreRepo.getString(DatastoreKeys.libraryLastSyncTimestamp)?.let {
|
||||
LocalDateTime.parse(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun syncItems() {
|
||||
val syncStart = LocalDateTime.now()
|
||||
val lastSyncDate = getLastSyncTime() ?: LocalDateTime.MIN
|
||||
|
||||
// try? await dataService.syncOfflineItemsWithServerIfNeeded()
|
||||
}
|
||||
|
||||
fun load(clearPreviousSearch: Boolean = false) {
|
||||
if (clearPreviousSearch) {
|
||||
cursor = null
|
||||
|
||||
@ -107,6 +107,13 @@ import Views
|
||||
_ = try? await dataService.labels()
|
||||
}
|
||||
}
|
||||
|
||||
// Sync Items
|
||||
// 1 - Create start timestamp
|
||||
// 2 - Retrieve last sync time from datastore
|
||||
// 3 - Call syncOfflineItemsWithServerIfNeeded (DataService)
|
||||
// 4 - Call dataService.syncLinkedItems
|
||||
//
|
||||
|
||||
func syncItems(dataService: DataService) async {
|
||||
let syncStart = Date.now
|
||||
|
||||
Reference in New Issue
Block a user