diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt index fb73fe3ed..c312f0c64 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt @@ -4,6 +4,39 @@ import android.util.Log import app.omnivore.omnivore.networking.* import app.omnivore.omnivore.persistence.entities.* +suspend fun DataService.librarySearch(cursor: String?, query: String): SavedItemSyncResult { + val searchResult = networker.search(cursor = cursor, limit = 10, query = query) + + val savedItems = searchResult.items.map { it.item } + + db.savedItemDao().insertAll(savedItems) + + val labels: MutableList = mutableListOf() + val crossRefs: MutableList = mutableListOf() + + // save labels + for (searchItem in searchResult.items) { + labels.addAll(searchItem.labels) + + val newCrossRefs = searchItem.labels.map { + SavedItemAndSavedItemLabelCrossRef(savedItemLabelId = it.savedItemLabelId, savedItemId = searchItem.item.savedItemId) + } + + crossRefs.addAll(newCrossRefs) + } + + db.savedItemLabelDao().insertAll(labels) + db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs) + + return SavedItemSyncResult( + hasError = false, + hasMoreItems = false, + cursor = searchResult.cursor, + count = searchResult.items.size, + savedItemSlugs = savedItems.map { it.slug } + ) +} + suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 20): SavedItemSyncResult { val syncResult = networker.savedItemUpdates(cursor = cursor, limit = limit, since = since) ?: return SavedItemSyncResult.errorResult diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SearchQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SearchQuery.kt index f1718846e..a1706493e 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SearchQuery.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SearchQuery.kt @@ -2,7 +2,9 @@ package app.omnivore.omnivore.networking import app.omnivore.omnivore.graphql.generated.SearchQuery import app.omnivore.omnivore.graphql.generated.TypeaheadSearchQuery +import app.omnivore.omnivore.persistence.entities.SavedItem import app.omnivore.omnivore.persistence.entities.SavedItemCardData +import app.omnivore.omnivore.persistence.entities.SavedItemLabel import com.apollographql.apollo3.api.Optional data class SearchQueryResponse( @@ -10,6 +12,16 @@ data class SearchQueryResponse( val cardsData: List ) +data class LibrarySearchQueryResponse( + val cursor: String?, + val items: List +) + +data class LibrarySearchItem( + val item: SavedItem, + val labels: List +) + suspend fun Networker.typeaheadSearch( query: String ): SearchQueryResponse { @@ -44,7 +56,7 @@ suspend fun Networker.search( cursor: String? = null, limit: Int = 15, query: String -): SearchQueryResponse { +): LibrarySearchQueryResponse { try { val result = authenticatedApolloClient().query( SearchQuery( @@ -57,22 +69,46 @@ suspend fun Networker.search( val newCursor = result.data?.search?.onSearchSuccess?.pageInfo?.endCursor val itemList = result.data?.search?.onSearchSuccess?.edges ?: listOf() - val cardsData = itemList.map { - SavedItemCardData( - savedItemId = it.node.id, - slug = it.node.slug, - publisherURLString = it.node.originalArticleUrl, - title = it.node.title, - author = it.node.author, - imageURLString = it.node.image, - isArchived = it.node.isArchived, - pageURLString = it.node.url, - contentReader = it.node.contentReader.rawValue, + val searchItems = itemList.map { + LibrarySearchItem( + item = SavedItem( + savedItemId = it.node.id, + title = it.node.title, + createdAt = it.node.createdAt as String, + savedAt = it.node.savedAt as String, + readAt = it.node.readAt as String?, + updatedAt = it.node.updatedAt as String?, + readingProgress = it.node.readingProgressPercent, + readingProgressAnchor = it.node.readingProgressAnchorIndex, + imageURLString = it.node.image, + pageURLString = it.node.url, + descriptionText = it.node.description, + publisherURLString = it.node.originalArticleUrl, + siteName = it.node.siteName, + author = it.node.author, + publishDate = it.node.publishedAt as String?, + slug = it.node.slug, + isArchived = it.node.isArchived, + contentReader = it.node.contentReader.rawValue, + content = null + ), + labels = (it.node.labels ?: listOf()).map { label -> + SavedItemLabel( + savedItemLabelId = label.id, + name = label.name, + color = label.color, + createdAt = null, + labelDescription = null + ) + } ) } - return SearchQueryResponse(newCursor, cardsData) + return LibrarySearchQueryResponse( + cursor = newCursor, + items = searchItems + ) } catch (e: java.lang.Exception) { - return SearchQueryResponse(null, listOf()) + return LibrarySearchQueryResponse(null, listOf()) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt index a43fd26f4..6dec98082 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt @@ -109,7 +109,11 @@ fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) { } InfiniteListHandler(listState = listState) { - libraryViewModel.load() + if (cardsData.isEmpty()) { + libraryViewModel.load() + } else { + libraryViewModel.loadUsingSearchAPI() + } } PullRefreshIndicator( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt index cc8730d76..d7af687cb 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt @@ -25,6 +25,7 @@ class LibraryViewModel @Inject constructor( private val datastoreRepo: DatastoreRepository ): ViewModel() { private var cursor: String? = null + private var librarySearchCursor: String? = null // These are used to make sure we handle search result // responses in the right order @@ -108,6 +109,21 @@ class LibraryViewModel @Inject constructor( performSearch(clearPreviousSearch) } else { syncItems() + loadUsingSearchAPI() + } + } + } + + fun loadUsingSearchAPI() { + viewModelScope.launch { + withContext(Dispatchers.IO) { + val result = dataService.librarySearch(cursor = librarySearchCursor, query = searchQueryString()) + result.cursor?.let { + librarySearchCursor = it + } + CoroutineScope(Dispatchers.Main).launch { + isRefreshing = false + } } } } @@ -136,6 +152,7 @@ class LibraryViewModel @Inject constructor( } suspend fun handleFilterChanges() { + librarySearchCursor = null if (searchTextLiveData.value != "") { performSearch(true) } else if (appliedSortFilterLiveData.value != null && appliedFilterLiveData.value != null) { @@ -194,7 +211,7 @@ class LibraryViewModel @Inject constructor( searchIdx += 1 // Execute the search - val searchResult = networker.typeaheadSearch(searchQueryString()) + val searchResult = networker.typeaheadSearch(searchTextLiveData.value ?: "") // Search results aren't guaranteed to return in order so this // will discard old results that are returned while a user is typing. @@ -237,16 +254,14 @@ class LibraryViewModel @Inject constructor( } private fun searchQueryString(): String { - return searchTextLiveData.value ?: "" - // Unused code for typeahead search -// var query = "${appliedFilterLiveData.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}" -// val searchText = searchTextLiveData.value ?: "" -// -// if (searchText.isNotEmpty()) { -// query += " $searchText" -// } -// -// return query + var query = "${appliedFilterLiveData.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}" + val searchText = searchTextLiveData.value ?: "" + + if (searchText.isNotEmpty()) { + query += " $searchText" + } + + return query } }