From aac794949ce6fd43f6c2ebbec6896426469e3b25 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 26 Sep 2022 22:44:30 -0700 Subject: [PATCH 1/7] send saved highlight to server --- .../src/main/graphql/CreateHighlight.graphql | 13 ++++++++ .../src/main/graphql/DeleteHighlight.graphql | 12 +++++++ .../graphql/ReadingProgressMutation.graphql | 14 ++++++++ .../src/main/graphql/UpdateHighlight.graphql | 13 ++++++++ .../omnivore/networking/HighlightMutations.kt | 32 +++++++++++++++++++ .../omnivore/networking/SearchQuery.kt | 4 +-- .../omnivore/omnivore/ui/reader/WebReader.kt | 6 ++-- .../omnivore/ui/reader/WebReaderViewModel.kt | 19 ++++++----- 8 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 android/Omnivore/app/src/main/graphql/CreateHighlight.graphql create mode 100644 android/Omnivore/app/src/main/graphql/DeleteHighlight.graphql create mode 100644 android/Omnivore/app/src/main/graphql/ReadingProgressMutation.graphql create mode 100644 android/Omnivore/app/src/main/graphql/UpdateHighlight.graphql create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/HighlightMutations.kt diff --git a/android/Omnivore/app/src/main/graphql/CreateHighlight.graphql b/android/Omnivore/app/src/main/graphql/CreateHighlight.graphql new file mode 100644 index 000000000..d4417067d --- /dev/null +++ b/android/Omnivore/app/src/main/graphql/CreateHighlight.graphql @@ -0,0 +1,13 @@ +mutation CreateHighlight($input: CreateHighlightInput!) { + createHighlight(input: $input) { + ... on CreateHighlightSuccess { + highlight { + ...HighlightFields + } + } + + ... on CreateHighlightError { + errorCodes + } + } +} diff --git a/android/Omnivore/app/src/main/graphql/DeleteHighlight.graphql b/android/Omnivore/app/src/main/graphql/DeleteHighlight.graphql new file mode 100644 index 000000000..e61011a3e --- /dev/null +++ b/android/Omnivore/app/src/main/graphql/DeleteHighlight.graphql @@ -0,0 +1,12 @@ +mutation DeleteHighlight($highlightId: ID!) { + deleteHighlight(highlightId: $highlightId) { + ... on DeleteHighlightSuccess { + highlight { + id + } + } + ... on DeleteHighlightError { + errorCodes + } + } +} diff --git a/android/Omnivore/app/src/main/graphql/ReadingProgressMutation.graphql b/android/Omnivore/app/src/main/graphql/ReadingProgressMutation.graphql new file mode 100644 index 000000000..80a30eea1 --- /dev/null +++ b/android/Omnivore/app/src/main/graphql/ReadingProgressMutation.graphql @@ -0,0 +1,14 @@ +mutation SaveArticleReadingProgress($input: SaveArticleReadingProgressInput!) { + saveArticleReadingProgress(input: $input) { + ... on SaveArticleReadingProgressSuccess { + updatedArticle { + id + readingProgressPercent + readingProgressAnchorIndex + } + } + ... on SaveArticleReadingProgressError { + errorCodes + } + } +} diff --git a/android/Omnivore/app/src/main/graphql/UpdateHighlight.graphql b/android/Omnivore/app/src/main/graphql/UpdateHighlight.graphql new file mode 100644 index 000000000..18f93dcea --- /dev/null +++ b/android/Omnivore/app/src/main/graphql/UpdateHighlight.graphql @@ -0,0 +1,13 @@ +mutation UpdateHighlight($input: UpdateHighlightInput!) { + updateHighlight(input: $input) { + ... on UpdateHighlightSuccess { + highlight { + id + } + } + + ... on UpdateHighlightError { + errorCodes + } + } +} 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 new file mode 100644 index 000000000..ace426c7a --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/HighlightMutations.kt @@ -0,0 +1,32 @@ +package app.omnivore.omnivore.networking + +import app.omnivore.omnivore.graphql.generated.CreateHighlightMutation +import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput +import com.apollographql.apollo3.api.Optional +import com.google.gson.Gson + +data class CreateHighlightParams( + val shortId: String?, + val highlightID: String?, + val quote: String?, + val patch: String?, + val articleId: String?, + val `annotation`: String? +) { + fun asCreateHighlightInput() = CreateHighlightInput( + annotation = Optional.presentIfNotNull(`annotation`), + articleId = articleId ?: "", + id = highlightID ?: "", + patch = patch ?: "", + quote = quote ?: "", + shortId = shortId ?: "" + ) +} + +suspend fun Networker.createHighlight(jsonString: String) { + val input = Gson().fromJson(jsonString, CreateHighlightParams::class.java).asCreateHighlightInput() + + authenticatedApolloClient().mutation( + CreateHighlightMutation(input) + ).execute() +} 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 a658a3aea..7086c123e 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 @@ -22,7 +22,7 @@ suspend fun Networker.search( ) ).execute() - val cursor = result.data?.search?.onSearchSuccess?.pageInfo?.endCursor + val newCursor = result.data?.search?.onSearchSuccess?.pageInfo?.endCursor val itemList = result.data?.search?.onSearchSuccess?.edges ?: listOf() val items = itemList.map { @@ -49,5 +49,5 @@ suspend fun Networker.search( ) } - return SearchQueryResponse(cursor, items) + return SearchQueryResponse(newCursor, items) } 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 98401f013..2d938dc89 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 @@ -28,7 +28,7 @@ fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewMod if (webReaderParams != null) { WebReader(webReaderParams!!, webReaderViewModel) } else { - // TODO: add a proper loading view + // TODO: add a proper loading viewhandleIncomingWebMessage Text("Loading...") } } @@ -143,9 +143,9 @@ class OmnivoreWebView(context: Context) : WebView(context) { } } -class AndroidWebKitMessenger(val messageHandler: (String, JSONObject) -> Unit) { +class AndroidWebKitMessenger(val messageHandler: (String, String) -> Unit) { @JavascriptInterface fun handleIdentifiableMessage(actionID: String, jsonString: String) { - messageHandler(actionID, JSONObject(jsonString)) + messageHandler(actionID, jsonString) } } 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 f05943dcb..e116176ef 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 @@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.models.LinkedItem import app.omnivore.omnivore.networking.Networker +import app.omnivore.omnivore.networking.createHighlight import app.omnivore.omnivore.networking.linkedItem import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel @@ -45,32 +46,34 @@ class WebReaderViewModel @Inject constructor( } } - fun handleIncomingWebMessage(actionID: String, json: JSONObject) { + fun handleIncomingWebMessage(actionID: String, jsonString: String) { when (actionID) { "createHighlight" -> { - Log.d("Loggo", "receive create highlight action: $json") + viewModelScope.launch { + networker.createHighlight(jsonString) + } } "deleteHighlight" -> { // { highlightId } - Log.d("Loggo", "receive delete highlight action: $json") + Log.d("Loggo", "receive delete highlight action: $jsonString") } "updateHighlight" -> { - Log.d("Loggo", "receive update highlight action: $json") + Log.d("Loggo", "receive update highlight action: $jsonString") } "articleReadingProgress" -> { - Log.d("Loggo", "received article reading progress action: $json") + Log.d("Loggo", "received article reading progress action: $jsonString") } "annotate" -> { - Log.d("Loggo", "received annotate action: $json") + Log.d("Loggo", "received annotate action: $jsonString") } "existingHighlightTap" -> { - Log.d("Loggo", "receive existing highlight tap action: $json") + Log.d("Loggo", "receive existing highlight tap action: $jsonString") } "shareHighlight" -> { // unimplemented } else -> { - Log.d("Loggo", "receive unrecognized action of $actionID with json: $json") + Log.d("Loggo", "receive unrecognized action of $actionID with json: $jsonString") } } } From f6376b929ff480f3e01b13e2ad8beb9917e164a7 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 27 Sep 2022 09:30:20 -0700 Subject: [PATCH 2/7] add a save annotation view --- .../omnivore/networking/HighlightMutations.kt | 12 ++- .../omnivore/ui/reader/AnnotationEditView.kt | 68 +++++++++++++++ .../omnivore/omnivore/ui/reader/WebReader.kt | 82 +++++++++++++------ .../omnivore/ui/reader/WebReaderViewModel.kt | 21 ++++- 4 files changed, 151 insertions(+), 32 deletions(-) create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt 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 ace426c7a..8c418d63e 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 @@ -1,5 +1,6 @@ package app.omnivore.omnivore.networking +import android.util.Log import app.omnivore.omnivore.graphql.generated.CreateHighlightMutation import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput import com.apollographql.apollo3.api.Optional @@ -23,10 +24,13 @@ data class CreateHighlightParams( ) } -suspend fun Networker.createHighlight(jsonString: String) { +suspend fun Networker.createHighlight(jsonString: String): Boolean { val input = Gson().fromJson(jsonString, CreateHighlightParams::class.java).asCreateHighlightInput() - authenticatedApolloClient().mutation( - CreateHighlightMutation(input) - ).execute() + Log.d("Loggo", "created highlight input: $input") + + val result = authenticatedApolloClient().mutation(CreateHighlightMutation(input)).execute() + + val highlight = result.data?.createHighlight?.onCreateHighlightSuccess?.highlight + return highlight != null } 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 new file mode 100644 index 000000000..e308250e2 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt @@ -0,0 +1,68 @@ +package app.omnivore.omnivore.ui.reader + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +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.unit.dp + +// TODO: better layout and styling for this view +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AnnotationEditView( + initialAnnotation: String, + onSave: (String) -> Unit, + onCancel: () -> Unit, +) { + val annotation = remember { mutableStateOf(initialAnnotation) } + + Column( + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .background(MaterialTheme.colorScheme.background) + .padding(8.dp), + ) { + Column( + modifier = Modifier.padding(16.dp), + ) { + Text(text = "Note") + + Spacer(modifier = Modifier.height(8.dp)) + + TextField( + value = annotation.value, + onValueChange = { annotation.value = it } + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Row( + modifier = Modifier.align(Alignment.End) + ) { + Button( + onClick = { + onCancel() + } + ) { + Text("Cancel") + } + + Spacer(modifier = Modifier.width(8.dp)) + + Button( + onClick = { + onSave(annotation.value) + } + ) { + Text("Save") + } + } + } +} 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 2d938dc89..1c4532d97 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 @@ -8,6 +8,7 @@ import android.view.* import android.webkit.JavascriptInterface import android.webkit.WebView import android.webkit.WebViewClient +import androidx.compose.foundation.layout.Box import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -28,7 +29,7 @@ fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewMod if (webReaderParams != null) { WebReader(webReaderParams!!, webReaderViewModel) } else { - // TODO: add a proper loading viewhandleIncomingWebMessage + // TODO: add a proper loading view Text("Loading...") } } @@ -36,6 +37,8 @@ fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewMod @SuppressLint("SetJavaScriptEnabled") @Composable fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) { + val annotation: String? by webReaderViewModel.annotationLiveData.observeAsState(null) + WebView.setWebContentsDebuggingEnabled(true) val webReaderContent = WebReaderContent( @@ -51,32 +54,57 @@ fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) { val styledContent = webReaderContent.styledContent() - AndroidView(factory = { - OmnivoreWebView(it).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT + Box { + AndroidView(factory = { + OmnivoreWebView(it).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + + settings.javaScriptEnabled = true + settings.allowContentAccess = true + settings.allowFileAccess = true + settings.domStorageEnabled = true + + webViewClient = object : WebViewClient() { + } + + val javascriptInterface = AndroidWebKitMessenger { actionID, json -> + webReaderViewModel.handleIncomingWebMessage(actionID, json) + } + + addJavascriptInterface(javascriptInterface, "AndroidWebKitMessenger") + loadDataWithBaseURL( + "file:///android_asset/", + styledContent, + "text/html; charset=utf-8", + "utf-8", + null + ); + + } + }, update = { + it.loadDataWithBaseURL( + "file:///android_asset/", + styledContent, + "text/html; charset=utf-8", + "utf-8", + null + ); + }) + + if (annotation != null) { + AnnotationEditView( + initialAnnotation = annotation!!, + onSave = { Log.d("Loggo", "Saving annotation: $it") }, + onCancel = { + Log.d("Loggo", "Cancelling annotation") + webReaderViewModel.cancelAnnotationEdit() + } ) - - settings.javaScriptEnabled = true - settings.allowContentAccess = true - settings.allowFileAccess = true - settings.domStorageEnabled = true - - webViewClient = object : WebViewClient() { - } - - val javascriptInterface = AndroidWebKitMessenger { actionID, json -> - webReaderViewModel.handleIncomingWebMessage(actionID, json) - } - - addJavascriptInterface(javascriptInterface, "AndroidWebKitMessenger") - loadDataWithBaseURL("file:///android_asset/", styledContent, "text/html; charset=utf-8", "utf-8", null); - } - }, update = { - it.loadDataWithBaseURL("file:///android_asset/", styledContent, "text/html; charset=utf-8", "utf-8", null); - }) + } } class OmnivoreWebView(context: Context) : WebView(context) { @@ -97,7 +125,9 @@ class OmnivoreWebView(context: Context) : WebView(context) { override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { return when (item.itemId) { R.id.annotate -> { - Log.d("Loggo", "Annotate action selected") + val script = "var event = new Event('annotate');document.dispatchEvent(event);" + evaluateJavascript(script, null) // Maybe this one isn't needed? + // TODO: open note modal mode.finish() true } 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 e116176ef..c887db6e0 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 @@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.models.LinkedItem +import app.omnivore.omnivore.networking.CreateHighlightParams import app.omnivore.omnivore.networking.Networker import app.omnivore.omnivore.networking.createHighlight import app.omnivore.omnivore.networking.linkedItem @@ -20,12 +21,17 @@ data class WebReaderParams( val articleContent: ArticleContent ) +data class AnnotationWebViewMessage( + val annotation: String? +) + @HiltViewModel class WebReaderViewModel @Inject constructor( private val datastoreRepo: DatastoreRepository, private val networker: Networker ): ViewModel() { val webReaderParamsLiveData = MutableLiveData(null) + val annotationLiveData = MutableLiveData(null) fun loadItem(slug: String) { viewModelScope.launch { @@ -50,7 +56,8 @@ class WebReaderViewModel @Inject constructor( when (actionID) { "createHighlight" -> { viewModelScope.launch { - networker.createHighlight(jsonString) + val isHighlightSynced = networker.createHighlight(jsonString) + Log.d("Network", "isHighlightSynced = $isHighlightSynced") } } "deleteHighlight" -> { @@ -64,7 +71,12 @@ class WebReaderViewModel @Inject constructor( Log.d("Loggo", "received article reading progress action: $jsonString") } "annotate" -> { - Log.d("Loggo", "received annotate action: $jsonString") + viewModelScope.launch { + val annotation = Gson() + .fromJson(jsonString, AnnotationWebViewMessage::class.java) + .annotation ?: "" + annotationLiveData.value = annotation + } } "existingHighlightTap" -> { Log.d("Loggo", "receive existing highlight tap action: $jsonString") @@ -80,5 +92,10 @@ class WebReaderViewModel @Inject constructor( fun reset() { webReaderParamsLiveData.value = null + annotationLiveData.value = null + } + + fun cancelAnnotationEdit() { + annotationLiveData.value = null } } From d87d25e381afcf3dd9212aed5d4363725f576cf8 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 27 Sep 2022 10:11:48 -0700 Subject: [PATCH 3/7] send js to webview when annotation is created --- .../omnivore/omnivore/ui/reader/WebReader.kt | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) 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 1c4532d97..58315be84 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 @@ -13,6 +13,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.viewinterop.AndroidView import app.omnivore.omnivore.R import org.json.JSONObject @@ -37,6 +39,8 @@ fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewMod @SuppressLint("SetJavaScriptEnabled") @Composable fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) { + val javascriptToExecute = remember { mutableStateOf(null) } + val annotation: String? by webReaderViewModel.annotationLiveData.observeAsState(null) WebView.setWebContentsDebuggingEnabled(true) @@ -81,25 +85,23 @@ fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) { "text/html; charset=utf-8", "utf-8", null - ); - + ) } }, update = { - it.loadDataWithBaseURL( - "file:///android_asset/", - styledContent, - "text/html; charset=utf-8", - "utf-8", - null - ); + if (javascriptToExecute.value != null) { + it.evaluateJavascript(javascriptToExecute.value!!, null) + } }) if (annotation != null) { AnnotationEditView( initialAnnotation = annotation!!, - onSave = { Log.d("Loggo", "Saving annotation: $it") }, + onSave = { + val script = "var event = new Event('saveAnnotation');event.annotation = '$it';document.dispatchEvent(event);" + javascriptToExecute.value = script + webReaderViewModel.cancelAnnotationEdit() + }, onCancel = { - Log.d("Loggo", "Cancelling annotation") webReaderViewModel.cancelAnnotationEdit() } ) From d6a38790cd32ce7f3347a954ecff697948beb79b Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 27 Sep 2022 20:13:51 -0700 Subject: [PATCH 4/7] remove TODO comment --- .../main/java/app/omnivore/omnivore/ui/reader/WebReader.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 58315be84..e302307d7 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 @@ -79,6 +79,7 @@ fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) { } addJavascriptInterface(javascriptInterface, "AndroidWebKitMessenger") + loadDataWithBaseURL( "file:///android_asset/", styledContent, @@ -128,8 +129,7 @@ class OmnivoreWebView(context: Context) : WebView(context) { return when (item.itemId) { R.id.annotate -> { val script = "var event = new Event('annotate');document.dispatchEvent(event);" - evaluateJavascript(script, null) // Maybe this one isn't needed? - // TODO: open note modal + evaluateJavascript(script, null) mode.finish() true } From 4d889752f6af611b3aacde32b7151137aae45ca8 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 28 Sep 2022 12:34:55 -0700 Subject: [PATCH 5/7] send reading progress updates to server --- .../java/app/omnivore/omnivore/ui/reader/WebReader.kt | 1 + .../omnivore/omnivore/ui/reader/WebReaderViewModel.kt | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) 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 e302307d7..776e98123 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 @@ -39,6 +39,7 @@ fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewMod @SuppressLint("SetJavaScriptEnabled") @Composable fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) { + // TODO: maybe handle cases where js can be queued up? val javascriptToExecute = remember { mutableStateOf(null) } val annotation: String? by webReaderViewModel.annotationLiveData.observeAsState(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 c887db6e0..d3cf9cef7 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 @@ -6,10 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.models.LinkedItem -import app.omnivore.omnivore.networking.CreateHighlightParams -import app.omnivore.omnivore.networking.Networker -import app.omnivore.omnivore.networking.createHighlight -import app.omnivore.omnivore.networking.linkedItem +import app.omnivore.omnivore.networking.* import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -68,7 +65,10 @@ class WebReaderViewModel @Inject constructor( Log.d("Loggo", "receive update highlight action: $jsonString") } "articleReadingProgress" -> { - Log.d("Loggo", "received article reading progress action: $jsonString") + viewModelScope.launch { + val isReadingProgressSynced = networker.updateReadingProgress(jsonString) + Log.d("Network", "isReadingProgressSynced = $isReadingProgressSynced") + } } "annotate" -> { viewModelScope.launch { From 8e6d4d387ba1c16e66f677330ff5549edbce4dd2 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 28 Sep 2022 13:51:15 -0700 Subject: [PATCH 6/7] create text selection menu for an existing highlight --- .../networking/ReadingProgressMutations.kt | 36 +++++++++++++++++ .../omnivore/omnivore/ui/reader/WebReader.kt | 39 +++++++++++++++---- .../omnivore/ui/reader/WebReaderViewModel.kt | 3 -- .../res/menu/highlight_selection_menu.xml | 15 +++++++ .../app/src/main/res/values/strings.xml | 1 + 5 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ReadingProgressMutations.kt create mode 100644 android/Omnivore/app/src/main/res/menu/highlight_selection_menu.xml diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ReadingProgressMutations.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ReadingProgressMutations.kt new file mode 100644 index 000000000..f6670efe3 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ReadingProgressMutations.kt @@ -0,0 +1,36 @@ +package app.omnivore.omnivore.networking + +import app.omnivore.omnivore.graphql.generated.SaveArticleReadingProgressMutation +import app.omnivore.omnivore.graphql.generated.type.SaveArticleReadingProgressInput + + +import android.util.Log +import com.google.gson.Gson + +data class ReadingProgressParams( + val id: String?, + val readingProgressPercent: Double?, + val readingProgressAnchorIndex: Int? +) { + fun asSaveReadingProgressInput() = SaveArticleReadingProgressInput( + id = id ?: "", + readingProgressPercent = readingProgressPercent ?: 0.0, + readingProgressAnchorIndex = readingProgressAnchorIndex ?: 0 + ) +} + +suspend fun Networker.updateReadingProgress(jsonString: String): Boolean { + val input = Gson().fromJson(jsonString, ReadingProgressParams::class.java).asSaveReadingProgressInput() + + Log.d("Loggo", "created reading progress input: $input") + + val result = authenticatedApolloClient() + .mutation(SaveArticleReadingProgressMutation(input)) + .execute() + + val articleID = result.data?.saveArticleReadingProgress?.onSaveArticleReadingProgressSuccess?.updatedArticle?.id + + Log.d("Loggo", "updated article with id: $articleID") + + return articleID != null +} 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 776e98123..fc272beae 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 @@ -17,6 +17,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.viewinterop.AndroidView import app.omnivore.omnivore.R +import app.omnivore.omnivore.networking.ReadingProgressParams +import com.google.gson.Gson import org.json.JSONObject @@ -76,7 +78,17 @@ fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) { } val javascriptInterface = AndroidWebKitMessenger { actionID, json -> - webReaderViewModel.handleIncomingWebMessage(actionID, json) + when (actionID) { + "existingHighlightTap" -> { + isExistingHighlightSelected = true + actionTapCoordinates = Gson().fromJson(json, ActionTapCoordinates::class.java) + Log.d("Loggo", "receive existing highlight tap action: $actionTapCoordinates") + startActionMode(null) + } + else -> { + webReaderViewModel.handleIncomingWebMessage(actionID, json) + } + } } addJavascriptInterface(javascriptInterface, "AndroidWebKitMessenger") @@ -112,10 +124,18 @@ fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) { } class OmnivoreWebView(context: Context) : WebView(context) { + var isExistingHighlightSelected = false + var actionTapCoordinates: ActionTapCoordinates? = null + private val actionModeCallback = object : ActionMode.Callback2() { // Called when the action mode is created; startActionMode() was called override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { - mode.menuInflater.inflate(R.menu.text_selection_menu, menu) + if (isExistingHighlightSelected) { + mode.menuInflater.inflate(R.menu.highlight_selection_menu, menu) + isExistingHighlightSelected = false + } else { + mode.menuInflater.inflate(R.menu.text_selection_menu, menu) + } return true } @@ -158,21 +178,19 @@ class OmnivoreWebView(context: Context) : WebView(context) { } } - private var currentActionModeCallback: ActionMode.Callback? = actionModeCallback - override fun startActionMode(callback: ActionMode.Callback?): ActionMode { - return super.startActionMode(currentActionModeCallback) + return super.startActionMode(actionModeCallback) } override fun startActionModeForChild( originalView: View?, callback: ActionMode.Callback? ): ActionMode { - return super.startActionModeForChild(originalView, currentActionModeCallback) + return super.startActionModeForChild(originalView, actionModeCallback) } override fun startActionMode(callback: ActionMode.Callback?, type: Int): ActionMode { - return super.startActionMode(currentActionModeCallback, type) + return super.startActionMode(actionModeCallback, type) } } @@ -182,3 +200,10 @@ class AndroidWebKitMessenger(val messageHandler: (String, String) -> Unit) { messageHandler(actionID, jsonString) } } + +data class ActionTapCoordinates( + val rectX: Double, + val rectY: Double, + val rectWidth: Double, + val rectHeight: Double, +) 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 d3cf9cef7..cb0908ed5 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 @@ -78,9 +78,6 @@ class WebReaderViewModel @Inject constructor( annotationLiveData.value = annotation } } - "existingHighlightTap" -> { - Log.d("Loggo", "receive existing highlight tap action: $jsonString") - } "shareHighlight" -> { // unimplemented } diff --git a/android/Omnivore/app/src/main/res/menu/highlight_selection_menu.xml b/android/Omnivore/app/src/main/res/menu/highlight_selection_menu.xml new file mode 100644 index 000000000..4cc6d8400 --- /dev/null +++ b/android/Omnivore/app/src/main/res/menu/highlight_selection_menu.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/android/Omnivore/app/src/main/res/values/strings.xml b/android/Omnivore/app/src/main/res/values/strings.xml index 7bb14ba15..b6ed5d009 100644 --- a/android/Omnivore/app/src/main/res/values/strings.xml +++ b/android/Omnivore/app/src/main/res/values/strings.xml @@ -5,4 +5,5 @@ Save articles and read them later in our distraction-free reader. Highlight Annotate + Delete From e22cd75288aeb396dca47b6d9f9b09908ed00e88 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 28 Sep 2022 22:05:29 -0700 Subject: [PATCH 7/7] add delete highlight action --- .../app/omnivore/omnivore/ui/reader/WebReader.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) 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 fc272beae..c141a78ef 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 @@ -83,7 +83,7 @@ fun WebReader(params: WebReaderParams, webReaderViewModel: WebReaderViewModel) { isExistingHighlightSelected = true actionTapCoordinates = Gson().fromJson(json, ActionTapCoordinates::class.java) Log.d("Loggo", "receive existing highlight tap action: $actionTapCoordinates") - startActionMode(null) + startActionMode(null, ActionMode.TYPE_PRIMARY) } else -> { webReaderViewModel.handleIncomingWebMessage(actionID, json) @@ -161,6 +161,13 @@ class OmnivoreWebView(context: Context) : WebView(context) { mode.finish() true } + R.id.delete -> { + val script = "var event = new Event('remove');document.dispatchEvent(event);" + evaluateJavascript(script, null) + clearFocus() + mode.finish() + true + } else -> { Log.d("Loggo", "${item.itemId} selected") false @@ -170,10 +177,13 @@ class OmnivoreWebView(context: Context) : WebView(context) { // Called when the user exits the action mode override fun onDestroyActionMode(mode: ActionMode) { -// actionMode = null + Log.d("Loggo", "destroying menu: $mode") + isExistingHighlightSelected = false + actionTapCoordinates = null } override fun onGetContentRect(mode: ActionMode?, view: View?, outRect: Rect?) { + Log.d("Loggo", "outRect: $outRect, View: $view") outRect?.set(left, top, right, bottom) } } @@ -190,6 +200,7 @@ class OmnivoreWebView(context: Context) : WebView(context) { } override fun startActionMode(callback: ActionMode.Callback?, type: Int): ActionMode { + Log.d("Loggo", "startActionMode:type called") return super.startActionMode(actionModeCallback, type) } }