From f6376b929ff480f3e01b13e2ad8beb9917e164a7 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 27 Sep 2022 09:30:20 -0700 Subject: [PATCH] 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 } }