From 04664510195e49027596884f95f82ba70356f4c5 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Sat, 27 May 2023 08:37:47 +0800 Subject: [PATCH 01/11] WIP: Android notebooks --- android/Omnivore/app/build.gradle | 1 + .../app/src/main/graphql/schema.graphqls | 164 +++++++++++++---- .../dataService/HighlightActionHandlers.kt | 57 ++++++ .../omnivore/networking/HighlightMutations.kt | 3 + .../persistence/entities/Highlight.kt | 1 + .../persistence/entities/SavedItem.kt | 16 ++ .../omnivore/ui/notebook/NotebookView.kt | 166 +++++++++--------- .../omnivore/ui/notebook/NotebookViewModel.kt | 18 +- .../ui/reader/WebReaderLoadingContainer.kt | 28 ++- 9 files changed, 330 insertions(+), 124 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index db0079448..4d05dc295 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -148,6 +148,7 @@ dependencies { // Room Deps implementation "androidx.room:room-runtime:$room_version" + implementation "androidx.room:room-ktx:$room_version" annotationProcessor "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version" diff --git a/android/Omnivore/app/src/main/graphql/schema.graphqls b/android/Omnivore/app/src/main/graphql/schema.graphqls index 420960b75..fd10083a7 100644 --- a/android/Omnivore/app/src/main/graphql/schema.graphqls +++ b/android/Omnivore/app/src/main/graphql/schema.graphqls @@ -1,4 +1,9 @@ -directive @sanitize(allowedTags: [String], maxLength: Int, minLength: 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!]! @@ -150,7 +155,9 @@ enum ArticleSavingRequestErrorCode { UNAUTHORIZED } -union ArticleSavingRequestResult = ArticleSavingRequestError | ArticleSavingRequestSuccess +union ArticleSavingRequestResult = + ArticleSavingRequestError + | ArticleSavingRequestSuccess enum ArticleSavingRequestStatus { ARCHIVED @@ -248,7 +255,9 @@ input CreateArticleSavingRequestInput { url: String! } -union CreateArticleSavingRequestResult = CreateArticleSavingRequestError | CreateArticleSavingRequestSuccess +union CreateArticleSavingRequestResult = + CreateArticleSavingRequestError + | CreateArticleSavingRequestSuccess type CreateArticleSavingRequestSuccess { articleSavingRequest: ArticleSavingRequest! @@ -329,7 +338,9 @@ input CreateHighlightReplyInput { text: String! } -union CreateHighlightReplyResult = CreateHighlightReplyError | CreateHighlightReplySuccess +union CreateHighlightReplyResult = + CreateHighlightReplyError + | CreateHighlightReplySuccess type CreateHighlightReplySuccess { highlightReply: HighlightReply! @@ -373,7 +384,9 @@ enum CreateNewsletterEmailErrorCode { UNAUTHORIZED } -union CreateNewsletterEmailResult = CreateNewsletterEmailError | CreateNewsletterEmailSuccess +union CreateNewsletterEmailResult = + CreateNewsletterEmailError + | CreateNewsletterEmailSuccess type CreateNewsletterEmailSuccess { newsletterEmail: NewsletterEmail! @@ -481,7 +494,9 @@ enum DeleteHighlightReplyErrorCode { UNAUTHORIZED } -union DeleteHighlightReplyResult = DeleteHighlightReplyError | DeleteHighlightReplySuccess +union DeleteHighlightReplyResult = + DeleteHighlightReplyError + | DeleteHighlightReplySuccess type DeleteHighlightReplySuccess { highlightReply: HighlightReply! @@ -503,7 +518,9 @@ enum DeleteIntegrationErrorCode { UNAUTHORIZED } -union DeleteIntegrationResult = DeleteIntegrationError | DeleteIntegrationSuccess +union DeleteIntegrationResult = + DeleteIntegrationError + | DeleteIntegrationSuccess type DeleteIntegrationSuccess { integration: Integration! @@ -535,7 +552,9 @@ enum DeleteNewsletterEmailErrorCode { UNAUTHORIZED } -union DeleteNewsletterEmailResult = DeleteNewsletterEmailError | DeleteNewsletterEmailSuccess +union DeleteNewsletterEmailResult = + DeleteNewsletterEmailError + | DeleteNewsletterEmailSuccess type DeleteNewsletterEmailSuccess { newsletterEmail: NewsletterEmail! @@ -752,7 +771,9 @@ enum GetUserPersonalizationErrorCode { UNAUTHORIZED } -union GetUserPersonalizationResult = GetUserPersonalizationError | GetUserPersonalizationSuccess +union GetUserPersonalizationResult = + GetUserPersonalizationError + | GetUserPersonalizationSuccess type GetUserPersonalizationSuccess { userPersonalization: UserPersonalization @@ -848,7 +869,9 @@ enum ImportFromIntegrationErrorCode { UNAUTHORIZED } -union ImportFromIntegrationResult = ImportFromIntegrationError | ImportFromIntegrationSuccess +union ImportFromIntegrationResult = + ImportFromIntegrationError + | ImportFromIntegrationSuccess type ImportFromIntegrationSuccess { success: Boolean! @@ -1092,10 +1115,14 @@ type Mutation { addPopularRead(name: String!): AddPopularReadResult! bulkAction(action: BulkActionType!, query: String): BulkActionResult! createArticle(input: CreateArticleInput!): CreateArticleResult! - createArticleSavingRequest(input: CreateArticleSavingRequestInput!): CreateArticleSavingRequestResult! + createArticleSavingRequest( + input: CreateArticleSavingRequestInput! + ): CreateArticleSavingRequestResult! createGroup(input: CreateGroupInput!): CreateGroupResult! createHighlight(input: CreateHighlightInput!): CreateHighlightResult! - createHighlightReply(input: CreateHighlightReplyInput!): CreateHighlightReplyResult! + createHighlightReply( + input: CreateHighlightReplyInput! + ): CreateHighlightReplyResult! createLabel(input: CreateLabelInput!): CreateLabelResult! createNewsletterEmail: CreateNewsletterEmailResult! createReaction(input: CreateReactionInput!): CreateReactionResult! @@ -1124,10 +1151,14 @@ type Mutation { moveLabel(input: MoveLabelInput!): MoveLabelResult! optInFeature(input: OptInFeatureInput!): OptInFeatureResult! recommend(input: RecommendInput!): RecommendResult! - recommendHighlights(input: RecommendHighlightsInput!): RecommendHighlightsResult! + recommendHighlights( + input: RecommendHighlightsInput! + ): RecommendHighlightsResult! reportItem(input: ReportItemInput!): ReportItemResult! revokeApiKey(id: ID!): RevokeApiKeyResult! - saveArticleReadingProgress(input: SaveArticleReadingProgressInput!): SaveArticleReadingProgressResult! + saveArticleReadingProgress( + input: SaveArticleReadingProgressInput! + ): SaveArticleReadingProgressResult! saveFile(input: SaveFileInput!): SaveResult! saveFilter(input: SaveFilterInput!): SaveFilterResult! savePage(input: SavePageInput!): SaveResult! @@ -1142,21 +1173,32 @@ type Mutation { setRule(input: SetRuleInput!): SetRuleResult! setShareArticle(input: SetShareArticleInput!): SetShareArticleResult! setShareHighlight(input: SetShareHighlightInput!): SetShareHighlightResult! - setUserPersonalization(input: SetUserPersonalizationInput!): SetUserPersonalizationResult! + setUserPersonalization( + input: SetUserPersonalizationInput! + ): SetUserPersonalizationResult! setWebhook(input: SetWebhookInput!): SetWebhookResult! subscribe(name: String!): SubscribeResult! unsubscribe(name: String!): UnsubscribeResult! updateHighlight(input: UpdateHighlightInput!): UpdateHighlightResult! - updateHighlightReply(input: UpdateHighlightReplyInput!): UpdateHighlightReplyResult! + updateHighlightReply( + input: UpdateHighlightReplyInput! + ): UpdateHighlightReplyResult! updateLabel(input: UpdateLabelInput!): UpdateLabelResult! - updateLinkShareInfo(input: UpdateLinkShareInfoInput!): UpdateLinkShareInfoResult! + updateLinkShareInfo( + input: UpdateLinkShareInfoInput! + ): UpdateLinkShareInfoResult! updatePage(input: UpdatePageInput!): UpdatePageResult! updateReminder(input: UpdateReminderInput!): UpdateReminderResult! - updateSharedComment(input: UpdateSharedCommentInput!): UpdateSharedCommentResult! + updateSharedComment( + input: UpdateSharedCommentInput! + ): UpdateSharedCommentResult! updateUser(input: UpdateUserInput!): UpdateUserResult! updateUserProfile(input: UpdateUserProfileInput!): UpdateUserProfileResult! uploadFileRequest(input: UploadFileRequestInput!): UploadFileRequestResult! - uploadImportFile(contentType: String!, type: UploadImportFileType!): UploadImportFileResult! + uploadImportFile( + contentType: String! + type: UploadImportFileType! + ): UploadImportFileResult! } type NewsletterEmail { @@ -1281,9 +1323,21 @@ type Query { apiKeys: ApiKeysResult! 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! + 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! + feedArticles( + after: String + first: Int + sharedByUser: ID + sort: SortParams + ): FeedArticlesResult! filters: FiltersResult! getFollowers(userId: ID): GetFollowersResult! getFollowing(userId: ID): GetFollowingResult! @@ -1298,12 +1352,27 @@ type Query { recentSearches: RecentSearchesResult! reminder(linkId: ID!): ReminderResult! rules(enabled: Boolean): RulesResult! - search(after: String, first: Int, format: String, includeContent: Boolean, query: String): SearchResult! + search( + after: String + first: Int + format: String + includeContent: Boolean + query: String + ): SearchResult! sendInstallInstructions: SendInstallInstructionsResult! - sharedArticle(selectedHighlightId: String, slug: String!, username: String!): SharedArticleResult! + 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!, sort: SortParams): UpdatesSinceResult! + updatesSince( + after: String + first: Int + since: Date! + sort: SortParams + ): UpdatesSinceResult! user(userId: ID, username: String): UserResult! users: UsersResult! validateUsername(username: String!): Boolean! @@ -1409,7 +1478,9 @@ input RecommendHighlightsInput { pageId: ID! } -union RecommendHighlightsResult = RecommendHighlightsError | RecommendHighlightsSuccess +union RecommendHighlightsResult = + RecommendHighlightsError + | RecommendHighlightsSuccess type RecommendHighlightsSuccess { success: Boolean! @@ -1574,7 +1645,9 @@ input SaveArticleReadingProgressInput { readingProgressTopPercent: Float } -union SaveArticleReadingProgressResult = SaveArticleReadingProgressError | SaveArticleReadingProgressSuccess +union SaveArticleReadingProgressResult = + SaveArticleReadingProgressError + | SaveArticleReadingProgressSuccess type SaveArticleReadingProgressSuccess { updatedArticle: Article! @@ -1721,7 +1794,9 @@ enum SendInstallInstructionsErrorCode { UNAUTHORIZED } -union SendInstallInstructionsResult = SendInstallInstructionsError | SendInstallInstructionsSuccess +union SendInstallInstructionsResult = + SendInstallInstructionsError + | SendInstallInstructionsSuccess type SendInstallInstructionsSuccess { sent: Boolean! @@ -1741,7 +1816,9 @@ input SetBookmarkArticleInput { bookmark: Boolean! } -union SetBookmarkArticleResult = SetBookmarkArticleError | SetBookmarkArticleSuccess +union SetBookmarkArticleResult = + SetBookmarkArticleError + | SetBookmarkArticleSuccess type SetBookmarkArticleSuccess { bookmarkedArticle: Article! @@ -1904,7 +1981,9 @@ input SetShareHighlightInput { share: Boolean! } -union SetShareHighlightResult = SetShareHighlightError | SetShareHighlightSuccess +union SetShareHighlightResult = + SetShareHighlightError + | SetShareHighlightSuccess type SetShareHighlightSuccess { highlight: Highlight! @@ -1932,7 +2011,9 @@ input SetUserPersonalizationInput { theme: String } -union SetUserPersonalizationResult = SetUserPersonalizationError | SetUserPersonalizationSuccess +union SetUserPersonalizationResult = + SetUserPersonalizationError + | SetUserPersonalizationSuccess type SetUserPersonalizationSuccess { updatedUserPersonalization: UserPersonalization! @@ -2144,7 +2225,9 @@ input UpdateHighlightReplyInput { text: String! } -union UpdateHighlightReplyResult = UpdateHighlightReplyError | UpdateHighlightReplySuccess +union UpdateHighlightReplyResult = + UpdateHighlightReplyError + | UpdateHighlightReplySuccess type UpdateHighlightReplySuccess { highlightReply: HighlightReply! @@ -2195,7 +2278,9 @@ input UpdateLinkShareInfoInput { title: String! } -union UpdateLinkShareInfoResult = UpdateLinkShareInfoError | UpdateLinkShareInfoSuccess +union UpdateLinkShareInfoResult = + UpdateLinkShareInfoError + | UpdateLinkShareInfoSuccess type UpdateLinkShareInfoSuccess { message: String! @@ -2271,7 +2356,9 @@ input UpdateSharedCommentInput { sharedComment: String! } -union UpdateSharedCommentResult = UpdateSharedCommentError | UpdateSharedCommentSuccess +union UpdateSharedCommentResult = + UpdateSharedCommentError + | UpdateSharedCommentSuccess type UpdateSharedCommentSuccess { articleID: ID! @@ -2313,7 +2400,9 @@ input UpdateUserProfileInput { username: String } -union UpdateUserProfileResult = UpdateUserProfileError | UpdateUserProfileSuccess +union UpdateUserProfileResult = + UpdateUserProfileError + | UpdateUserProfileSuccess type UpdateUserProfileSuccess { user: User! @@ -2357,7 +2446,9 @@ input UploadFileRequestInput { url: String! } -union UploadFileRequestResult = UploadFileRequestError | UploadFileRequestSuccess +union UploadFileRequestResult = + UploadFileRequestError + | UploadFileRequestSuccess type UploadFileRequestSuccess { createdPageId: String @@ -2397,7 +2488,8 @@ type User { followersCount: Int friendsCount: Int id: ID! - isFriend: Boolean @deprecated(reason: "isFriend has been replaced with viewerIsFollowing") + isFriend: Boolean + @deprecated(reason: "isFriend has been replaced with viewerIsFollowing") isFullUser: Boolean name: String! picture: 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 97d545506..3d5fd733c 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 @@ -1,12 +1,16 @@ package app.omnivore.omnivore.dataService +import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput +import app.omnivore.omnivore.graphql.generated.type.HighlightType import app.omnivore.omnivore.models.ServerSyncStatus import app.omnivore.omnivore.networking.* import app.omnivore.omnivore.persistence.entities.Highlight import app.omnivore.omnivore.persistence.entities.SavedItemAndHighlightCrossRef +import com.apollographql.apollo3.api.Optional import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.util.* suspend fun DataService.createWebHighlight(jsonString: String) { val createHighlightInput = Gson().fromJson(jsonString, CreateHighlightParams::class.java).asCreateHighlightInput() @@ -44,6 +48,59 @@ suspend fun DataService.createWebHighlight(jsonString: String) { } } +suspend fun DataService.createNoteHighlight(savedItemId: String, note: String) { + val shortId = UUID.randomUUID().toString() + val createHighlightId = UUID.randomUUID().toString() + val createHighlightParams = CreateHighlightParams( + type = HighlightType.NOTE,, + shortId = shortId, + id = createHighlightId, + quote = null, + patch = null, + articleId = null, + annotation = note, + ) + + withContext(Dispatchers.IO) { + val highlight = Highlight( + type = "HIGHLIGHT", + highlightId = createHighlightInput.id, + shortId = createHighlightInput.shortId, + quote = createHighlightInput.quote.getOrNull(), + prefix = null, + suffix = null, + patch = createHighlightInput.patch.getOrNull(), + annotation = createHighlightInput.annotation.getOrNull(), + createdAt = null, + updatedAt = null, + createdByMe = false + ) + + highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue + + val crossRef = SavedItemAndHighlightCrossRef( + highlightId = createHighlightId, + savedItemId = savedItemId + ) + + db.highlightDao().insertAll(listOf(highlight)) + db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef)) + + val newHighlight = networker.createHighlight(input = CreateHighlightParams( + shortId = shortId, + id = createHighlightId, + quote = null, + patch = null, + articleId = null, + annotation = note, + ).asCreateHighlightInput()) + + newHighlight?.let { + db.highlightDao().update(it) + } + } +} + suspend fun DataService.mergeWebHighlights(jsonString: String) { val mergeHighlightInput = Gson().fromJson(jsonString, MergeHighlightsParams::class.java).asMergeHighlightInput() 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 2d272f732..3d184d90b 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 @@ -6,6 +6,7 @@ import app.omnivore.omnivore.graphql.generated.DeleteHighlightMutation import app.omnivore.omnivore.graphql.generated.MergeHighlightMutation import app.omnivore.omnivore.graphql.generated.UpdateHighlightMutation 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.persistence.entities.Highlight @@ -13,6 +14,7 @@ import com.apollographql.apollo3.api.Optional import com.google.gson.Gson data class CreateHighlightParams( + val type: HighlightType, val shortId: String?, val id: String?, val quote: String?, @@ -21,6 +23,7 @@ data class CreateHighlightParams( val `annotation`: String? ) { fun asCreateHighlightInput() = CreateHighlightInput( + type = type, annotation = Optional.presentIfNotNull(`annotation`), articleId = articleId ?: "", id = id ?: "", 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 0071697c6..fc6baa8a2 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 @@ -1,5 +1,6 @@ package app.omnivore.omnivore.persistence.entities +import androidx.lifecycle.LiveData import androidx.room.* import app.omnivore.omnivore.models.ServerSyncStatus import com.google.gson.annotations.SerializedName 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 7ffd74805..6c18a967a 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 @@ -161,6 +161,22 @@ interface SavedItemDao { ) fun getLibraryItemById(savedItemId: String): LiveData + @Transaction + @Query( + "SELECT ${SavedItemQueryConstants.libraryColumns} " + + "FROM SavedItem " + + "LEFT OUTER JOIN SavedItemAndSavedItemLabelCrossRef on SavedItem.savedItemId = SavedItemAndSavedItemLabelCrossRef.savedItemId " + + "LEFT OUTER JOIN SavedItemAndHighlightCrossRef on SavedItem.savedItemId = SavedItemAndHighlightCrossRef.savedItemId " + + + "LEFT OUTER JOIN SavedItemLabel on SavedItemLabel.savedItemLabelId = SavedItemAndSavedItemLabelCrossRef.savedItemLabelId " + + "LEFT OUTER JOIN Highlight on highlight.highlightId = SavedItemAndHighlightCrossRef.highlightId " + + + "WHERE SavedItem.savedItemId = :savedItemId " + + + "GROUP BY SavedItem.savedItemId " + ) + suspend fun getById(savedItemId: String): SavedItemWithLabelsAndHighlights? + @Transaction @Query( "SELECT ${SavedItemQueryConstants.libraryColumns} " + diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt index 4bce9ac33..960137080 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt @@ -3,6 +3,8 @@ package app.omnivore.omnivore.ui.notebook import android.content.ClipData import android.content.ClipboardManager import android.content.Context +import androidx.activity.OnBackPressedCallback +import androidx.activity.compose.BackHandler import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -25,9 +27,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.colorResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp @@ -68,15 +73,12 @@ fun notebookMD(notes: List, highlights: List): String { @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable -fun NotebookView(savedItemId: String, viewModel: NotebookViewModel) { +fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditArticleNotes: () -> Unit) { var isMenuOpen by remember { mutableStateOf(false) } val savedItem = viewModel.getLibraryItemById(savedItemId).observeAsState() val scrollState = rememberScrollState() - val modalBottomSheetState = rememberModalBottomSheetState( - ModalBottomSheetValue.Hidden, - ) val coroutineScope = rememberCoroutineScope() val snackBarHostState = remember { SnackbarHostState() } val clipboard: ClipboardManager? = @@ -85,69 +87,59 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel) { val notes = savedItem.value?.highlights?.filter { it.type == "NOTE" } ?: listOf() val highlights = savedItem.value?.highlights?.filter { it.type == "HIGHLIGHT" } ?: listOf() - ModalBottomSheetLayout( - sheetBackgroundColor = Color.Transparent, - sheetState = modalBottomSheetState, - sheetContent = { - // EditNoteModal() - Spacer(modifier = Modifier.weight(1.0F)) - } - ) { - Scaffold( - topBar = { - TopAppBar( - title = { Text("Notebook") }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.background - ), - actions = { - Box { - IconButton(onClick = { - isMenuOpen = true - }) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = null - ) - } - if (isMenuOpen) { - DropdownMenu( - expanded = isMenuOpen, - onDismissRequest = { isMenuOpen = false } - ) { - DropdownMenuItem( - text = { Text("Copy") }, - onClick = { - val clip = ClipData.newPlainText("notebook", notebookMD(notes, highlights)) - clipboard?.let { - clipboard?.setPrimaryClip(clip) - } ?: run { - coroutineScope.launch { - snackBarHostState - .showSnackbar("Notebook copied") - } + Scaffold( + topBar = { + TopAppBar( + title = { Text("Notebook") }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background + ), + actions = { + Box { + IconButton(onClick = { + isMenuOpen = true + }) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = null + ) + } + if (isMenuOpen) { + DropdownMenu( + expanded = isMenuOpen, + onDismissRequest = { isMenuOpen = false } + ) { + DropdownMenuItem( + text = { Text("Copy") }, + onClick = { + val clip = ClipData.newPlainText("notebook", notebookMD(notes, highlights)) + clipboard?.let { + clipboard?.setPrimaryClip(clip) + } ?: run { + coroutineScope.launch { + snackBarHostState + .showSnackbar("Notebook copied") } - isMenuOpen = false } - ) - } + isMenuOpen = false + } + ) } } } - ) - } - ) { paddingValues -> - Column( - modifier = Modifier - .verticalScroll(scrollState) - .fillMaxSize() - ) { - savedItem.value?.let { - if (notes.isNotEmpty()) { - ArticleNotes(it) - } - HighlightsList(it) } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .verticalScroll(scrollState) + .fillMaxSize() + .padding(top = paddingValues.calculateTopPadding()) + ) { + savedItem.value?.let { + ArticleNotes(viewModel, it, onEditArticleNotes) + HighlightsList(it) } } } @@ -155,22 +147,22 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel) { @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable -fun EditNoteModal() { - val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher +fun EditNoteModal(onDismiss: (text: String) -> Unit) { + val focusRequester = remember { FocusRequester() } val annotation = remember { mutableStateOf("") } BottomSheetUI() { Scaffold( topBar = { TopAppBar( - title = { Text("Note") }, + title = { Text("Edit Note") }, modifier = Modifier.statusBarsPadding(), colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.background ), navigationIcon = { IconButton(onClick = { - onBackPressedDispatcher?.onBackPressed() + onDismiss(annotation.value) }) { Icon( imageVector = androidx.compose.material.icons.Icons.Filled.ArrowBack, @@ -185,26 +177,31 @@ fun EditNoteModal() { TextField( modifier = Modifier .padding(paddingValues) + .focusRequester(focusRequester) .fillMaxSize(), value = annotation.value, onValueChange = { annotation.value = it } ) } } + + BackHandler(enabled = true) { + onDismiss(annotation.value) + } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } } @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable -fun ArticleNotes(item: SavedItemWithLabelsAndHighlights) { +fun ArticleNotes(viewModel: NotebookViewModel, item: SavedItemWithLabelsAndHighlights, onEditArticleNotes: () -> Unit) { val notes = item.highlights?.filter { it.type == "NOTE" } ?: listOf() - val showDialog = remember { mutableStateOf(false) } - val modalBottomSheetState = rememberModalBottomSheetState( - ModalBottomSheetValue.Expanded, - ) - val annotation = remember { mutableStateOf("") } Column(modifier = Modifier .fillMaxWidth() .padding(start = 15.dp) + .padding(top = 20.dp, bottom = 50.dp) ) { Text("Article Notes") Divider(modifier = Modifier.padding(bottom= 15.dp)) @@ -219,9 +216,7 @@ fun ArticleNotes(item: SavedItemWithLabelsAndHighlights) { if (notes.isEmpty()) { Button( onClick = { -// viewModelScope.launch { -// datastoreRepo.clearValue(DatastoreKeys.omnivorePendingUserToken) -// } + onEditArticleNotes() }, modifier = Modifier .padding(0.dp, end = 15.dp) @@ -232,12 +227,12 @@ fun ArticleNotes(item: SavedItemWithLabelsAndHighlights) { containerColor = MaterialTheme.colorScheme.surfaceVariant ) ) { -// Text( -// text = "Add Notes...", -// style = androidx.compose.material.MaterialTheme.typography.subtitle2, -// modifier = Modifier -// .padding(vertical = 2.dp, horizontal = 0.dp), -// ) + Text( + text = "Add Notes...", + style = androidx.compose.material.MaterialTheme.typography.subtitle2, + modifier = Modifier + .padding(vertical = 2.dp, horizontal = 0.dp), + ) Spacer(Modifier.weight(1f)) } } @@ -366,8 +361,9 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights) { } } +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable -private fun BottomSheetUI(content: @Composable () -> Unit) { +fun BottomSheetUI(content: @Composable () -> Unit) { Box( modifier = Modifier .wrapContentHeight() @@ -375,9 +371,13 @@ private fun BottomSheetUI(content: @Composable () -> Unit) { .clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp)) .background(Color.White) .statusBarsPadding() - .padding(top = 20.dp) ) { - content() + Scaffold( + ) { paddingValues -> + Box(modifier = Modifier.fillMaxSize()) { + content() + } + } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt index e1095d1ab..22a795d2a 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt @@ -2,6 +2,8 @@ package app.omnivore.omnivore.ui.notebook import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.liveData +import androidx.lifecycle.map import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.dataService.DataService import app.omnivore.omnivore.networking.Networker @@ -16,8 +18,22 @@ class NotebookViewModel @Inject constructor( private val dataService: DataService, private val datastoreRepo: DatastoreRepository ): ViewModel() { - fun getLibraryItemById(savedItemId: String): LiveData { return dataService.db.savedItemDao().getLibraryItemById(savedItemId) } + + suspend fun addArticleNote(savedItemId: String, note: String): Boolean { + val item = dataService.db.savedItemDao().getById(savedItemId) + println("this is an item: $item") + return item?.let { item -> + println("this is an item: $item") + val noteHighlight = item.highlights.first { it.type == "NOTE" } + noteHighlight?.let { + + } ?: run { + + } + return true + } ?: false + } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 89d92554c..01bb77603 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -48,6 +48,7 @@ import kotlinx.coroutines.launch import kotlin.math.roundToInt import androidx.compose.material3.Button import androidx.compose.ui.platform.LocalContext +import app.omnivore.omnivore.ui.notebook.EditNoteModal @AndroidEntryPoint @@ -115,6 +116,7 @@ enum class BottomSheetState( NONE(), PREFERENCES(), NOTEBOOK(), + ADDNOTE(), HIGHLIGHTNOTE(), LABELS(), LINK() @@ -133,7 +135,6 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(null) val shouldPopView: Boolean by webReaderViewModel.shouldPopViewLiveData.observeAsState(false) - val labels: List by webReaderViewModel.savedItemLabelsLiveData.observeAsState(listOf()) val maxToolbarHeight = 48.dp @@ -151,9 +152,11 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, webReaderContent.styledContent() } ?: null + val modalBottomSheetState = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Hidden, - confirmStateChange = { + skipHalfExpanded = bottomSheetState == BottomSheetState.ADDNOTE, + confirmValueChange = { if (it == ModalBottomSheetValue.Hidden) { webReaderViewModel.resetBottomSheet() } @@ -175,6 +178,11 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, modalBottomSheetState.show() } } + BottomSheetState.ADDNOTE -> { + coroutineScope.launch { + modalBottomSheetState.show() + } + } BottomSheetState.HIGHLIGHTNOTE -> { coroutineScope.launch { modalBottomSheetState.show() @@ -217,13 +225,25 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, BottomSheetState.NOTEBOOK -> { webReaderParams?.let { params -> BottomSheetUI(title = "Notebook") { - NotebookView(savedItemId = params.item.savedItemId, viewModel = notebookViewModel) + NotebookView(savedItemId = params.item.savedItemId, viewModel = notebookViewModel, onEditArticleNotes = { + webReaderViewModel.setBottomSheet(BottomSheetState.ADDNOTE) + }) } } } + BottomSheetState.ADDNOTE -> { + webReaderParams?.let { params -> + EditNoteModal(onDismiss = { + coroutineScope.launch { + notebookViewModel.addArticleNote(savedItemId = params.item.savedItemId, note = it) + webReaderViewModel.setBottomSheet(BottomSheetState.NOTEBOOK) + } + }) + } + } BottomSheetState.HIGHLIGHTNOTE -> { webReaderViewModel.annotation?.let { annotation -> - BottomSheetUI(title = "Note") { + BottomSheetUI(title = "Edit Note") { AnnotationEditView( initialAnnotation = annotation, onSave = { From 048facb7cafbce8fcf9cc5b7e82c2e1600145899 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 29 May 2023 10:30:23 +0800 Subject: [PATCH 02/11] Fix delete propogation --- .../dataService/HighlightActionHandlers.kt | 31 ++++++++++--------- .../omnivore/dataService/LibrarySync.kt | 4 +++ .../omnivore/networking/HighlightMutations.kt | 4 +-- .../persistence/entities/SavedItem.kt | 3 ++ 4 files changed, 25 insertions(+), 17 deletions(-) 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 3d5fd733c..1362ca9cd 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 @@ -51,29 +51,29 @@ suspend fun DataService.createWebHighlight(jsonString: String) { suspend fun DataService.createNoteHighlight(savedItemId: String, note: String) { val shortId = UUID.randomUUID().toString() val createHighlightId = UUID.randomUUID().toString() - val createHighlightParams = CreateHighlightParams( - type = HighlightType.NOTE,, - shortId = shortId, - id = createHighlightId, - quote = null, - patch = null, - articleId = null, - annotation = note, - ) +// val createHighlightParams = CreateHighlightParams( +// type = HighlightType.NOTE,, +// shortId = shortId, +// id = createHighlightId, +// quote = null, +// patch = null, +// articleId = null, +// annotation = note, +// ) withContext(Dispatchers.IO) { val highlight = Highlight( type = "HIGHLIGHT", - highlightId = createHighlightInput.id, - shortId = createHighlightInput.shortId, - quote = createHighlightInput.quote.getOrNull(), + highlightId = createHighlightId, + shortId = shortId, + quote = null, prefix = null, suffix = null, - patch = createHighlightInput.patch.getOrNull(), - annotation = createHighlightInput.annotation.getOrNull(), + patch =null, + annotation = note, createdAt = null, updatedAt = null, - createdByMe = false + createdByMe = true ) highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue @@ -87,6 +87,7 @@ suspend fun DataService.createNoteHighlight(savedItemId: String, note: String) { db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef)) val newHighlight = networker.createHighlight(input = CreateHighlightParams( + type = HighlightType.HIGHLIGHT, shortId = shortId, id = createHighlightId, quote = 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 4181471e3..a28a71a9b 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 @@ -34,6 +34,10 @@ suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 20): S val syncResult = networker.savedItemUpdates(cursor = cursor, limit = limit, since = since) ?: return SavedItemSyncResult.errorResult + if (syncResult.deletedItemIDs.isNotEmpty()) { + db.savedItemDao().deleteByIds(syncResult.deletedItemIDs) + } + val savedItems = syncResult.items.map { val savedItem = SavedItem( savedItemId = it.id, 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 3d184d90b..85930d502 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 @@ -23,8 +23,8 @@ data class CreateHighlightParams( val `annotation`: String? ) { fun asCreateHighlightInput() = CreateHighlightInput( - type = type, - annotation = Optional.presentIfNotNull(`annotation`), + type = Optional.presentIfNotNull(type), + annotation = Optional.presentIfNotNull(annotation), articleId = articleId ?: "", id = id ?: "", patch = Optional.presentIfNotNull(patch), 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 6c18a967a..863ebe7ae 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 @@ -142,6 +142,9 @@ interface SavedItemDao { @Query("DELETE FROM savedItem WHERE savedItemId = :itemID") fun deleteById(itemID: String) + @Query("DELETE FROM savedItem WHERE savedItemId in (:itemIDs)") + fun deleteByIds(itemIDs: List) + @Update fun update(savedItem: SavedItem) From 73b71f002c251459cd495dc620cc596ba7fec6fd Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 29 May 2023 11:11:44 +0800 Subject: [PATCH 03/11] Themeing, text for notebooks --- .../omnivore/ui/notebook/NotebookView.kt | 36 +++++++++++-------- .../omnivore/ui/reader/AnnotationEditView.kt | 19 ++++++---- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt index 960137080..626975ae3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* +import androidx.compose.material.TextFieldColors import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material3.* @@ -21,6 +22,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment @@ -43,7 +45,7 @@ import app.omnivore.omnivore.ui.library.* import dev.jeziellago.compose.markdowntext.MarkdownText import kotlinx.coroutines.launch import app.omnivore.omnivore.persistence.entities.Highlight - +import app.omnivore.omnivore.ui.theme.OmnivoreTheme fun notebookMD(notes: List, highlights: List): String { @@ -179,7 +181,11 @@ fun EditNoteModal(onDismiss: (text: String) -> Unit) { .padding(paddingValues) .focusRequester(focusRequester) .fillMaxSize(), - value = annotation.value, onValueChange = { annotation.value = it } + value = annotation.value, onValueChange = { annotation.value = it }, + colors = TextFieldDefaults.textFieldColors( + textColor = Color.White + ) + ) } } @@ -364,18 +370,20 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights) { @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable fun BottomSheetUI(content: @Composable () -> Unit) { - Box( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp)) - .background(Color.White) - .statusBarsPadding() - ) { - Scaffold( - ) { paddingValues -> - Box(modifier = Modifier.fillMaxSize()) { - content() + OmnivoreTheme() { + Box( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp)) + .background(Color.White) + .statusBarsPadding() + ) { + Scaffold( + ) { paddingValues -> + Box(modifier = Modifier.fillMaxSize()) { + content() + } } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt index f7c1605cb..5c9f5c67d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.unit.dp @@ -121,13 +122,17 @@ fun AnnotationEditView( .padding(paddingValues) .fillMaxSize() ) { - TextField( - value = annotation.value, - onValueChange = { annotation.value = it }, - modifier = Modifier - .focusRequester(focusRequester) - .fillMaxSize() - ) + TextField( + value = annotation.value, + onValueChange = { annotation.value = it }, + colors = TextFieldDefaults.textFieldColors( + textColor = Color.White, + containerColor = Color.Green, + ), + modifier = Modifier + .focusRequester(focusRequester) + .fillMaxSize() + ) } } } From ffd7bcf568179c8d4dae46e7b92b02ab181f2b22 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 29 May 2023 14:13:44 +0800 Subject: [PATCH 04/11] Allow creating article notes from the notebook --- .../dataService/HighlightActionHandlers.kt | 21 +++++-------- .../persistence/entities/Highlight.kt | 4 +++ .../omnivore/ui/notebook/NotebookView.kt | 31 ++++++++++--------- .../omnivore/ui/notebook/NotebookViewModel.kt | 7 +++-- .../ui/reader/WebReaderLoadingContainer.kt | 15 ++++++--- 5 files changed, 42 insertions(+), 36 deletions(-) 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 1362ca9cd..285e0fe80 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 @@ -48,22 +48,13 @@ suspend fun DataService.createWebHighlight(jsonString: String) { } } -suspend fun DataService.createNoteHighlight(savedItemId: String, note: String) { +suspend fun DataService.createNoteHighlight(savedItemId: String, note: String): String { val shortId = UUID.randomUUID().toString() val createHighlightId = UUID.randomUUID().toString() -// val createHighlightParams = CreateHighlightParams( -// type = HighlightType.NOTE,, -// shortId = shortId, -// id = createHighlightId, -// quote = null, -// patch = null, -// articleId = null, -// annotation = note, -// ) withContext(Dispatchers.IO) { val highlight = Highlight( - type = "HIGHLIGHT", + type = "NOTE", highlightId = createHighlightId, shortId = shortId, quote = null, @@ -87,12 +78,12 @@ suspend fun DataService.createNoteHighlight(savedItemId: String, note: String) { db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef)) val newHighlight = networker.createHighlight(input = CreateHighlightParams( - type = HighlightType.HIGHLIGHT, - shortId = shortId, + type = HighlightType.NOTE, + articleId = savedItemId, id = createHighlightId, + shortId = shortId, quote = null, patch = null, - articleId = null, annotation = note, ).asCreateHighlightInput()) @@ -100,6 +91,8 @@ suspend fun DataService.createNoteHighlight(savedItemId: String, note: String) { db.highlightDao().update(it) } } + + return createHighlightId } suspend fun DataService.mergeWebHighlights(jsonString: String) { 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 fc6baa8a2..691b15ecd 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 @@ -91,6 +91,10 @@ interface HighlightDao { @Query("SELECT * FROM highlight WHERE highlightId = :highlightId") fun findById(highlightId: String): Highlight? + // Server sync status is passed in here to work around Room compile-time query rules, but should always be NEEDS_UPDATE + @Query("UPDATE highlight SET annotation = :note, serverSyncStatus = :serverSyncStatus WHERE highlightId = :highlightId") + fun updateNote(highlightId: String, note: String, serverSyncStatus: Int = ServerSyncStatus.NEEDS_UPDATE.rawValue) + @Update fun update(highlight: Highlight) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt index 626975ae3..2b3bcda91 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt @@ -23,6 +23,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.TextButton import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment @@ -36,6 +37,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -149,28 +151,31 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditArticl @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable -fun EditNoteModal(onDismiss: (text: String) -> Unit) { +fun EditNoteModal(initialValue: String?, onDismiss: (save: Boolean, text: String?) -> Unit) { val focusRequester = remember { FocusRequester() } - val annotation = remember { mutableStateOf("") } + val annotation = remember { mutableStateOf(initialValue ?: "") } BottomSheetUI() { Scaffold( topBar = { - TopAppBar( - title = { Text("Edit Note") }, + CenterAlignedTopAppBar( + title = { Text("Note") }, modifier = Modifier.statusBarsPadding(), colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.background ), navigationIcon = { - IconButton(onClick = { - onDismiss(annotation.value) + TextButton(onClick = { + onDismiss(false, initialValue) }) { - Icon( - imageVector = androidx.compose.material.icons.Icons.Filled.ArrowBack, - modifier = Modifier, - contentDescription = "Back" - ) + Text(text = "Cancel") + } + }, + actions = { + TextButton(onClick = { + onDismiss(true, annotation.value) + }) { + Text(text = "Save") } } ) @@ -190,10 +195,6 @@ fun EditNoteModal(onDismiss: (text: String) -> Unit) { } } - BackHandler(enabled = true) { - onDismiss(annotation.value) - } - LaunchedEffect(Unit) { focusRequester.requestFocus() } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt index 22a795d2a..ce1dc43fc 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.liveData import androidx.lifecycle.map import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.dataService.DataService +import app.omnivore.omnivore.dataService.createNoteHighlight import app.omnivore.omnivore.networking.Networker import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights import app.omnivore.omnivore.ui.library.SavedItemViewModel @@ -27,11 +28,11 @@ class NotebookViewModel @Inject constructor( println("this is an item: $item") return item?.let { item -> println("this is an item: $item") - val noteHighlight = item.highlights.first { it.type == "NOTE" } + val noteHighlight = item.highlights.firstOrNull { it.type == "NOTE" } noteHighlight?.let { - + dataService.db.highlightDao().updateNote(highlightId = noteHighlight.highlightId, note = note) } ?: run { - + dataService.createNoteHighlight(savedItemId, note) } return true } ?: false diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 01bb77603..7e7e99c1f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -233,11 +233,18 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } BottomSheetState.ADDNOTE -> { webReaderParams?.let { params -> - EditNoteModal(onDismiss = { - coroutineScope.launch { - notebookViewModel.addArticleNote(savedItemId = params.item.savedItemId, note = it) + EditNoteModal( + initialValue = null, + onDismiss = { save, note -> + if (save && note != null) { + coroutineScope.launch { + notebookViewModel.addArticleNote( + savedItemId = params.item.savedItemId, + note = note + ) + } + } webReaderViewModel.setBottomSheet(BottomSheetState.NOTEBOOK) - } }) } } From a4e6b00d05bae2e6bdea95e6636e11e4ca190d70 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 30 May 2023 11:09:45 +0800 Subject: [PATCH 05/11] Proper escaping of annotations --- .../app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 8a8729006..752647bd4 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 @@ -319,7 +319,11 @@ class WebReaderViewModel @Inject constructor( } fun saveAnnotation(annotation: String) { - val script = "var event = new Event('saveAnnotation');event.annotation = '$annotation';document.dispatchEvent(event);" + val jsonAnnotation = Gson().toJson(annotation) + val script = "var event = new Event('saveAnnotation');event.annotation = $jsonAnnotation;document.dispatchEvent(event);" + + Log.d("loggo", script) + enqueueScript(script) cancelAnnotationEdit() } From 49903bf9382eab31f6ef0a18ba4408b9ea5df10d Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 30 May 2023 12:08:55 +0800 Subject: [PATCH 06/11] Edit notes on highlights/articles --- .../src/main/graphql/ArticleContent.graphql | 1 + .../omnivore/networking/HighlightMutations.kt | 6 +- .../omnivore/networking/SavedItemQuery.kt | 4 +- .../omnivore/networking/SearchQuery.kt | 4 +- .../persistence/entities/Highlight.kt | 6 +- .../omnivore/ui/notebook/NotebookView.kt | 194 +++++++++--------- .../omnivore/ui/notebook/NotebookViewModel.kt | 35 ++-- .../omnivore/omnivore/ui/reader/WebReader.kt | 16 +- .../ui/reader/WebReaderLoadingContainer.kt | 61 ++---- 9 files changed, 154 insertions(+), 173 deletions(-) diff --git a/android/Omnivore/app/src/main/graphql/ArticleContent.graphql b/android/Omnivore/app/src/main/graphql/ArticleContent.graphql index 79dcf6456..ef6b812e7 100644 --- a/android/Omnivore/app/src/main/graphql/ArticleContent.graphql +++ b/android/Omnivore/app/src/main/graphql/ArticleContent.graphql @@ -53,6 +53,7 @@ fragment HighlightFields on Highlight { patch annotation createdByMe + createdAt updatedAt sharedAt } 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 85930d502..e7793f3f2 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 @@ -139,8 +139,6 @@ suspend fun Networker.createHighlight(input: CreateHighlightInput): Highlight? { val createdHighlight = result.data?.createHighlight?.onCreateHighlightSuccess?.highlight if (createdHighlight != null) { -// val updatedAtString = createdHighlight.highlightFields.updatedAt as? String - return Highlight( type = createdHighlight.highlightFields.type.toString(), highlightId = createdHighlight.highlightFields.id, @@ -150,8 +148,8 @@ suspend fun Networker.createHighlight(input: CreateHighlightInput): Highlight? { suffix = createdHighlight.highlightFields.suffix, patch = createdHighlight.highlightFields.patch, annotation = createdHighlight.highlightFields.annotation, - createdAt = null, // TODO: update gql query to get this - updatedAt = null, // TODO: fix updatedAtString?.let { LocalDate.parse(it) }, + createdAt = createdHighlight.highlightFields.createdAt.toString(), + updatedAt = createdHighlight.highlightFields.updatedAt.toString(), createdByMe = createdHighlight.highlightFields.createdByMe ) } else { 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 134f09d45..13fc3aed9 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 @@ -51,8 +51,8 @@ suspend fun Networker.savedItem(slug: String): SavedItemQueryResponse { suffix = it.highlightFields.suffix, patch = it.highlightFields.patch, annotation = it.highlightFields.annotation, - createdAt = null, // TODO: update gql query to get this - updatedAt = null, //updatedAtString?.let { str -> LocalDate.parse(str) }, TODO: fix date parsing + createdAt = it.highlightFields.createdAt as String?, + updatedAt = it.highlightFields.updatedAt as String?, createdByMe = it.highlightFields.createdByMe ) } 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 fcfab3114..19e23f877 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 @@ -64,7 +64,7 @@ suspend fun Networker.search( savedItemLabelId = label.labelFields.id, name = label.labelFields.name, color = label.labelFields.color, - createdAt = null, + createdAt = label.labelFields.createdAt as String?, labelDescription = null ) }, @@ -81,7 +81,7 @@ suspend fun Networker.search( shortId = highlight.highlightFields.shortId, suffix = highlight.highlightFields.suffix, updatedAt = highlight.highlightFields.updatedAt as String?, - createdAt = null, + createdAt = highlight.highlightFields.createdAt as String?, ) } ) 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 691b15ecd..f23cfb04f 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 @@ -13,7 +13,7 @@ data class Highlight( val type: String, var annotation: String?, val createdAt: String?, - val createdByMe: Boolean, + val createdByMe: Boolean = true, val markedForDeletion: Boolean = false, var patch: String?, var prefix: String?, @@ -22,10 +22,6 @@ data class Highlight( var shortId: String, val suffix: String?, val updatedAt: String? - - // has many SavedItemLabels (inverse: labels have many highlights) - // has one savedItem (inverse: savedItem has many highlights - // has a UserProfile (no inverse) ) @Entity( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt index 2b3bcda91..e2c0155a6 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt @@ -3,14 +3,10 @@ package app.omnivore.omnivore.ui.notebook import android.content.ClipData import android.content.ClipboardManager import android.content.Context -import androidx.activity.OnBackPressedCallback -import androidx.activity.compose.BackHandler -import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* -import androidx.compose.material.TextFieldColors import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material3.* @@ -35,9 +31,7 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -77,7 +71,7 @@ fun notebookMD(notes: List, highlights: List): String { @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable -fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditArticleNotes: () -> Unit) { +fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditNote: (note: Highlight?) -> Unit) { var isMenuOpen by remember { mutableStateOf(false) } @@ -142,8 +136,8 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditArticl .padding(top = paddingValues.calculateTopPadding()) ) { savedItem.value?.let { - ArticleNotes(viewModel, it, onEditArticleNotes) - HighlightsList(it) + ArticleNotes(viewModel, it, onEditNote) + HighlightsList(it, onEditNote) } } } @@ -202,7 +196,7 @@ fun EditNoteModal(initialValue: String?, onDismiss: (save: Boolean, text: String @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable -fun ArticleNotes(viewModel: NotebookViewModel, item: SavedItemWithLabelsAndHighlights, onEditArticleNotes: () -> Unit) { +fun ArticleNotes(viewModel: NotebookViewModel, item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Highlight?) -> Unit) { val notes = item.highlights?.filter { it.type == "NOTE" } ?: listOf() Column(modifier = Modifier @@ -218,12 +212,13 @@ fun ArticleNotes(viewModel: NotebookViewModel, item: SavedItemWithLabelsAndHighl fontSize = 14.sp, style = TextStyle(lineHeight = 18.sp), color = MaterialTheme.colorScheme.onPrimaryContainer, + onClick = { onEditNote(note) } ) } if (notes.isEmpty()) { Button( onClick = { - onEditArticleNotes() + onEditNote(null) }, modifier = Modifier .padding(0.dp, end = 15.dp) @@ -248,7 +243,7 @@ fun ArticleNotes(viewModel: NotebookViewModel, item: SavedItemWithLabelsAndHighl @OptIn(ExperimentalMaterial3Api::class) @Composable -fun HighlightsList(item: SavedItemWithLabelsAndHighlights) { +fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Highlight?) -> Unit) { val highlights = item.highlights?.filter { it.type == "HIGHLIGHT" } ?: listOf() val yellowColor = colorResource(R.color.cta_yellow) @@ -265,98 +260,107 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights) { Text("Highlights") Divider(modifier = Modifier.padding(bottom= 10.dp)) highlights.forEach { highlight -> - var isMenuOpen by remember { mutableStateOf(false) } + var isMenuOpen by remember { mutableStateOf(false) } - Row(modifier = Modifier - .fillMaxWidth() - .align(Alignment.End) - .padding(0.dp) - ) { - Spacer(Modifier.weight(1f)) - Box { - IconButton(onClick = { isMenuOpen = true }) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = null - ) - } - if (isMenuOpen) { - DropdownMenu( - expanded = isMenuOpen, - onDismissRequest = { isMenuOpen = false } - ) { - DropdownMenuItem( - text = { Text("Copy") }, - onClick = { - val clip = ClipData.newPlainText("highlight", highlight.quote) - clipboard?.let { - clipboard?.setPrimaryClip(clip) - } ?: run { - coroutineScope.launch { - snackBarHostState - .showSnackbar("Highlight copied") - } - } - isMenuOpen = false - } - ) - } - } - } - } - - highlight.quote?.let { - Row(modifier = Modifier - .padding(start = 2.dp, end = 15.dp) - .fillMaxWidth() - .drawWithCache { - onDrawWithContent { - // draw behind the content the vertical line on the left - drawLine( - color = yellowColor, - start = Offset.Zero, - end = Offset(0f, this.size.height), - strokeWidth = 10f - ) - - // draw the content - drawContent() - } - }) { - - MarkdownText( - modifier = Modifier - .padding(start = 15.dp, end = 15.dp), - markdown = it, - fontSize = 14.sp, - color = MaterialTheme.colorScheme.onPrimaryContainer, + Row(modifier = Modifier + .fillMaxWidth() + .align(Alignment.End) + .padding(0.dp) + ) { + Spacer(Modifier.weight(1f)) + Box { + IconButton(onClick = { isMenuOpen = true }) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = null ) } + if (isMenuOpen) { + DropdownMenu( + expanded = isMenuOpen, + onDismissRequest = { isMenuOpen = false } + ) { + DropdownMenuItem( + text = { Text("Copy") }, + onClick = { + val clip = ClipData.newPlainText("highlight", highlight.quote) + clipboard?.let { + clipboard?.setPrimaryClip(clip) + } ?: run { + coroutineScope.launch { + snackBarHostState + .showSnackbar("Highlight copied") + } + } + isMenuOpen = false + } + ) + } + } } - highlight.annotation?.let { + } + + highlight.quote?.let { + Row(modifier = Modifier + .padding(start = 2.dp, end = 15.dp) + .fillMaxWidth() + .drawWithCache { + onDrawWithContent { + // draw behind the content the vertical line on the left + drawLine( + color = yellowColor, + start = Offset.Zero, + end = Offset(0f, this.size.height), + strokeWidth = 10f + ) + + // draw the content + drawContent() + } + }) { + MarkdownText( - // modifier = Modifier.padding(paddingValues), + modifier = Modifier + .padding(start = 15.dp, end = 15.dp), markdown = it, fontSize = 14.sp, color = MaterialTheme.colorScheme.onPrimaryContainer, - ) - } ?: run { -// Surface( -// modifier = Modifier -// .padding(0.dp, end = 15.dp, top = 15.dp, bottom = 30.dp) -// .fillMaxWidth(), -// shape = androidx.compose.material.MaterialTheme.shapes.medium, -// color = MaterialTheme.colorScheme.surfaceVariant -// ) { -// Row { -// Text( -// text = "Add Notes...", -// style = androidx.compose.material.MaterialTheme.typography.subtitle2, -// modifier = Modifier.padding(vertical = 10.dp, horizontal = 10.dp) -// ) -// } -// } + ) } + } + highlight.annotation?.let { + MarkdownText( + // modifier = Modifier.padding(paddingValues), + markdown = it, + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onPrimaryContainer, + onClick = { onEditNote(highlight) }, + modifier = Modifier + .padding(top = 15.dp), + ) + } ?: run { + Button( + onClick = { + onEditNote(highlight) + }, + modifier = Modifier + .padding(0.dp, top = 15.dp, end = 15.dp) + .fillMaxWidth(), + shape = androidx.compose.material.MaterialTheme.shapes.medium, + colors = ButtonDefaults.buttonColors( + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + containerColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) { + Text( + text = "Add Note...", + style = androidx.compose.material.MaterialTheme.typography.subtitle2, + modifier = Modifier + .padding(vertical = 2.dp, horizontal = 0.dp), + ) + Spacer(Modifier.weight(1f)) + } + } } if (highlights.isEmpty()) { Text( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt index ce1dc43fc..83ea4f7e7 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt @@ -1,16 +1,17 @@ package app.omnivore.omnivore.ui.notebook -import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData -import androidx.lifecycle.map +import androidx.lifecycle.* import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.dataService.DataService import app.omnivore.omnivore.dataService.createNoteHighlight import app.omnivore.omnivore.networking.Networker +import app.omnivore.omnivore.persistence.entities.Highlight import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights import app.omnivore.omnivore.ui.library.SavedItemViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject @HiltViewModel @@ -19,22 +20,24 @@ class NotebookViewModel @Inject constructor( private val dataService: DataService, private val datastoreRepo: DatastoreRepository ): ViewModel() { + var highlightUnderEdit: Highlight? = null + fun getLibraryItemById(savedItemId: String): LiveData { return dataService.db.savedItemDao().getLibraryItemById(savedItemId) } - suspend fun addArticleNote(savedItemId: String, note: String): Boolean { - val item = dataService.db.savedItemDao().getById(savedItemId) - println("this is an item: $item") - return item?.let { item -> - println("this is an item: $item") - val noteHighlight = item.highlights.firstOrNull { it.type == "NOTE" } - noteHighlight?.let { - dataService.db.highlightDao().updateNote(highlightId = noteHighlight.highlightId, note = note) - } ?: run { - dataService.createNoteHighlight(savedItemId, note) + suspend fun addArticleNote(savedItemId: String, note: String) { + withContext(Dispatchers.IO) { + val item = dataService.db.savedItemDao().getById(savedItemId) + item?.let { item -> + val noteHighlight = item.highlights.firstOrNull { it.type == "NOTE" } + noteHighlight?.let { + dataService.db.highlightDao() + .updateNote(highlightId = noteHighlight.highlightId, note = note) + } ?: run { + dataService.createNoteHighlight(savedItemId, note) + } } - return true - } ?: false + } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt index 216942a75..682748878 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt @@ -32,8 +32,12 @@ import java.util.* @Composable fun WebReader( styledContent: String, - webReaderViewModel: WebReaderViewModel + webReaderViewModel: WebReaderViewModel, + currentTheme: Themes? ) { + val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState() + val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value } + val javascriptActionLoopUUID: UUID by webReaderViewModel .javascriptActionLoopUUIDLiveData .observeAsState(UUID.randomUUID()) @@ -55,15 +59,11 @@ fun WebReader( settings.allowFileAccess = true settings.domStorageEnabled = true - alpha = 0.0f + alpha = 1.0f + viewModel?.showNavBar() + setBackgroundColor(0xff0000); webViewClient = object : WebViewClient() { - override fun onPageFinished(view: WebView?, url: String?) { - super.onPageFinished(view, url) - viewModel?.showNavBar() - view?.animate()?.alpha(1.0f)?.duration = 200 - } - override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest? diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 7e7e99c1f..5b3fd827d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -3,7 +3,6 @@ package app.omnivore.omnivore.ui.reader import android.content.Intent import android.os.Bundle import android.view.View -import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.activity.compose.setContent @@ -15,11 +14,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.* -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text @@ -50,7 +46,6 @@ import androidx.compose.material3.Button import androidx.compose.ui.platform.LocalContext import app.omnivore.omnivore.ui.notebook.EditNoteModal - @AndroidEntryPoint class WebReaderLoadingContainerActivity: ComponentActivity() { val viewModel: WebReaderViewModel by viewModels() @@ -116,7 +111,7 @@ enum class BottomSheetState( NONE(), PREFERENCES(), NOTEBOOK(), - ADDNOTE(), + EDITNOTE(), HIGHLIGHTNOTE(), LABELS(), LINK() @@ -129,6 +124,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, onLibraryIconTap: (() -> Unit)? = null, webReaderViewModel: WebReaderViewModel, notebookViewModel: NotebookViewModel) { + val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState() + val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value } val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher val bottomSheetState: BottomSheetState? by webReaderViewModel.bottomSheetStateLiveData.observeAsState(BottomSheetState.NONE) @@ -155,7 +152,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, val modalBottomSheetState = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Hidden, - skipHalfExpanded = bottomSheetState == BottomSheetState.ADDNOTE, + skipHalfExpanded = bottomSheetState == BottomSheetState.EDITNOTE, confirmValueChange = { if (it == ModalBottomSheetValue.Hidden) { webReaderViewModel.resetBottomSheet() @@ -164,6 +161,11 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } ) + val showMenu = { + coroutineScope.launch { + modalBottomSheetState.show() + } + } when (bottomSheetState) { BottomSheetState.PREFERENCES -> { @@ -173,35 +175,10 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } } } - BottomSheetState.NOTEBOOK -> { - coroutineScope.launch { - modalBottomSheetState.show() - } - } - BottomSheetState.ADDNOTE -> { - coroutineScope.launch { - modalBottomSheetState.show() - } - } - BottomSheetState.HIGHLIGHTNOTE -> { - coroutineScope.launch { - modalBottomSheetState.show() - } - } - BottomSheetState.LABELS -> { - coroutineScope.launch { - modalBottomSheetState.show() - } - } - BottomSheetState.LINK -> { - coroutineScope.launch { - modalBottomSheetState.show() - } - } - BottomSheetState.NONE -> { - coroutineScope.launch { - modalBottomSheetState.hide() - } + BottomSheetState.NOTEBOOK, BottomSheetState.EDITNOTE, + BottomSheetState.HIGHLIGHTNOTE, BottomSheetState.LABELS, + BottomSheetState.LINK, -> { + showMenu() } else -> { coroutineScope.launch { @@ -225,16 +202,17 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, BottomSheetState.NOTEBOOK -> { webReaderParams?.let { params -> BottomSheetUI(title = "Notebook") { - NotebookView(savedItemId = params.item.savedItemId, viewModel = notebookViewModel, onEditArticleNotes = { - webReaderViewModel.setBottomSheet(BottomSheetState.ADDNOTE) + NotebookView(savedItemId = params.item.savedItemId, viewModel = notebookViewModel, onEditNote = { + notebookViewModel.highlightUnderEdit = it + webReaderViewModel.setBottomSheet(BottomSheetState.EDITNOTE) }) } } } - BottomSheetState.ADDNOTE -> { + BottomSheetState.EDITNOTE -> { webReaderParams?.let { params -> EditNoteModal( - initialValue = null, + initialValue = notebookViewModel.highlightUnderEdit?.annotation, onDismiss = { save, note -> if (save && note != null) { coroutineScope.launch { @@ -318,7 +296,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, if (styledContent != null) { WebReader( styledContent = styledContent, - webReaderViewModel = webReaderViewModel + webReaderViewModel = webReaderViewModel, + currentTheme = currentTheme, ) } From ddf7c5181630f756a519d2de4d9af9e583f2b874 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 30 May 2023 14:41:54 +0800 Subject: [PATCH 07/11] Update highlights on edit from the notebook view --- .../omnivore/ui/notebook/NotebookViewModel.kt | 21 +++++++++++++++++++ .../omnivore/omnivore/ui/reader/WebReader.kt | 12 ++++------- .../ui/reader/WebReaderLoadingContainer.kt | 19 +++++++++++------ 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt index 83ea4f7e7..7a58837f0 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt @@ -1,13 +1,19 @@ package app.omnivore.omnivore.ui.notebook import androidx.lifecycle.* +import androidx.room.Query import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.dataService.DataService import app.omnivore.omnivore.dataService.createNoteHighlight +import app.omnivore.omnivore.dataService.updateWebHighlight +import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput +import app.omnivore.omnivore.models.ServerSyncStatus import app.omnivore.omnivore.networking.Networker +import app.omnivore.omnivore.networking.updateHighlight import app.omnivore.omnivore.persistence.entities.Highlight import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights import app.omnivore.omnivore.ui.library.SavedItemViewModel +import com.apollographql.apollo3.api.Optional import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -34,10 +40,25 @@ class NotebookViewModel @Inject constructor( noteHighlight?.let { dataService.db.highlightDao() .updateNote(highlightId = noteHighlight.highlightId, note = note) + + networker.updateHighlight(input = UpdateHighlightInput( + highlightId = noteHighlight.highlightId, + annotation = Optional.presentIfNotNull(note), + )) } ?: run { dataService.createNoteHighlight(savedItemId, note) } } } } + + suspend fun updateHighlightNote(highlightId: String, note: String?) { + withContext(Dispatchers.IO) { + dataService.db.highlightDao().updateNote(highlightId, note ?: "") + networker.updateHighlight(input = UpdateHighlightInput( + highlightId = highlightId, + annotation = Optional.presentIfNotNull(note), + )) + } + } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt index 682748878..fd7ad70e1 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt @@ -4,22 +4,19 @@ import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Context -import android.graphics.Bitmap +import android.graphics.Color import android.graphics.Rect import android.util.Log import android.view.* import android.view.View.OnScrollChangeListener -import android.view.ViewTreeObserver.OnScrollChangedListener import android.webkit.JavascriptInterface import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.graphics.Color import androidx.compose.ui.viewinterop.AndroidView import app.omnivore.omnivore.R import com.google.gson.Gson @@ -35,9 +32,6 @@ fun WebReader( webReaderViewModel: WebReaderViewModel, currentTheme: Themes? ) { - val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState() - val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value } - val javascriptActionLoopUUID: UUID by webReaderViewModel .javascriptActionLoopUUIDLiveData .observeAsState(UUID.randomUUID()) @@ -61,7 +55,9 @@ fun WebReader( alpha = 1.0f viewModel?.showNavBar() - setBackgroundColor(0xff0000); + currentTheme?.let { theme -> + setBackgroundColor(theme.backgroundColor.toInt()); + } webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 5b3fd827d..c820b1447 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -214,13 +214,20 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, EditNoteModal( initialValue = notebookViewModel.highlightUnderEdit?.annotation, onDismiss = { save, note -> - if (save && note != null) { - coroutineScope.launch { - notebookViewModel.addArticleNote( - savedItemId = params.item.savedItemId, - note = note - ) + coroutineScope.launch { + if (save) { + notebookViewModel.highlightUnderEdit?.let { highlight -> + notebookViewModel.updateHighlightNote(highlight.highlightId, note) + } ?: run { + if (note != null) { + notebookViewModel.addArticleNote( + savedItemId = params.item.savedItemId, + note = note + ) + } + } } + notebookViewModel.highlightUnderEdit = null } webReaderViewModel.setBottomSheet(BottomSheetState.NOTEBOOK) }) From a6c5e450a26898f77ae68ac2a7dc01914fcc8354 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 30 May 2023 15:00:57 +0800 Subject: [PATCH 08/11] Implement saving highlight notes with the new edit note view --- .../ui/reader/WebReaderLoadingContainer.kt | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index c820b1447..b5f63643a 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -152,7 +152,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, val modalBottomSheetState = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Hidden, - skipHalfExpanded = bottomSheetState == BottomSheetState.EDITNOTE, + skipHalfExpanded = bottomSheetState == BottomSheetState.EDITNOTE || bottomSheetState == BottomSheetState.HIGHLIGHTNOTE, confirmValueChange = { if (it == ModalBottomSheetValue.Hidden) { webReaderViewModel.resetBottomSheet() @@ -234,25 +234,18 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } } BottomSheetState.HIGHLIGHTNOTE -> { - webReaderViewModel.annotation?.let { annotation -> - BottomSheetUI(title = "Edit Note") { - AnnotationEditView( - initialAnnotation = annotation, - onSave = { - webReaderViewModel.saveAnnotation(it) - coroutineScope.launch { - webReaderViewModel.resetBottomSheet() - } - }, - onCancel = { - webReaderViewModel.cancelAnnotationEdit() - coroutineScope.launch { - webReaderViewModel.resetBottomSheet() - } + EditNoteModal( + initialValue = webReaderViewModel.annotation, + onDismiss = { save, note -> + coroutineScope.launch { + if (save) { + webReaderViewModel.saveAnnotation(note ?: "") } - ) + webReaderViewModel.annotation = null + } + webReaderViewModel.resetBottomSheet() } - } + ) } BottomSheetState.LABELS -> { BottomSheetUI(title = "Notebook") { From 0c495c7264fa1a2702386ac162a6c348b106660e Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 30 May 2023 15:19:06 +0800 Subject: [PATCH 09/11] Implement clear on cancel of edit note --- .../omnivore/ui/reader/WebReaderLoadingContainer.kt | 2 ++ .../app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index b5f63643a..e0864f086 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -240,6 +240,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, coroutineScope.launch { if (save) { webReaderViewModel.saveAnnotation(note ?: "") + } else { + webReaderViewModel.cancelAnnotation() } webReaderViewModel.annotation = null } 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 752647bd4..0eb4747c8 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 @@ -328,6 +328,13 @@ class WebReaderViewModel @Inject constructor( cancelAnnotationEdit() } + fun cancelAnnotation() { + val script = "var event = new Event('dismissHighlight');document.dispatchEvent(event);" + + enqueueScript(script) + cancelAnnotationEdit() + } + fun cancelAnnotationEdit() { annotation = null resetBottomSheet() From 465e88ca888259eaadae4a58e3f53a577a0831b6 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 30 May 2023 16:09:53 +0800 Subject: [PATCH 10/11] Use a bottom sheet fragment for PDF editor --- .../java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt index 5c9f5c67d..7bcd2989b 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt @@ -32,9 +32,10 @@ import app.omnivore.omnivore.ui.notebook.ArticleNotes import app.omnivore.omnivore.ui.notebook.HighlightsList import app.omnivore.omnivore.ui.notebook.notebookMD import app.omnivore.omnivore.ui.theme.OmnivoreTheme +import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlinx.coroutines.launch -class AnnotationEditFragment : DialogFragment() { +class AnnotationEditFragment : BottomSheetDialogFragment() { private var onSave: (String) -> Unit = {} private var onCancel: () -> Unit = {} private var initialAnnotation: String = "" From 707245562e30ebef47270b23963ca0000b27d62d Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 30 May 2023 22:08:29 +0800 Subject: [PATCH 11/11] Work on PDF notes --- android/Omnivore/app/build.gradle | 4 +- .../omnivore/ui/notebook/NotebookView.kt | 3 +- .../omnivore/ui/reader/AnnotationEditView.kt | 179 ++++-------------- .../main/res/layout/pdf_reader_fragment.xml | 2 +- 4 files changed, 36 insertions(+), 152 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index 4d05dc295..1e52cc6b1 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 80 - versionName "0.0.80" + versionCode 82 + versionName "0.0.82" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt index e2c0155a6..45a7f3aa4 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt @@ -184,7 +184,6 @@ fun EditNoteModal(initialValue: String?, onDismiss: (save: Boolean, text: String colors = TextFieldDefaults.textFieldColors( textColor = Color.White ) - ) } } @@ -381,7 +380,7 @@ fun BottomSheetUI(content: @Composable () -> Unit) { .wrapContentHeight() .fillMaxWidth() .clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp)) - .background(Color.White) + .background(Color.Transparent) .statusBarsPadding() ) { Scaffold( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt index 7bcd2989b..97abd9781 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt @@ -1,41 +1,30 @@ package app.omnivore.omnivore.ui.reader -import android.content.ClipData +import android.R import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.compose.foundation.background +import android.view.WindowManager import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.unit.dp import androidx.fragment.app.DialogFragment -import app.omnivore.omnivore.ui.notebook.ArticleNotes -import app.omnivore.omnivore.ui.notebook.HighlightsList -import app.omnivore.omnivore.ui.notebook.notebookMD +import app.omnivore.omnivore.ui.notebook.EditNoteModal import app.omnivore.omnivore.ui.theme.OmnivoreTheme +import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED +import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import kotlinx.coroutines.launch -class AnnotationEditFragment : BottomSheetDialogFragment() { + +class AnnotationEditFragment : DialogFragment() { private var onSave: (String) -> Unit = {} private var onCancel: () -> Unit = {} private var initialAnnotation: String = "" @@ -50,24 +39,38 @@ class AnnotationEditFragment : BottomSheetDialogFragment() { this.onCancel = onCancel } + @OptIn(ExperimentalMaterial3Api::class) override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { + return ComposeView(requireContext()).apply { - // Dispose of the Composition when the view's LifecycleOwner - // is destroyed - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + + (dialog as? BottomSheetDialog)?.let { + it.behavior.skipCollapsed = true + it.behavior.state = STATE_EXPANDED + } + + dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN) + + + + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - OmnivoreTheme { - AnnotationEditView( - initialAnnotation, - onSave, - onCancel, - // dismissAction = { dismiss() } - ) - } + val annotation = remember { mutableStateOf(initialAnnotation ?: "") } + + OmnivoreTheme { + EditNoteModal(initialValue = initialAnnotation, onDismiss = { save, text -> + if (save) { + onSave(text ?: "") + } else { + onCancel() + } + dismissNow() + }) + } } } } @@ -77,121 +80,3 @@ class AnnotationEditFragment : BottomSheetDialogFragment() { super.onDismiss(dialog) } } - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AnnotationEditView( - initialAnnotation: String, - onSave: (String) -> Unit, - onCancel: () -> Unit, -) { - val annotation = remember { mutableStateOf(initialAnnotation) } - val focusRequester = FocusRequester() - - Scaffold( - topBar = { - TopAppBar( - title = { Text("Notebook") }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.background - ), - navigationIcon = { - IconButton(onClick = { - onCancel() - }) { - Icon( - imageVector = Icons.Filled.ArrowBack, - modifier = Modifier, - contentDescription = "Back", - ) - } - }, - actions = { - TextButton( - onClick = { - onSave(annotation.value) - } - ) { - Text("Save") - } - } - ) - } - ) { paddingValues -> - Column( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() - ) { - TextField( - value = annotation.value, - onValueChange = { annotation.value = it }, - colors = TextFieldDefaults.textFieldColors( - textColor = Color.White, - containerColor = Color.Green, - ), - modifier = Modifier - .focusRequester(focusRequester) - .fillMaxSize() - ) - } - } -} -// -// Column( -// modifier = Modifier.padding(16.dp), -// horizontalAlignment = Alignment.CenterHorizontally, -// ) { -// Row { -// TextButton( -// onClick = { -// onCancel() -// dismissAction() -// } -// ) { -// Text("Cancel") -// } -// -// Spacer(modifier = Modifier.weight(1.0F)) -// -// Text(text = "Note") -// -// Spacer(modifier = Modifier.weight(1.0F)) -// -// TextButton( -// onClick = { -// onSave(annotation.value) -// dismissAction() -// } -// ) { -// Text("Save") -// } -// } -// -// Spacer(modifier = Modifier.height(8.dp)) -// -// -// -// Spacer(modifier = Modifier.height(16.dp)) - -// -// LaunchedEffect(Unit) { -// focusRequester.requestFocus() -// } - -// Row { -// Spacer(modifier = Modifier.weight(0.1F)) -// TextField( -// value = annotation.value, -// onValueChange = { annotation.value = it }, -// modifier = Modifier -// .width(IntrinsicSize.Max) -// .height(IntrinsicSize.Max) -// .weight(1.0F) -// ) -// Spacer(modifier = Modifier.weight(0.1F)) -// } -// } -// -// // } -//} diff --git a/android/Omnivore/app/src/main/res/layout/pdf_reader_fragment.xml b/android/Omnivore/app/src/main/res/layout/pdf_reader_fragment.xml index 401e22ae8..2cf240f6f 100644 --- a/android/Omnivore/app/src/main/res/layout/pdf_reader_fragment.xml +++ b/android/Omnivore/app/src/main/res/layout/pdf_reader_fragment.xml @@ -7,7 +7,7 @@ + android:layout_height="match_parent" />