fix imports

This commit is contained in:
Stefano Sansone 2024-02-09 11:38:20 +00:00
parent 61ed6589bb
commit a2056c7583

View file

@ -23,7 +23,6 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.viewinterop.AndroidView
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.reader.OmnivoreWebView.Direction
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -33,330 +32,330 @@ import java.util.*
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun WebReader(
styledContent: String,
webReaderViewModel: WebReaderViewModel,
currentTheme: Themes?
styledContent: String, webReaderViewModel: WebReaderViewModel, currentTheme: Themes?
) {
val javascriptActionLoopUUID: UUID by webReaderViewModel
.javascriptActionLoopUUIDLiveData
.observeAsState(UUID.randomUUID())
val isDarkMode = isSystemInDarkTheme()
WebView.setWebContentsDebuggingEnabled(true)
// val showHighlightColorPalette = webReaderViewModel.showHighlightColorPalette.observeAsState()
// val highlightColor = webReaderViewModel.highlightColor.observeAsState()
Box {
AndroidView(factory = {
OmnivoreWebView(it).apply {
viewModel = webReaderViewModel
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
val javascriptActionLoopUUID: UUID by webReaderViewModel.javascriptActionLoopUUIDLiveData.observeAsState(
UUID.randomUUID()
)
val isDarkMode = isSystemInDarkTheme()
settings.javaScriptEnabled = true
settings.allowContentAccess = true
settings.allowFileAccess = true
settings.domStorageEnabled = true
WebView.setWebContentsDebuggingEnabled(true)
alpha = 1.0f
viewModel?.showNavBar()
currentTheme?.let { theme ->
val bg = when (theme) {
Themes.SYSTEM -> {
if (isDarkMode) {
Color.BLACK
} else {
Color.WHITE
}
}
else -> {
theme.backgroundColor
}
}
setBackgroundColor(bg.toInt())
Box {
AndroidView(factory = {
OmnivoreWebView(it).apply {
viewModel = webReaderViewModel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val scrollbarColor = when (theme) {
Themes.SYSTEM -> {
if (isDarkMode) {
Color.WHITE
} else {
Color.BLACK
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
)
settings.javaScriptEnabled = true
settings.allowContentAccess = true
settings.allowFileAccess = true
settings.domStorageEnabled = true
alpha = 1.0f
viewModel?.showNavBar()
currentTheme?.let { theme ->
val bg = when (theme) {
Themes.SYSTEM -> {
if (isDarkMode) {
Color.BLACK
} else {
Color.WHITE
}
}
else -> {
theme.backgroundColor
}
}
setBackgroundColor(bg.toInt())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val scrollbarColor = when (theme) {
Themes.SYSTEM -> {
if (isDarkMode) {
Color.WHITE
} else {
Color.BLACK
}
}
else -> {
theme.scrollbarColor
}
}
val rad = 8f
val shape = ShapeDrawable(
RoundRectShape(
floatArrayOf(
rad, rad, rad, rad, rad, rad, rad, rad
), null, null
)
)
shape.paint.color = scrollbarColor.toInt()
verticalScrollbarThumbDrawable = shape
}
}
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?, request: WebResourceRequest?
): Boolean {
var handled: Boolean? = null
request?.let {
if (request.isForMainFrame && request.hasGesture() && viewModel != null) {
viewModel?.showOpenLinkSheet(context, request.url)
handled = true
}
}
return handled ?: super.shouldOverrideUrlLoading(view, request)
}
}
val javascriptInterface = AndroidWebKitMessenger { actionID, json ->
webReaderViewModel.hasTappedExistingHighlight = false
when (actionID) {
"userTap" -> {
val tapCoordinates = Gson().fromJson(json, TapCoordinates::class.java)
Log.d("wvt", "received tap action: $tapCoordinates")
CoroutineScope(Dispatchers.Main).launch {
webReaderViewModel.lastTapCoordinates = tapCoordinates
actionMode?.finish()
actionMode = null
}
}
"existingHighlightTap" -> {
val tapCoordinates = Gson().fromJson(json, TapCoordinates::class.java)
CoroutineScope(Dispatchers.Main).launch {
webReaderViewModel.hasTappedExistingHighlight = true
webReaderViewModel.lastTapCoordinates = tapCoordinates
startActionMode(null, ActionMode.TYPE_FLOATING)
}
}
"writeToClipboard" -> {
val quote = Gson().fromJson(json, HighlightQuote::class.java).quote
quote.let { unwrappedQuote ->
val clipboard =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(unwrappedQuote, unwrappedQuote)
clipboard.setPrimaryClip(clip)
}
}
else -> {
webReaderViewModel.handleIncomingWebMessage(actionID, json)
}
}
}
addJavascriptInterface(javascriptInterface, "AndroidWebKitMessenger")
loadDataWithBaseURL(
"file:///android_asset/",
styledContent,
"text/html; charset=utf-8",
"utf-8",
null
)
requestFocus()
setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN) {
when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
scrollVertically(OmnivoreWebView.Direction.UP)
return@setOnKeyListener true
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
scrollVertically(OmnivoreWebView.Direction.DOWN)
return@setOnKeyListener true
}
}
}
// default value
false
}
}
else -> {
theme.scrollbarColor
}
}
val rad = 8f
val shape = ShapeDrawable(RoundRectShape(floatArrayOf(rad, rad, rad, rad, rad, rad, rad, rad), null, null))
shape.paint.color = scrollbarColor.toInt()
verticalScrollbarThumbDrawable = shape
}
}
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
var handled: Boolean? = null
request?.let {
if (request.isForMainFrame && request.hasGesture() && viewModel != null) {
viewModel?.showOpenLinkSheet(context, request.url)
handled = true
}
}, update = {
if (javascriptActionLoopUUID != webReaderViewModel.lastJavascriptActionLoopUUID) {
for (script in webReaderViewModel.javascriptDispatchQueue) {
Log.d("js", "executing script: $script")
it.evaluateJavascript(script, null)
}
webReaderViewModel.resetJavascriptDispatchQueue()
}
return handled ?: super.shouldOverrideUrlLoading(view, request)
}
}
val javascriptInterface = AndroidWebKitMessenger { actionID, json ->
webReaderViewModel.hasTappedExistingHighlight = false
when (actionID) {
"userTap" -> {
val tapCoordinates = Gson().fromJson(json, TapCoordinates::class.java)
Log.d("wvt", "received tap action: $tapCoordinates")
CoroutineScope(Dispatchers.Main).launch {
webReaderViewModel.lastTapCoordinates = tapCoordinates
actionMode?.finish()
actionMode = null
}
}
"existingHighlightTap" -> {
val tapCoordinates = Gson().fromJson(json, TapCoordinates::class.java)
CoroutineScope(Dispatchers.Main).launch {
webReaderViewModel.hasTappedExistingHighlight = true
webReaderViewModel.lastTapCoordinates = tapCoordinates
startActionMode(null, ActionMode.TYPE_FLOATING)
}
}
"writeToClipboard" -> {
val quote = Gson().fromJson(json, HighlightQuote::class.java).quote
quote.let { unwrappedQuote ->
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(unwrappedQuote, unwrappedQuote)
clipboard.setPrimaryClip(clip)
}
}
else -> {
webReaderViewModel.handleIncomingWebMessage(actionID, json)
}
}
}
addJavascriptInterface(javascriptInterface, "AndroidWebKitMessenger")
loadDataWithBaseURL(
"file:///android_asset/",
styledContent,
"text/html; charset=utf-8",
"utf-8",
null
)
requestFocus()
setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN) {
when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
scrollVertically(Direction.UP)
return@setOnKeyListener true
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
scrollVertically(Direction.DOWN)
return@setOnKeyListener true
}
}
}
// default value
false
}
}
}, update = {
if (javascriptActionLoopUUID != webReaderViewModel.lastJavascriptActionLoopUUID) {
for (script in webReaderViewModel.javascriptDispatchQueue) {
Log.d("js", "executing script: $script")
it.evaluateJavascript(script, null)
}
webReaderViewModel.resetJavascriptDispatchQueue()
}
})
// if (showHighlightColorPalette.value == true) {
// HighlightColorPalette(
// mode = if (isDarkMode) HighlightColorPaletteMode.Dark else HighlightColorPaletteMode.Light,
// selectedColorName = highlightColor.value?.name ?: "yellow",
// onColorSelected = {
// webReaderViewModel.setHighlightColor(it)
// },
// modifier = Modifier
// .align(Alignment.BottomCenter)
// .padding(12.dp, 12.dp, 12.dp, 36.dp)
// )
// }
}
})
}
}
class OmnivoreWebView(context: Context) : WebView(context), OnScrollChangeListener {
var viewModel: WebReaderViewModel? = null
var actionMode: ActionMode? = null
val density = resources.displayMetrics.density
var viewModel: WebReaderViewModel? = null
var actionMode: ActionMode? = null
val density = resources.displayMetrics.density
init {
setOnScrollChangeListener(this)
}
enum class Direction(val value: Int) {
UP(-1),
DOWN(1)
}
fun scrollVertically(direction: Direction, heightFactor: Int = 10) {
if (canScrollVertically(direction.value)) {
val scrollByValue = height.div(heightFactor)
scrollBy(0, direction.value.times(scrollByValue))
}
}
private val actionModeCallback = object : ActionMode.Callback2() {
// Called when the action mode is created; startActionMode() was called
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
actionMode = mode
if (viewModel?.hasTappedExistingHighlight == true) {
Log.d("wv", "inflating existing highlight menu")
mode.menuInflater.inflate(R.menu.highlight_selection_menu, menu)
} else {
viewModel?.showHighlightColorPalette()
mode.menuInflater.inflate(R.menu.text_selection_menu, menu)
}
return true
init {
setOnScrollChangeListener(this)
}
// Called each time the action mode is shown. Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return false // Return false if nothing is done
enum class Direction(val value: Int) {
UP(-1), DOWN(1)
}
// Called when the user selects a contextual menu item
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.annotateHighlight, R.id.annotate -> {
val script = "var event = new Event('annotate');document.dispatchEvent(event);"
evaluateJavascript(script) {
mode.finish()
fun scrollVertically(direction: Direction, heightFactor: Int = 10) {
if (canScrollVertically(direction.value)) {
val scrollByValue = height.div(heightFactor)
scrollBy(0, direction.value.times(scrollByValue))
}
}
private val actionModeCallback = object : ActionMode.Callback2() {
// Called when the action mode is created; startActionMode() was called
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
actionMode = mode
if (viewModel?.hasTappedExistingHighlight == true) {
Log.d("wv", "inflating existing highlight menu")
mode.menuInflater.inflate(R.menu.highlight_selection_menu, menu)
} else {
viewModel?.showHighlightColorPalette()
mode.menuInflater.inflate(R.menu.text_selection_menu, menu)
}
return true
}
// Called each time the action mode is shown. Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return false // Return false if nothing is done
}
// Called when the user selects a contextual menu item
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.annotateHighlight, R.id.annotate -> {
val script = "var event = new Event('annotate');document.dispatchEvent(event);"
evaluateJavascript(script) {
mode.finish()
actionMode = null
}
true
}
R.id.highlight -> {
val script = "var event = new Event('highlight');document.dispatchEvent(event);"
evaluateJavascript(script) {
clearFocus()
mode.finish()
actionMode = null
}
true
}
R.id.copyHighlight -> {
val script =
"var event = new Event('copyHighlight');document.dispatchEvent(event);"
evaluateJavascript(script) {
clearFocus()
mode.finish()
actionMode = null
}
true
}
R.id.copyTextSelection -> {
val script =
"var event = new Event('copyTextSelection');document.dispatchEvent(event);"
evaluateJavascript(script) {
clearFocus()
mode.finish()
actionMode = null
}
true
}
R.id.removeHighlight -> {
val script = "var event = new Event('remove');document.dispatchEvent(event);"
evaluateJavascript(script) {
clearFocus()
mode.finish()
actionMode = null
}
true
}
else -> {
Log.d("Loggo", "${item.itemId} selected")
false
}
}
}
// Called when the user exits the action mode
override fun onDestroyActionMode(mode: ActionMode) {
Log.d("wv", "destroying menu: $mode")
viewModel?.hasTappedExistingHighlight = false
viewModel?.hideHighlightColorPalette()
actionMode = null
}
true
}
R.id.highlight -> {
val script = "var event = new Event('highlight');document.dispatchEvent(event);"
evaluateJavascript(script) {
clearFocus()
mode.finish()
actionMode = null
}
true
override fun onGetContentRect(mode: ActionMode?, view: View?, outRect: Rect?) {
Log.d("wv", "outRect: $outRect, View: $view")
if (viewModel?.lastTapCoordinates != null) {
val xValue = (viewModel!!.lastTapCoordinates!!.tapX * density).toInt()
val yValue = (viewModel!!.lastTapCoordinates!!.tapY * density).toInt()
val rect = Rect(xValue, yValue, xValue, yValue)
Log.d(
"wvt",
"setting rect based on last tapped rect: ${viewModel?.lastTapCoordinates.toString()}"
)
Log.d("wvt", "rect: $rect")
outRect?.set(rect)
} else {
outRect?.set(left, top, right, bottom)
}
}
R.id.copyHighlight -> {
val script = "var event = new Event('copyHighlight');document.dispatchEvent(event);"
evaluateJavascript(script) {
clearFocus()
mode.finish()
actionMode = null
}
true
}
R.id.copyTextSelection -> {
val script = "var event = new Event('copyTextSelection');document.dispatchEvent(event);"
evaluateJavascript(script) {
clearFocus()
mode.finish()
actionMode = null
}
true
}
R.id.removeHighlight -> {
val script = "var event = new Event('remove');document.dispatchEvent(event);"
evaluateJavascript(script) {
clearFocus()
mode.finish()
actionMode = null
}
true
}
else -> {
Log.d("Loggo", "${item.itemId} selected")
false
}
}
}
// Called when the user exits the action mode
override fun onDestroyActionMode(mode: ActionMode) {
Log.d("wv", "destroying menu: $mode")
viewModel?.hasTappedExistingHighlight = false
viewModel?.hideHighlightColorPalette()
actionMode = null
override fun startActionMode(callback: ActionMode.Callback?): ActionMode {
Log.d("wv", "startActionMode:callback called")
return super.startActionMode(actionModeCallback)
}
override fun onGetContentRect(mode: ActionMode?, view: View?, outRect: Rect?) {
Log.d("wv", "outRect: $outRect, View: $view")
if (viewModel?.lastTapCoordinates != null) {
val xValue = (viewModel!!.lastTapCoordinates!!.tapX * density).toInt()
val yValue = (viewModel!!.lastTapCoordinates!!.tapY * density).toInt()
val rect = Rect(xValue, yValue, xValue, yValue)
Log.d("wvt", "setting rect based on last tapped rect: ${viewModel?.lastTapCoordinates.toString()}")
Log.d("wvt", "rect: $rect")
outRect?.set(rect)
} else {
outRect?.set(left, top, right, bottom)
}
override fun startActionModeForChild(
originalView: View?, callback: ActionMode.Callback?
): ActionMode {
Log.d("wv", "startActionMode:originalView:callback called")
return super.startActionModeForChild(originalView, actionModeCallback)
}
}
override fun startActionMode(callback: ActionMode.Callback?): ActionMode {
Log.d("wv", "startActionMode:callback called")
return super.startActionMode(actionModeCallback)
}
override fun startActionMode(callback: ActionMode.Callback?, type: Int): ActionMode {
Log.d("wv", "startActionMode:type called")
return super.startActionMode(actionModeCallback, type)
}
override fun startActionModeForChild(
originalView: View?,
callback: ActionMode.Callback?
): ActionMode {
Log.d("wv", "startActionMode:originalView:callback called")
return super.startActionModeForChild(originalView, actionModeCallback)
}
override fun startActionMode(callback: ActionMode.Callback?, type: Int): ActionMode {
Log.d("wv", "startActionMode:type called")
return super.startActionMode(actionModeCallback, type)
}
override fun onScrollChange(view: View?, x: Int, y: Int, oldX: Int, oldY: Int) {
viewModel?.onScrollChange((oldY - y).toFloat())
}
override fun onScrollChange(view: View?, x: Int, y: Int, oldX: Int, oldY: Int) {
viewModel?.onScrollChange((oldY - y).toFloat())
}
}
class AndroidWebKitMessenger(val messageHandler: (String, String) -> Unit) {
@JavascriptInterface
fun handleIdentifiableMessage(actionID: String, jsonString: String) {
messageHandler(actionID, jsonString)
}
@JavascriptInterface
fun handleIdentifiableMessage(actionID: String, jsonString: String) {
messageHandler(actionID, jsonString)
}
}
data class TapCoordinates(
val tapX: Double,
val tapY: Double
val tapX: Double, val tapY: Double
)
data class HighlightQuote(val quote: String?)