From 2ff12a70ad3eb86db0747ee37ef925fbb2577a6d Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 19 Apr 2023 21:11:38 +0800 Subject: [PATCH] Update GQL, fetch wordsCount --- .../src/main/graphql/ArticleContent.graphql | 1 + .../app/src/main/graphql/Search.graphql | 5 + .../app/src/main/graphql/UpdatesSince.graphql | 5 + .../app/src/main/graphql/schema.graphqls | 603 +++++++++++++++++- .../dataService/HighlightActionHandlers.kt | 4 +- .../omnivore/dataService/LibrarySync.kt | 3 +- .../dataService/SyncOfflineChanges.kt | 7 +- .../omnivore/networking/HighlightMutations.kt | 4 +- .../omnivore/networking/SavedItemQuery.kt | 3 +- .../omnivore/networking/SearchQuery.kt | 3 +- .../omnivore/persistence/AppDatabase.kt | 2 +- .../persistence/entities/Highlight.kt | 4 +- .../persistence/entities/SavedItem.kt | 6 +- .../omnivore/ui/library/LibraryViewModel.kt | 9 +- .../omnivore/omnivore/ui/reader/PDFReader.kt | 15 +- .../omnivore/ui/reader/PDFReaderViewModel.kt | 4 +- .../omnivore/ui/reader/WebReaderViewModel.kt | 5 +- .../ui/savedItemViews/SavedItemCard.kt | 103 ++- 18 files changed, 746 insertions(+), 40 deletions(-) diff --git a/android/Omnivore/app/src/main/graphql/ArticleContent.graphql b/android/Omnivore/app/src/main/graphql/ArticleContent.graphql index 350999bf7..ea8a8380f 100644 --- a/android/Omnivore/app/src/main/graphql/ArticleContent.graphql +++ b/android/Omnivore/app/src/main/graphql/ArticleContent.graphql @@ -40,6 +40,7 @@ fragment ArticleFields on Article { readAt updatedAt content + wordsCount } fragment HighlightFields on Highlight { diff --git a/android/Omnivore/app/src/main/graphql/Search.graphql b/android/Omnivore/app/src/main/graphql/Search.graphql index 7b231bafb..9a397ce81 100644 --- a/android/Omnivore/app/src/main/graphql/Search.graphql +++ b/android/Omnivore/app/src/main/graphql/Search.graphql @@ -26,6 +26,10 @@ query Search($after: String, $first: Int, $query: String) { name color } + highlights { + id + type + } pageId shortId quote @@ -36,6 +40,7 @@ query Search($after: String, $first: Int, $query: String) { readAt savedAt updatedAt + wordsCount } } pageInfo { diff --git a/android/Omnivore/app/src/main/graphql/UpdatesSince.graphql b/android/Omnivore/app/src/main/graphql/UpdatesSince.graphql index e7426f016..d1a7434bd 100644 --- a/android/Omnivore/app/src/main/graphql/UpdatesSince.graphql +++ b/android/Omnivore/app/src/main/graphql/UpdatesSince.graphql @@ -28,6 +28,10 @@ query UpdatesSince($after: String, $first: Int, $since: Date!) { name color } + highlights { + id + type + } pageId shortId quote @@ -39,6 +43,7 @@ query UpdatesSince($after: String, $first: Int, $since: Date!) { savedAt updatedAt language + wordsCount } } pageInfo { diff --git a/android/Omnivore/app/src/main/graphql/schema.graphqls b/android/Omnivore/app/src/main/graphql/schema.graphqls index a0d0e1161..420960b75 100644 --- a/android/Omnivore/app/src/main/graphql/schema.graphqls +++ b/android/Omnivore/app/src/main/graphql/schema.graphqls @@ -1,4 +1,4 @@ -directive @sanitize(allowedTags: [String], maxLength: Int, pattern: String) on INPUT_FIELD_DEFINITION +directive @sanitize(allowedTags: [String], maxLength: Int, minLength: Int, pattern: String) on INPUT_FIELD_DEFINITION type AddPopularReadError { errorCodes: [AddPopularReadErrorCode!]! @@ -86,6 +86,8 @@ type Article { readAt: Date readingProgressAnchorIndex: Int! readingProgressPercent: Float! + readingProgressTopPercent: Float + recommendations: [Recommendation!] savedAt: Date! savedByViewer: Boolean shareInfo: LinkShareInfo @@ -101,6 +103,7 @@ type Article { updatedAt: Date! uploadFileId: ID url: String! + wordsCount: Int } type ArticleEdge { @@ -132,6 +135,7 @@ type ArticleSavingRequest { slug: String! status: ArticleSavingRequestStatus! updatedAt: Date! + url: String! user: User! userId: ID! @deprecated(reason: "userId has been replaced with user") } @@ -141,6 +145,7 @@ type ArticleSavingRequestError { } enum ArticleSavingRequestErrorCode { + BAD_DATA NOT_FOUND UNAUTHORIZED } @@ -148,6 +153,7 @@ enum ArticleSavingRequestErrorCode { union ArticleSavingRequestResult = ArticleSavingRequestError | ArticleSavingRequestSuccess enum ArticleSavingRequestStatus { + ARCHIVED DELETED FAILED PROCESSING @@ -177,7 +183,27 @@ type ArticlesSuccess { pageInfo: PageInfo! } +type BulkActionError { + errorCodes: [BulkActionErrorCode!]! +} + +enum BulkActionErrorCode { + UNAUTHORIZED +} + +union BulkActionResult = BulkActionError | BulkActionSuccess + +type BulkActionSuccess { + success: Boolean! +} + +enum BulkActionType { + ARCHIVE + DELETE +} + enum ContentReader { + EPUB PDF WEB } @@ -198,9 +224,11 @@ enum CreateArticleErrorCode { input CreateArticleInput { articleSavingRequestId: ID + labels: [CreateLabelInput!] preparedDocument: PreparedDocumentInput skipParsing: Boolean source: String + state: ArticleSavingRequestStatus uploadFileId: ID url: String! } @@ -232,6 +260,31 @@ type CreateArticleSuccess { user: User! } +type CreateGroupError { + errorCodes: [CreateGroupErrorCode!]! +} + +enum CreateGroupErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +input CreateGroupInput { + description: String + expiresInDays: Int + maxMembers: Int + name: String! + onlyAdminCanPost: Boolean + onlyAdminCanSeeMembers: Boolean + topics: [String!] +} + +union CreateGroupResult = CreateGroupError | CreateGroupSuccess + +type CreateGroupSuccess { + group: RecommendationGroup! +} + type CreateHighlightError { errorCodes: [CreateHighlightErrorCode!]! } @@ -247,13 +300,17 @@ enum CreateHighlightErrorCode { input CreateHighlightInput { annotation: String articleId: ID! + highlightPositionAnchorIndex: Int + highlightPositionPercent: Float + html: String id: ID! - patch: String! + patch: String prefix: String - quote: String! + quote: String sharedAt: Date shortId: String! suffix: String + type: HighlightType } type CreateHighlightReplyError { @@ -296,7 +353,7 @@ enum CreateLabelErrorCode { } input CreateLabelInput { - color: String! + color: String description: String name: String! } @@ -388,6 +445,22 @@ type DeleteAccountSuccess { userID: ID! } +type DeleteFilterError { + errorCodes: [DeleteFilterErrorCode!]! +} + +enum DeleteFilterErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +union DeleteFilterResult = DeleteFilterError | DeleteFilterSuccess + +type DeleteFilterSuccess { + filter: Filter! +} + type DeleteHighlightError { errorCodes: [DeleteHighlightErrorCode!]! } @@ -500,6 +573,22 @@ type DeleteReminderSuccess { reminder: Reminder! } +type DeleteRuleError { + errorCodes: [DeleteRuleErrorCode!]! +} + +enum DeleteRuleErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +union DeleteRuleResult = DeleteRuleError | DeleteRuleSuccess + +type DeleteRuleSuccess { + rule: Rule! +} + type DeleteWebhookError { errorCodes: [DeleteWebhookErrorCode!]! } @@ -522,6 +611,31 @@ type DeviceToken { token: String! } +type DeviceTokensError { + errorCodes: [DeviceTokensErrorCode!]! +} + +enum DeviceTokensErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union DeviceTokensResult = DeviceTokensError | DeviceTokensSuccess + +type DeviceTokensSuccess { + deviceTokens: [DeviceToken!]! +} + +type Feature { + createdAt: Date! + expiresAt: Date + grantedAt: Date + id: ID! + name: String! + token: String! + updatedAt: Date! +} + type FeedArticle { annotationsCount: Int article: Article! @@ -555,6 +669,31 @@ type FeedArticlesSuccess { pageInfo: PageInfo! } +type Filter { + createdAt: Date! + description: String + filter: String! + id: ID! + name: String! + position: Int! + updatedAt: Date! +} + +type FiltersError { + errorCodes: [FiltersErrorCode!]! +} + +enum FiltersErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union FiltersResult = FiltersError | FiltersSuccess + +type FiltersSuccess { + filters: [Filter!]! +} + type GenerateApiKeyError { errorCodes: [GenerateApiKeyErrorCode!]! } @@ -644,19 +783,39 @@ type GoogleSignupSuccess { me: User! } +type GroupsError { + errorCodes: [GroupsErrorCode!]! +} + +enum GroupsErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union GroupsResult = GroupsError | GroupsSuccess + +type GroupsSuccess { + groups: [RecommendationGroup!]! +} + type Highlight { annotation: String createdAt: Date! createdByMe: Boolean! + highlightPositionAnchorIndex: Int + highlightPositionPercent: Float + html: String id: ID! - patch: String! + labels: [Label!] + patch: String prefix: String - quote: String! + quote: String reactions: [Reaction!]! replies: [HighlightReply!]! sharedAt: Date shortId: String! suffix: String + type: HighlightType! updatedAt: Date! user: User! } @@ -674,17 +833,40 @@ type HighlightStats { highlightCount: Int! } +enum HighlightType { + HIGHLIGHT + NOTE + REDACTION +} + +type ImportFromIntegrationError { + errorCodes: [ImportFromIntegrationErrorCode!]! +} + +enum ImportFromIntegrationErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union ImportFromIntegrationResult = ImportFromIntegrationError | ImportFromIntegrationSuccess + +type ImportFromIntegrationSuccess { + success: Boolean! +} + type Integration { createdAt: Date! enabled: Boolean! id: ID! + name: String! token: String! type: IntegrationType! updatedAt: Date! } enum IntegrationType { - READWISE + EXPORT + IMPORT } type IntegrationsError { @@ -702,6 +884,22 @@ type IntegrationsSuccess { integrations: [Integration!]! } +type JoinGroupError { + errorCodes: [JoinGroupErrorCode!]! +} + +enum JoinGroupErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +union JoinGroupResult = JoinGroupError | JoinGroupSuccess + +type JoinGroupSuccess { + group: RecommendationGroup! +} + type Label { color: String! createdAt: Date @@ -727,6 +925,22 @@ type LabelsSuccess { labels: [Label!]! } +type LeaveGroupError { + errorCodes: [LeaveGroupErrorCode!]! +} + +enum LeaveGroupErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +union LeaveGroupResult = LeaveGroupError | LeaveGroupSuccess + +type LeaveGroupSuccess { + success: Boolean! +} + type Link { highlightStats: HighlightStats! id: ID! @@ -782,6 +996,22 @@ type LoginSuccess { me: User! } +type MarkEmailAsItemError { + errorCodes: [MarkEmailAsItemErrorCode!]! +} + +enum MarkEmailAsItemErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +union MarkEmailAsItemResult = MarkEmailAsItemError | MarkEmailAsItemSuccess + +type MarkEmailAsItemSuccess { + success: Boolean! +} + type MergeHighlightError { errorCodes: [MergeHighlightErrorCode!]! } @@ -797,6 +1027,9 @@ enum MergeHighlightErrorCode { input MergeHighlightInput { annotation: String articleId: ID! + highlightPositionAnchorIndex: Int + highlightPositionPercent: Float + html: String id: ID! overlapHighlightIdList: [String!]! patch: String! @@ -813,6 +1046,27 @@ type MergeHighlightSuccess { overlapHighlightIdList: [String!]! } +type MoveFilterError { + errorCodes: [MoveFilterErrorCode!]! +} + +enum MoveFilterErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +input MoveFilterInput { + afterFilterId: ID + filterId: ID! +} + +union MoveFilterResult = MoveFilterError | MoveFilterSuccess + +type MoveFilterSuccess { + filter: Filter! +} + type MoveLabelError { errorCodes: [MoveLabelErrorCode!]! } @@ -836,8 +1090,10 @@ type MoveLabelSuccess { type Mutation { addPopularRead(name: String!): AddPopularReadResult! + bulkAction(action: BulkActionType!, query: String): BulkActionResult! createArticle(input: CreateArticleInput!): CreateArticleResult! createArticleSavingRequest(input: CreateArticleSavingRequestInput!): CreateArticleSavingRequestResult! + createGroup(input: CreateGroupInput!): CreateGroupResult! createHighlight(input: CreateHighlightInput!): CreateHighlightResult! createHighlightReply(input: CreateHighlightReplyInput!): CreateHighlightReplyResult! createLabel(input: CreateLabelInput!): CreateLabelResult! @@ -845,6 +1101,7 @@ type Mutation { createReaction(input: CreateReactionInput!): CreateReactionResult! createReminder(input: CreateReminderInput!): CreateReminderResult! deleteAccount(userID: ID!): DeleteAccountResult! + deleteFilter(id: ID!): DeleteFilterResult! deleteHighlight(highlightId: ID!): DeleteHighlightResult! deleteHighlightReply(highlightReplyId: ID!): DeleteHighlightReplyResult! deleteIntegration(id: ID!): DeleteIntegrationResult! @@ -852,17 +1109,27 @@ type Mutation { deleteNewsletterEmail(newsletterEmailId: ID!): DeleteNewsletterEmailResult! deleteReaction(id: ID!): DeleteReactionResult! deleteReminder(id: ID!): DeleteReminderResult! + deleteRule(id: ID!): DeleteRuleResult! deleteWebhook(id: ID!): DeleteWebhookResult! generateApiKey(input: GenerateApiKeyInput!): GenerateApiKeyResult! googleLogin(input: GoogleLoginInput!): LoginResult! googleSignup(input: GoogleSignupInput!): GoogleSignupResult! + importFromIntegration(integrationId: ID!): ImportFromIntegrationResult! + joinGroup(inviteCode: String!): JoinGroupResult! + leaveGroup(groupId: ID!): LeaveGroupResult! logOut: LogOutResult! + markEmailAsItem(recentEmailId: ID!): MarkEmailAsItemResult! mergeHighlight(input: MergeHighlightInput!): MergeHighlightResult! + moveFilter(input: MoveFilterInput!): MoveFilterResult! moveLabel(input: MoveLabelInput!): MoveLabelResult! + optInFeature(input: OptInFeatureInput!): OptInFeatureResult! + recommend(input: RecommendInput!): RecommendResult! + recommendHighlights(input: RecommendHighlightsInput!): RecommendHighlightsResult! reportItem(input: ReportItemInput!): ReportItemResult! revokeApiKey(id: ID!): RevokeApiKeyResult! saveArticleReadingProgress(input: SaveArticleReadingProgressInput!): SaveArticleReadingProgressResult! saveFile(input: SaveFileInput!): SaveResult! + saveFilter(input: SaveFilterInput!): SaveFilterResult! savePage(input: SavePageInput!): SaveResult! saveUrl(input: SaveUrlInput!): SaveResult! setBookmarkArticle(input: SetBookmarkArticleInput!): SetBookmarkArticleResult! @@ -872,6 +1139,7 @@ type Mutation { setLabels(input: SetLabelsInput!): SetLabelsResult! setLabelsForHighlight(input: SetLabelsForHighlightInput!): SetLabelsResult! setLinkArchived(input: ArchiveLinkInput!): ArchiveLinkResult! + setRule(input: SetRuleInput!): SetRuleResult! setShareArticle(input: SetShareArticleInput!): SetShareArticleResult! setShareHighlight(input: SetShareHighlightInput!): SetShareHighlightResult! setUserPersonalization(input: SetUserPersonalizationInput!): SetUserPersonalizationResult! @@ -888,12 +1156,15 @@ type Mutation { updateUser(input: UpdateUserInput!): UpdateUserResult! updateUserProfile(input: UpdateUserProfileInput!): UpdateUserProfileResult! uploadFileRequest(input: UploadFileRequestInput!): UploadFileRequestResult! + uploadImportFile(contentType: String!, type: UploadImportFileType!): UploadImportFileResult! } type NewsletterEmail { address: String! confirmationCode: String + createdAt: Date! id: ID! + subscriptionCount: Int! } type NewsletterEmailsError { @@ -911,6 +1182,25 @@ type NewsletterEmailsSuccess { newsletterEmails: [NewsletterEmail!]! } +type OptInFeatureError { + errorCodes: [OptInFeatureErrorCode!]! +} + +enum OptInFeatureErrorCode { + BAD_REQUEST + NOT_FOUND +} + +input OptInFeatureInput { + name: String! +} + +union OptInFeatureResult = OptInFeatureError | OptInFeatureSuccess + +type OptInFeatureSuccess { + feature: Feature! +} + type Page { author: String createdAt: Date! @@ -951,11 +1241,29 @@ enum PageType { BOOK FILE HIGHLIGHTS + IMAGE PROFILE + TWEET UNKNOWN + VIDEO WEBSITE } +input ParseResult { + byline: String + content: String! + dir: String + excerpt: String! + language: String + length: Int! + previewImage: String + publishedDate: Date + siteIcon: String + siteName: String + textContent: String! + title: String! +} + input PreparedDocumentInput { document: String! pageInfo: PageInfoInput! @@ -971,25 +1279,31 @@ type Profile { type Query { apiKeys: ApiKeysResult! - article(slug: String!, username: String!): ArticleResult! - articleSavingRequest(id: ID!): ArticleSavingRequestResult! + article(format: String, slug: String!, username: String!): ArticleResult! + articleSavingRequest(id: ID, url: String): ArticleSavingRequestResult! articles(after: String, first: Int, includePending: Boolean, query: String, sharedOnly: Boolean, sort: SortParams): ArticlesResult! + deviceTokens: DeviceTokensResult! feedArticles(after: String, first: Int, sharedByUser: ID, sort: SortParams): FeedArticlesResult! + filters: FiltersResult! getFollowers(userId: ID): GetFollowersResult! getFollowing(userId: ID): GetFollowingResult! getUserPersonalization: GetUserPersonalizationResult! + groups: GroupsResult! hello: String integrations: IntegrationsResult! labels: LabelsResult! me: User newsletterEmails: NewsletterEmailsResult! + recentEmails: RecentEmailsResult! + recentSearches: RecentSearchesResult! reminder(linkId: ID!): ReminderResult! - search(after: String, first: Int, query: String): SearchResult! + rules(enabled: Boolean): RulesResult! + search(after: String, first: Int, format: String, includeContent: Boolean, query: String): SearchResult! sendInstallInstructions: SendInstallInstructionsResult! sharedArticle(selectedHighlightId: String, slug: String!, username: String!): SharedArticleResult! subscriptions(sort: SortParams): SubscriptionsResult! typeaheadSearch(first: Int, query: String!): TypeaheadSearchResult! - updatesSince(after: String, first: Int, since: Date!): UpdatesSinceResult! + updatesSince(after: String, first: Int, since: Date!, sort: SortParams): UpdatesSinceResult! user(userId: ID, username: String): UserResult! users: UsersResult! validateUsername(username: String!): Boolean! @@ -1021,6 +1335,128 @@ type ReadState { readingTime: Int } +type RecentEmail { + createdAt: Date! + from: String! + html: String + id: ID! + subject: String! + text: String! + to: String! + type: String! +} + +type RecentEmailsError { + errorCodes: [RecentEmailsErrorCode!]! +} + +enum RecentEmailsErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union RecentEmailsResult = RecentEmailsError | RecentEmailsSuccess + +type RecentEmailsSuccess { + recentEmails: [RecentEmail!]! +} + +type RecentSearch { + createdAt: Date! + id: ID! + term: String! +} + +type RecentSearchesError { + errorCodes: [RecentSearchesErrorCode!]! +} + +enum RecentSearchesErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union RecentSearchesResult = RecentSearchesError | RecentSearchesSuccess + +type RecentSearchesSuccess { + searches: [RecentSearch!]! +} + +type RecommendError { + errorCodes: [RecommendErrorCode!]! +} + +enum RecommendErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +type RecommendHighlightsError { + errorCodes: [RecommendHighlightsErrorCode!]! +} + +enum RecommendHighlightsErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +input RecommendHighlightsInput { + groupIds: [ID!]! + highlightIds: [ID!]! + note: String + pageId: ID! +} + +union RecommendHighlightsResult = RecommendHighlightsError | RecommendHighlightsSuccess + +type RecommendHighlightsSuccess { + success: Boolean! +} + +input RecommendInput { + groupIds: [ID!]! + note: String + pageId: ID! + recommendedWithHighlights: Boolean +} + +union RecommendResult = RecommendError | RecommendSuccess + +type RecommendSuccess { + success: Boolean! +} + +type Recommendation { + id: ID! + name: String! + note: String + recommendedAt: Date! + user: RecommendingUser +} + +type RecommendationGroup { + admins: [User!]! + canPost: Boolean! + canSeeMembers: Boolean! + createdAt: Date! + description: String + id: ID! + inviteUrl: String! + members: [User!]! + name: String! + topics: [String!] + updatedAt: Date! +} + +type RecommendingUser { + name: String! + profileImageURL: String + userId: String! + username: String! +} + type Reminder { archiveUntil: Boolean! id: ID! @@ -1079,6 +1515,48 @@ type RevokeApiKeySuccess { apiKey: ApiKey! } +type Rule { + actions: [RuleAction!]! + createdAt: Date! + enabled: Boolean! + filter: String! + id: ID! + name: String! + updatedAt: Date! +} + +type RuleAction { + params: [String!]! + type: RuleActionType! +} + +input RuleActionInput { + params: [String!]! + type: RuleActionType! +} + +enum RuleActionType { + ADD_LABEL + ARCHIVE + MARK_AS_READ + SEND_NOTIFICATION +} + +type RulesError { + errorCodes: [RulesErrorCode!]! +} + +enum RulesErrorCode { + BAD_REQUEST + UNAUTHORIZED +} + +union RulesResult = RulesError | RulesSuccess + +type RulesSuccess { + rules: [Rule!]! +} + type SaveArticleReadingProgressError { errorCodes: [SaveArticleReadingProgressErrorCode!]! } @@ -1093,6 +1571,7 @@ input SaveArticleReadingProgressInput { id: ID! readingProgressAnchorIndex: Int! readingProgressPercent: Float! + readingProgressTopPercent: Float } union SaveArticleReadingProgressResult = SaveArticleReadingProgressError | SaveArticleReadingProgressSuccess @@ -1107,21 +1586,50 @@ type SaveError { } enum SaveErrorCode { + EMBEDDED_HIGHLIGHT_FAILED UNAUTHORIZED UNKNOWN } input SaveFileInput { clientRequestId: ID! + labels: [CreateLabelInput!] source: String! + state: ArticleSavingRequestStatus uploadFileId: ID! url: String! } +type SaveFilterError { + errorCodes: [SaveFilterErrorCode!]! +} + +enum SaveFilterErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +input SaveFilterInput { + description: String + filter: String! + id: ID + name: String! +} + +union SaveFilterResult = SaveFilterError | SaveFilterSuccess + +type SaveFilterSuccess { + filter: Filter! +} + input SavePageInput { clientRequestId: ID! + labels: [CreateLabelInput!] originalContent: String! + parseResult: ParseResult source: String! + state: ArticleSavingRequestStatus title: String url: String! } @@ -1135,7 +1643,9 @@ type SaveSuccess { input SaveUrlInput { clientRequestId: ID! + labels: [CreateLabelInput!] source: String! + state: ArticleSavingRequestStatus url: String! } @@ -1144,12 +1654,14 @@ type SearchError { } enum SearchErrorCode { + QUERY_TOO_LONG UNAUTHORIZED } type SearchItem { annotation: String author: String + content: String contentReader: ContentReader! createdAt: Date! description: String @@ -1168,8 +1680,11 @@ type SearchItem { readAt: Date readingProgressAnchorIndex: Int! readingProgressPercent: Float! + readingProgressTopPercent: Float + recommendations: [Recommendation!] savedAt: Date! shortId: String + siteIcon: String siteName: String slug: String! state: ArticleSavingRequestStatus @@ -1180,6 +1695,7 @@ type SearchItem { updatedAt: Date uploadFileId: ID url: String! + wordsCount: Int } type SearchItemEdge { @@ -1287,8 +1803,9 @@ enum SetIntegrationErrorCode { input SetIntegrationInput { enabled: Boolean! id: ID + name: String! token: String! - type: IntegrationType! + type: IntegrationType } union SetIntegrationResult = SetIntegrationError | SetIntegrationSuccess @@ -1323,6 +1840,31 @@ type SetLabelsSuccess { labels: [Label!]! } +type SetRuleError { + errorCodes: [SetRuleErrorCode!]! +} + +enum SetRuleErrorCode { + BAD_REQUEST + NOT_FOUND + UNAUTHORIZED +} + +input SetRuleInput { + actions: [RuleActionInput!]! + description: String + enabled: Boolean! + filter: String! + id: ID + name: String! +} + +union SetRuleResult = SetRuleError | SetRuleSuccess + +type SetRuleSuccess { + rule: Rule! +} + type SetShareArticleError { errorCodes: [SetShareArticleErrorCode!]! } @@ -1373,6 +1915,7 @@ type SetUserPersonalizationError { } enum SetUserPersonalizationErrorCode { + NOT_FOUND UNAUTHORIZED } @@ -1382,6 +1925,10 @@ input SetUserPersonalizationInput { libraryLayoutType: String librarySortOrder: SortOrder margin: Int + speechRate: String + speechSecondaryVoice: String + speechVoice: String + speechVolume: String theme: String } @@ -1485,6 +2032,7 @@ type SubscribeSuccess { type Subscription { createdAt: Date! description: String + icon: String id: ID! name: String! newsletterEmail: String! @@ -1576,6 +2124,8 @@ enum UpdateHighlightErrorCode { input UpdateHighlightInput { annotation: String highlightId: ID! + html: String + quote: String sharedAt: Date } @@ -1664,8 +2214,11 @@ enum UpdatePageErrorCode { } input UpdatePageInput { + byline: String description: String pageId: ID! + publishedAt: Date + savedAt: Date title: String } @@ -1818,6 +2371,28 @@ enum UploadFileStatus { INITIALIZED } +type UploadImportFileError { + errorCodes: [UploadImportFileErrorCode!]! +} + +enum UploadImportFileErrorCode { + BAD_REQUEST + UNAUTHORIZED + UPLOAD_DAILY_LIMIT_EXCEEDED +} + +union UploadImportFileResult = UploadImportFileError | UploadImportFileSuccess + +type UploadImportFileSuccess { + uploadSignedUrl: String +} + +enum UploadImportFileType { + MATTER + POCKET + URL_LIST +} + type User { followersCount: Int friendsCount: Int @@ -1851,6 +2426,10 @@ type UserPersonalization { libraryLayoutType: String librarySortOrder: SortOrder margin: Int + speechRate: String + speechSecondaryVoice: String + speechVoice: String + speechVolume: String theme: String } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/HighlightActionHandlers.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/HighlightActionHandlers.kt index 6a354fed4..8b539b918 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/HighlightActionHandlers.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/HighlightActionHandlers.kt @@ -15,10 +15,10 @@ suspend fun DataService.createWebHighlight(jsonString: String) { val highlight = Highlight( highlightId = createHighlightInput.id, shortId = createHighlightInput.shortId, - quote = createHighlightInput.quote, + quote = createHighlightInput.quote.getOrNull(), prefix = null, suffix = null, - patch = createHighlightInput.patch, + patch = createHighlightInput.patch.getOrNull(), annotation = createHighlightInput.annotation.getOrNull(), createdAt = null, updatedAt = null, 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 a5f558f9f..adb4fe09c 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 @@ -62,7 +62,8 @@ suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 20): S slug = it.slug, isArchived = it.isArchived, contentReader = it.contentReader.rawValue, - content = null + content = null, + wordsCount = it.wordsCount ) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/SyncOfflineChanges.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/SyncOfflineChanges.kt index 829c9aa66..b1c949474 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/SyncOfflineChanges.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/SyncOfflineChanges.kt @@ -7,10 +7,13 @@ import app.omnivore.omnivore.networking.* import app.omnivore.omnivore.persistence.entities.Highlight import app.omnivore.omnivore.persistence.entities.SavedItem import com.apollographql.apollo3.api.Optional +import com.apollographql.apollo3.api.Optional.Companion.absent +import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import java.util.* suspend fun DataService.startSyncChannels() { for (savedItem in savedItemSyncChannel) { @@ -130,8 +133,8 @@ private suspend fun DataService.syncHighlight(highlight: Highlight) { annotation = Optional.presentIfNotNull(highlight.annotation), articleId = savedItemID ?: "", id = highlight.highlightId, - patch = highlight.patch ?: "", - quote = highlight.quote ?: "", + patch = Optional.presentIfNotNull(highlight.patch), + quote = Optional.presentIfNotNull(highlight.quote), shortId = highlight.shortId ?: "" ) ) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/HighlightMutations.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/HighlightMutations.kt index 97260c72d..baf676707 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/HighlightMutations.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/HighlightMutations.kt @@ -24,8 +24,8 @@ data class CreateHighlightParams( annotation = Optional.presentIfNotNull(`annotation`), articleId = articleId ?: "", id = id ?: "", - patch = patch ?: "", - quote = quote ?: "", + patch = Optional.presentIfNotNull(patch), + quote = Optional.presentIfNotNull(quote), shortId = shortId ?: "" ) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemQuery.kt index bc5ec8840..bfad2f85b 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemQuery.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemQuery.kt @@ -77,7 +77,8 @@ suspend fun Networker.savedItem(slug: String): SavedItemQueryResponse { slug = article.articleFields.slug, isArchived = article.articleFields.isArchived, contentReader = article.articleFields.contentReader.rawValue, - content = article.articleFields.content + content = article.articleFields.content, + wordsCount = article.articleFields.wordsCount ) return SavedItemQueryResponse(item = savedItem, highlights, labels = savedItemLabels, state = article.articleFields.state?.rawValue ?: "") 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 5a1811474..45bb98d1a 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 @@ -86,7 +86,8 @@ suspend fun Networker.search( slug = it.node.slug, isArchived = it.node.isArchived, contentReader = it.node.contentReader.rawValue, - content = null + content = null, + wordsCount = it.node.wordsCount, ), labels = (it.node.labels ?: listOf()).map { label -> SavedItemLabel( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/AppDatabase.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/AppDatabase.kt index 2557e4e7e..5d8322175 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/AppDatabase.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/AppDatabase.kt @@ -13,7 +13,7 @@ import app.omnivore.omnivore.persistence.entities.* SavedItemAndSavedItemLabelCrossRef::class, SavedItemAndHighlightCrossRef::class ], - version = 3 + version = 5 ) abstract class AppDatabase : RoomDatabase() { abstract fun viewerDao(): ViewerDao diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Highlight.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Highlight.kt index 757a8ee42..fa7b6a45e 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Highlight.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Highlight.kt @@ -11,9 +11,9 @@ data class Highlight( val createdAt: String?, val createdByMe: Boolean, val markedForDeletion: Boolean = false, - var patch: String, + var patch: String?, var prefix: String?, - var quote: String, + var quote: String?, var serverSyncStatus: Int = ServerSyncStatus.IS_SYNCED.rawValue, var shortId: String, val suffix: String?, diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt index ae493ebc2..9ad6c5cff 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt @@ -40,7 +40,8 @@ data class SavedItem( val originalHtml: String? = null, @ColumnInfo(typeAffinity = ColumnInfo.BLOB) val pdfData: ByteArray? = null, var serverSyncStatus: Int = 0, - val tempPDFURL: String? = null + val tempPDFURL: String? = null, + val wordsCount: Int? = null // hasMany highlights // hasMany labels @@ -78,6 +79,7 @@ data class SavedItemCardData( val contentReader: String?, val savedAt: String, val readingProgress: Double, + val wordsCount: Int? ) { fun publisherDisplayName(): String? { return publisherURLString?.toUri()?.host @@ -161,5 +163,5 @@ interface SavedItemDao { object SavedItemQueryConstants { - const val columns = "savedItemId, slug, publisherURLString, title, author, imageURLString, isArchived, pageURLString, contentReader, savedAt, readingProgress" + const val columns = "savedItemId, slug, publisherURLString, title, author, imageURLString, isArchived, pageURLString, contentReader, savedAt, readingProgress, wordsCount" } 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 a1c45a9ba..2e6812113 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 @@ -14,6 +14,7 @@ import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput import app.omnivore.omnivore.graphql.generated.type.SetLabelsInput import app.omnivore.omnivore.networking.* import app.omnivore.omnivore.persistence.entities.* +import com.apollographql.apollo3.api.Optional import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel @@ -141,7 +142,8 @@ class LibraryViewModel @Inject constructor( imageURLString = it.imageURLString, isArchived = it.isArchived, pageURLString = it.pageURLString, - contentReader = it.contentReader + contentReader = it.contentReader, + wordsCount = it.wordsCount ), labels = listOf() ) @@ -222,7 +224,8 @@ class LibraryViewModel @Inject constructor( pageURLString = it.pageURLString, contentReader = it.contentReader, savedAt = it.savedAt, - readingProgress = it.readingProgress + readingProgress = it.readingProgress, + wordsCount = it.wordsCount ), labels = listOf() ) @@ -388,7 +391,7 @@ class LibraryViewModel @Inject constructor( fun createNewSavedItemLabel(labelName: String, hexColorValue: String) { viewModelScope.launch { withContext(Dispatchers.IO) { - val newLabel = networker.createNewLabel(CreateLabelInput(color = hexColorValue, name = labelName)) + val newLabel = networker.createNewLabel(CreateLabelInput(color = Optional.presentIfNotNull(hexColorValue), name = labelName)) newLabel?.let { val savedItemLabel = SavedItemLabel( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt index d6249cd21..3cbd729d2 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt @@ -134,13 +134,16 @@ class PDFReaderActivity: AppCompatActivity(), DocumentListener, TextSelectionMan private fun loadHighlights(highlights: List) { for (highlight in highlights) { - val highlightAnnotation = fragment - .document - ?.annotationProvider - ?.createAnnotationFromInstantJson(highlight.patch) + val patch = highlight.patch + if (patch != null) { + val highlightAnnotation = fragment + .document + ?.annotationProvider + ?.createAnnotationFromInstantJson(patch) - highlightAnnotation?.let { - fragment.addAnnotationToPage(highlightAnnotation, true) + highlightAnnotation?.let { + fragment.addAnnotationToPage(highlightAnnotation, true) + } } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt index f5d37fc62..d5ec05cd7 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt @@ -133,8 +133,8 @@ class PDFReaderViewModel @Inject constructor( annotation = Optional.presentIfNotNull(note), articleId = itemID, id = highlightID, - patch = newAnnotation.toInstantJson(), - quote = quote, + patch = Optional.presentIfNotNull(newAnnotation.toInstantJson()), + quote = Optional.presentIfNotNull(quote), shortId = shortID, ) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt index ad442fbf9..38d037106 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt @@ -19,6 +19,8 @@ import app.omnivore.omnivore.networking.* import app.omnivore.omnivore.persistence.entities.SavedItemAndSavedItemLabelCrossRef import app.omnivore.omnivore.persistence.entities.SavedItemLabel import app.omnivore.omnivore.ui.library.SavedItemAction +import com.apollographql.apollo3.api.Optional +import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* @@ -394,7 +396,8 @@ class WebReaderViewModel @Inject constructor( fun createNewSavedItemLabel(labelName: String, hexColorValue: String) { viewModelScope.launch { withContext(Dispatchers.IO) { - val newLabel = networker.createNewLabel(CreateLabelInput(color = hexColorValue, name = labelName)) + + val newLabel = networker.createNewLabel(CreateLabelInput(color = Optional.presentIfNotNull(hexColorValue), name = labelName)) newLabel?.let { val savedItemLabel = SavedItemLabel( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt index ff85bfa7b..7ce81ce76 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt @@ -53,6 +53,8 @@ fun SavedItemCard(cardData: SavedItemCardData, labels: List, onC .padding(end = 20.dp) .defaultMinSize(minHeight = 55.dp) ) { + readInfo(item = cardData) + Text( text = cardData.title, style = TextStyle( @@ -66,7 +68,11 @@ fun SavedItemCard(cardData: SavedItemCardData, labels: List, onC if (cardData.author != null && cardData.author != "") { Text( text = byline(cardData), - style = MaterialTheme.typography.bodyMedium, + style = TextStyle( + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + color = Color(red = 137, green = 137, blue = 137) + ), maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -88,7 +94,7 @@ fun SavedItemCard(cardData: SavedItemCardData, labels: List, onC contentDescription = "Image associated with saved item", modifier = Modifier .size(55.dp, 73.dp) - .clip(RoundedCornerShape(4.dp)) + .clip(RoundedCornerShape(10.dp)) ) } } @@ -141,3 +147,96 @@ fun byline(item: SavedItemCardData): String { return "" } + +// +//var readingSpeed: Int64 { +// var result = UserDefaults.standard.integer(forKey: UserDefaultKey.userWordsPerMinute.rawValue) +// if result <= 0 { +// result = 235 +// } +// return Int64(result) +//} + +fun estimatedReadingTime(item: SavedItemCardData): String { + item.wordsCount?.let { + if (it > 0) { + val readLen = Math.max(1, it / 235) + return "$readLen MIN READ • " + } + } + return "" +} + +fun readingProgress(item: SavedItemCardData): String { + // If there is no wordsCount don't show progress because it will make no sense + item.wordsCount?.let { + if (it > 0) { + val intVal = item.readingProgress.toInt() + return "$intVal%" + } + } + return "" +} +// +//var highlightsText: String { +// if let highlights = item.highlights, highlights.count > 0 { +// let fmted = LocalText.pluralizedText(key: "number_of_highlights", count: highlights.count) +// if item.wordsCount > 0 { +// return " • \(fmted)" +// } +// return fmted +// } +// return "" +//} +// +//var notesText: String { +// let notes = item.highlights?.filter { item in +// if let highlight = item as? Highlight { +// return !(highlight.annotation ?? "").isEmpty +// } +// return false +// } +// +// if let notes = notes, notes.count > 0 { +// let fmted = LocalText.pluralizedText(key: "number_of_notes", count: notes.count) +// if item.wordsCount > 0 { +// return " • \(fmted)" +// } +// return fmted +// } +// return "" +//} + +@Composable +fun readInfo(item: SavedItemCardData) { + Row( + modifier = Modifier.fillMaxWidth().defaultMinSize(minHeight = 15.dp) + ) { + Text( + text = estimatedReadingTime(item), + style = TextStyle( + fontSize = 11.sp, + fontWeight = FontWeight.Medium, + color = Color(red = 137, green = 137, blue = 137) + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + Text( + text = readingProgress(item), + style = TextStyle( + fontSize = 11.sp, + fontWeight = FontWeight.Medium, + color = if (item.readingProgress > 1) Color(red = 85, green = 185, blue = 56) else Color(red = 137, green = 137, blue = 137) + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + +// Text("\(highlightsText)") +// +// Text("\(notesText)") + + } +}