add android message handlers in same places webKit handlers are used

This commit is contained in:
Satindar Dhillon
2022-09-22 22:02:47 -07:00
parent 1d619dda5d
commit 821f2da3a0
5 changed files with 113 additions and 77 deletions

View File

@ -66,7 +66,7 @@ fun WebReader(params: WebReaderParams) {
webViewClient = object : WebViewClient() { webViewClient = object : WebViewClient() {
} }
addJavascriptInterface(WebMessageHandler(), "WebMessageHandler") addJavascriptInterface(AndroidWebKitMessageHandler(), "AndroidWebKitMessageHandler")
loadDataWithBaseURL("file:///android_asset/", styledContent, "text/html; charset=utf-8", "utf-8", null); loadDataWithBaseURL("file:///android_asset/", styledContent, "text/html; charset=utf-8", "utf-8", null);
} }
@ -139,7 +139,7 @@ class OmnivoreWebView(context: Context) : WebView(context) {
} }
class WebMessageHandler { class AndroidWebKitMessageHandler {
@JavascriptInterface @JavascriptInterface
fun handleMessage(jsonString: String) { fun handleMessage(jsonString: String) {
val message = JSONObject(jsonString) val message = JSONObject(jsonString)

View File

@ -18,7 +18,7 @@ const mutation = async (name, input) => {
} else { } else {
// Send android a message // Send android a message
console.log('sending android a message', message) console.log('sending android a message', message)
WebMessageHandler.handleMessage(JSON.stringify(message)) AndroidWebKitMessageHandler.handleMessage(JSON.stringify(message))
} }
} }

View File

@ -1,5 +1,9 @@
export {} export {}
declare type AndroidWebKitMessageHandler = {
handleMessage: (string) => void
}
declare global { declare global {
interface Window { interface Window {
webkit?: Webkit webkit?: Webkit
@ -9,6 +13,7 @@ declare global {
Intercom: Function Intercom: Function
intercomSettings: IntercomSettings intercomSettings: IntercomSettings
analytics?: Analytics analytics?: Analytics
AndroidWebKitMessageHandler?: AndroidWebKitMessageHandler
} }
} }

View File

@ -84,21 +84,22 @@ export function Article(props: ArticleProps): JSX.Element {
window.webkit.messageHandlers.readingProgressUpdate?.postMessage({ window.webkit.messageHandlers.readingProgressUpdate?.postMessage({
progress: readingProgress, progress: readingProgress,
}) })
} else if (typeof window?.AndroidWebKitMessageHandler != 'undefined') {
window.AndroidWebKitMessageHandler.handleMessage(
JSON.stringify({ progress: readingProgress })
)
} }
}, [readingProgress]) }, [readingProgress])
useScrollWatcher( useScrollWatcher((changeset: ScrollOffsetChangeset) => {
(changeset: ScrollOffsetChangeset) => { if (window && window.document.scrollingElement) {
if (window && window.document.scrollingElement) { const newReadingProgress =
const newReadingProgress = window.scrollY / window.document.scrollingElement.scrollHeight
window.scrollY / window.document.scrollingElement.scrollHeight const adjustedReadingProgress =
const adjustedReadingProgress = newReadingProgress > 0.92 ? 1 : newReadingProgress
newReadingProgress > 0.92 ? 1 : newReadingProgress debouncedSetReadingProgress(adjustedReadingProgress * 100)
debouncedSetReadingProgress(adjustedReadingProgress * 100) }
} }, 1000)
},
1000
)
const layoutImages = useCallback( const layoutImages = useCallback(
(image: HTMLImageElement, container: HTMLDivElement | null) => { (image: HTMLImageElement, container: HTMLDivElement | null) => {
@ -127,46 +128,46 @@ export function Article(props: ArticleProps): JSX.Element {
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return return
} }
if (!shouldScrollToInitialPosition) { if (!shouldScrollToInitialPosition) {
return return
} }
setShouldScrollToInitialPosition(false) setShouldScrollToInitialPosition(false)
// If we are scrolling to a highlight, dont scroll to read position // If we are scrolling to a highlight, dont scroll to read position
if (props.highlightHref.current) { if (props.highlightHref.current) {
return return
} }
if (props.initialReadingProgress && props.initialReadingProgress >= 98) { if (props.initialReadingProgress && props.initialReadingProgress >= 98) {
return return
} }
const anchorElement = props.highlightHref.current const anchorElement = props.highlightHref.current
? document.querySelector( ? document.querySelector(
`[omnivore-highlight-id="${props.highlightHref.current}"]` `[omnivore-highlight-id="${props.highlightHref.current}"]`
) )
: document.querySelector( : document.querySelector(
`[data-omnivore-anchor-idx='${props.initialAnchorIndex.toString()}']` `[data-omnivore-anchor-idx='${props.initialAnchorIndex.toString()}']`
) )
if (anchorElement) { if (anchorElement) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const calculateOffset = (obj: any): number => { const calculateOffset = (obj: any): number => {
let offset = 0 let offset = 0
if (obj.offsetParent) { if (obj.offsetParent) {
do { do {
offset += obj.offsetTop offset += obj.offsetTop
} while ((obj = obj.offsetParent)) } while ((obj = obj.offsetParent))
return offset return offset
}
return 0
} }
const calculatedOffset = calculateOffset(anchorElement) return 0
window.document.documentElement.scroll(0, calculatedOffset - 100)
} }
const calculatedOffset = calculateOffset(anchorElement)
window.document.documentElement.scroll(0, calculatedOffset - 100)
}
}, [ }, [
props.initialAnchorIndex, props.initialAnchorIndex,
props.initialReadingProgress, props.initialReadingProgress,

View File

@ -1,4 +1,10 @@
import { useEffect, useRef, useCallback, useState, MutableRefObject } from 'react' import {
useEffect,
useRef,
useCallback,
useState,
MutableRefObject,
} from 'react'
import { makeHighlightStartEndOffset } from '../../../lib/highlights/highlightGenerator' import { makeHighlightStartEndOffset } from '../../../lib/highlights/highlightGenerator'
import type { HighlightLocation } from '../../../lib/highlights/highlightGenerator' import type { HighlightLocation } from '../../../lib/highlights/highlightGenerator'
import { useSelection } from '../../../lib/highlights/useSelection' import { useSelection } from '../../../lib/highlights/useSelection'
@ -30,7 +36,6 @@ type HighlightsLayerProps = {
scrollToHighlight: MutableRefObject<string | null> scrollToHighlight: MutableRefObject<string | null>
setShowHighlightsModal: React.Dispatch<React.SetStateAction<boolean>> setShowHighlightsModal: React.Dispatch<React.SetStateAction<boolean>>
articleMutations: ArticleMutations articleMutations: ArticleMutations
} }
type HighlightModalAction = 'none' | 'addComment' | 'share' type HighlightModalAction = 'none' | 'addComment' | 'share'
@ -88,7 +93,9 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
// that now that all the content has been injected into the // that now that all the content has been injected into the
// page. // page.
if (props.scrollToHighlight.current) { if (props.scrollToHighlight.current) {
const anchorElement = document.querySelector(`[omnivore-highlight-id="${props.scrollToHighlight.current}"]`) const anchorElement = document.querySelector(
`[omnivore-highlight-id="${props.scrollToHighlight.current}"]`
)
if (anchorElement) { if (anchorElement) {
anchorElement.scrollIntoView({ behavior: 'auto' }) anchorElement.scrollIntoView({ behavior: 'auto' })
} }
@ -100,7 +107,8 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
const highlightId = id || focusedHighlight?.id const highlightId = id || focusedHighlight?.id
if (!highlightId) return if (!highlightId) return
const didDeleteHighlight = await props.articleMutations.deleteHighlightMutation(highlightId) const didDeleteHighlight =
await props.articleMutations.deleteHighlightMutation(highlightId)
if (didDeleteHighlight) { if (didDeleteHighlight) {
removeHighlights( removeHighlights(
@ -154,6 +162,13 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
actionID: 'annotate', actionID: 'annotate',
annotation: inputs.highlight?.annotation ?? '', annotation: inputs.highlight?.annotation ?? '',
}) })
} else if (typeof window?.AndroidWebKitMessageHandler != 'undefined') {
window.AndroidWebKitMessageHandler.handleMessage(
JSON.stringify({
actionID: 'annotate',
annotation: inputs.highlight?.annotation ?? '',
})
)
} else { } else {
inputs.createHighlightForNote = async (note?: string) => { inputs.createHighlightForNote = async (note?: string) => {
if (!inputs.selectionData) { if (!inputs.selectionData) {
@ -171,13 +186,16 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
selection: SelectionAttributes, selection: SelectionAttributes,
note?: string note?: string
): Promise<Highlight | undefined> => { ): Promise<Highlight | undefined> => {
const result = await createHighlight({ const result = await createHighlight(
selection: selection, {
articleId: props.articleId, selection: selection,
existingHighlights: highlights, articleId: props.articleId,
highlightStartEndOffsets: highlightLocations, existingHighlights: highlights,
annotation: note, highlightStartEndOffsets: highlightLocations,
}, props.articleMutations) annotation: note,
},
props.articleMutations
)
if (!result.highlights || result.highlights.length == 0) { if (!result.highlights || result.highlights.length == 0) {
// TODO: show an error message // TODO: show an error message
@ -235,16 +253,18 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
) )
const scrollToHighlight = (id: string) => { const scrollToHighlight = (id: string) => {
const foundElement = document.querySelector(`[omnivore-highlight-id="${id}"]`) const foundElement = document.querySelector(
if(foundElement){ `[omnivore-highlight-id="${id}"]`
foundElement.scrollIntoView({ )
block: 'center', if (foundElement) {
behavior: 'smooth' foundElement.scrollIntoView({
}) block: 'center',
window.location.hash = `#${id}` behavior: 'smooth',
props.setShowHighlightsModal(false) })
} window.location.hash = `#${id}`
} props.setShowHighlightsModal(false)
}
}
// Detect mouseclick on a highlight -- call `setFocusedHighlight` when highlight detected // Detect mouseclick on a highlight -- call `setFocusedHighlight` when highlight detected
const handleClickHighlight = useCallback( const handleClickHighlight = useCallback(
@ -269,13 +289,17 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
// In the native app we post a message with the rect of the // In the native app we post a message with the rect of the
// highlight, so the app can display a native menu // highlight, so the app can display a native menu
const rect = (target as Element).getBoundingClientRect() const rect = (target as Element).getBoundingClientRect()
window?.webkit?.messageHandlers.viewerAction?.postMessage({ const message = {
actionID: 'showMenu', actionID: 'showMenu',
rectX: rect.x, rectX: rect.x,
rectY: rect.y, rectY: rect.y,
rectWidth: rect.width, rectWidth: rect.width,
rectHeight: rect.height, rectHeight: rect.height,
}) }
window?.webkit?.messageHandlers.viewerAction?.postMessage(message)
window?.AndroidWebKitMessageHandler?.handleMessage(
JSON.stringify(message)
)
setFocusedHighlight(highlight) setFocusedHighlight(highlight)
} }
} else if ((target as Element).hasAttribute(highlightNoteIdAttribute)) { } else if ((target as Element).hasAttribute(highlightNoteIdAttribute)) {
@ -326,13 +350,19 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
break break
case 'share': case 'share':
if (props.isAppleAppEmbed) { if (props.isAppleAppEmbed) {
// send action to native app (naive app doesn't handle this yet so it's a no-op) const message = {
window?.webkit?.messageHandlers.highlightAction?.postMessage({
actionID: 'share', actionID: 'share',
highlightID: focusedHighlight?.id, highlightID: focusedHighlight?.id,
}) }
window?.webkit?.messageHandlers.highlightAction?.postMessage(
message
)
} }
window?.AndroidWebKitMessageHandler?.handleMessage(
JSON.stringify(message)
)
if (focusedHighlight) { if (focusedHighlight) {
if (canShareNative) { if (canShareNative) {
handleNativeShare(focusedHighlight.shortId) handleNativeShare(focusedHighlight.shortId)
@ -392,7 +422,9 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
} }
const speakingSection = async (event: SpeakingSectionEvent) => { const speakingSection = async (event: SpeakingSectionEvent) => {
const item = document.querySelector(`[data-omnivore-anchor-idx="${event.anchorIdx}"]`) const item = document.querySelector(
`[data-omnivore-anchor-idx="${event.anchorIdx}"]`
)
const otherItems = document.querySelectorAll('.speakingSection') const otherItems = document.querySelectorAll('.speakingSection')
otherItems.forEach((other) => { otherItems.forEach((other) => {
if (other != item) { if (other != item) {
@ -435,7 +467,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
document.addEventListener('saveAnnotation', saveAnnotation) document.addEventListener('saveAnnotation', saveAnnotation)
document.addEventListener('speakingSection', speakingSection) document.addEventListener('speakingSection', speakingSection)
return () => { return () => {
document.removeEventListener('annotate', annotate) document.removeEventListener('annotate', annotate)
document.removeEventListener('highlight', highlight) document.removeEventListener('highlight', highlight)
@ -445,7 +476,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
document.removeEventListener('dismissHighlight', dismissHighlight) document.removeEventListener('dismissHighlight', dismissHighlight)
document.removeEventListener('saveAnnotation', saveAnnotation) document.removeEventListener('saveAnnotation', saveAnnotation)
document.removeEventListener('speakingSection', speakingSection) document.removeEventListener('speakingSection', speakingSection)
} }
}) })
@ -523,4 +553,4 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
} }
return <></> return <></>
} }