Merge pull request #2874 from remychantenay/android-highlight-color-palette
Android: Add Highlight color palette for web reader
This commit is contained in:
@ -12,7 +12,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
suspend fun DataService.createWebHighlight(jsonString: String) {
|
suspend fun DataService.createWebHighlight(jsonString: String, colorName: String?) {
|
||||||
val createHighlightInput = Gson().fromJson(jsonString, CreateHighlightParams::class.java).asCreateHighlightInput()
|
val createHighlightInput = Gson().fromJson(jsonString, CreateHighlightParams::class.java).asCreateHighlightInput()
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
@ -28,7 +28,7 @@ suspend fun DataService.createWebHighlight(jsonString: String) {
|
|||||||
createdAt = null,
|
createdAt = null,
|
||||||
updatedAt = null,
|
updatedAt = null,
|
||||||
createdByMe = false,
|
createdByMe = false,
|
||||||
color = null,
|
color = colorName ?: createHighlightInput.color.getOrNull(),
|
||||||
)
|
)
|
||||||
|
|
||||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
|
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
|
||||||
@ -80,13 +80,13 @@ suspend fun DataService.createNoteHighlight(savedItemId: String, note: String):
|
|||||||
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
|
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
|
||||||
|
|
||||||
val newHighlight = networker.createHighlight(input = CreateHighlightParams(
|
val newHighlight = networker.createHighlight(input = CreateHighlightParams(
|
||||||
type = HighlightType.NOTE,
|
type = HighlightType.NOTE,
|
||||||
articleId = savedItemId,
|
articleId = savedItemId,
|
||||||
id = createHighlightId,
|
id = createHighlightId,
|
||||||
shortId = shortId,
|
shortId = shortId,
|
||||||
quote = null,
|
quote = null,
|
||||||
patch = null,
|
patch = null,
|
||||||
annotation = note,
|
annotation = note,
|
||||||
).asCreateHighlightInput())
|
).asCreateHighlightInput())
|
||||||
|
|
||||||
newHighlight?.let {
|
newHighlight?.let {
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
package app.omnivore.omnivore.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
data class HighlightColor(
|
||||||
|
val name: String = "yellow",
|
||||||
|
val color: Color = Color(0xFFFFD234),
|
||||||
|
)
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import app.omnivore.omnivore.ui.components.HighlightColor
|
||||||
|
import app.omnivore.omnivore.ui.components.HighlightColorPaletteMode
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HighlightColorPalette(
|
||||||
|
mode: HighlightColorPaletteMode = HighlightColorPaletteMode.Light,
|
||||||
|
selectedColorName: String,
|
||||||
|
onColorSelected: (color: HighlightColor) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
color = mode.backgroundColor,
|
||||||
|
shadowElevation = 9.dp
|
||||||
|
) {
|
||||||
|
Row(modifier = Modifier.padding(8.dp, 2.dp, 8.dp, 2.dp)) {
|
||||||
|
HighlightColorPaletteItem(
|
||||||
|
color = HighlightColor(name = "yellow", Color(0xFFFFD234)),
|
||||||
|
isSelected = "yellow" == selectedColorName,
|
||||||
|
onClick = onColorSelected
|
||||||
|
)
|
||||||
|
HighlightColorPaletteItem(
|
||||||
|
color = HighlightColor(name = "red", Color(0xFFFB9A9A)),
|
||||||
|
isSelected = "red" == selectedColorName,
|
||||||
|
onClick = onColorSelected
|
||||||
|
)
|
||||||
|
HighlightColorPaletteItem(
|
||||||
|
color = HighlightColor(name = "green", Color(0xFF55C689)),
|
||||||
|
isSelected = "green" == selectedColorName,
|
||||||
|
onClick = onColorSelected
|
||||||
|
)
|
||||||
|
HighlightColorPaletteItem(
|
||||||
|
color = HighlightColor(name = "blue", Color(0xFF6AB1FF)),
|
||||||
|
isSelected = "blue" == selectedColorName,
|
||||||
|
onClick = onColorSelected
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Check
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import app.omnivore.omnivore.ui.components.HighlightColor
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HighlightColorPaletteItem(
|
||||||
|
color: HighlightColor,
|
||||||
|
isSelected: Boolean,
|
||||||
|
onClick: (color: HighlightColor) -> Unit,
|
||||||
|
modifier: Modifier = Modifier.padding(6.dp)
|
||||||
|
) {
|
||||||
|
Column (
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(color.color)
|
||||||
|
.clickable { onClick(color) }
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (isSelected) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Check,
|
||||||
|
contentDescription = "checkIcon",
|
||||||
|
tint = Color.DarkGray,
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package app.omnivore.omnivore.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
enum class HighlightColorPaletteMode(val backgroundColor: Color) {
|
||||||
|
Light(Color.White),
|
||||||
|
Dark(Color.Black),
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package app.omnivore.omnivore.ui.reader
|
package app.omnivore.omnivore.ui.reader
|
||||||
|
|
||||||
|
import HighlightColorPalette
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
@ -18,8 +19,12 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import app.omnivore.omnivore.R
|
import app.omnivore.omnivore.R
|
||||||
|
import app.omnivore.omnivore.ui.components.HighlightColorPaletteMode
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -40,6 +45,9 @@ fun WebReader(
|
|||||||
|
|
||||||
WebView.setWebContentsDebuggingEnabled(true)
|
WebView.setWebContentsDebuggingEnabled(true)
|
||||||
|
|
||||||
|
val showHighlightColorPalette = webReaderViewModel.showHighlightColorPalette.observeAsState()
|
||||||
|
val highlightColor = webReaderViewModel.highlightColor.observeAsState()
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
AndroidView(factory = {
|
AndroidView(factory = {
|
||||||
OmnivoreWebView(it).apply {
|
OmnivoreWebView(it).apply {
|
||||||
@ -144,6 +152,18 @@ fun WebReader(
|
|||||||
webReaderViewModel.resetJavascriptDispatchQueue()
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,6 +184,7 @@ class OmnivoreWebView(context: Context) : WebView(context), OnScrollChangeListen
|
|||||||
Log.d("wv", "inflating existing highlight menu")
|
Log.d("wv", "inflating existing highlight menu")
|
||||||
mode.menuInflater.inflate(R.menu.highlight_selection_menu, menu)
|
mode.menuInflater.inflate(R.menu.highlight_selection_menu, menu)
|
||||||
} else {
|
} else {
|
||||||
|
viewModel?.showHighlightColorPalette()
|
||||||
mode.menuInflater.inflate(R.menu.text_selection_menu, menu)
|
mode.menuInflater.inflate(R.menu.text_selection_menu, menu)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -233,6 +254,7 @@ class OmnivoreWebView(context: Context) : WebView(context), OnScrollChangeListen
|
|||||||
override fun onDestroyActionMode(mode: ActionMode) {
|
override fun onDestroyActionMode(mode: ActionMode) {
|
||||||
Log.d("wv", "destroying menu: $mode")
|
Log.d("wv", "destroying menu: $mode")
|
||||||
viewModel?.hasTappedExistingHighlight = false
|
viewModel?.hasTappedExistingHighlight = false
|
||||||
|
viewModel?.hideHighlightColorPalette()
|
||||||
actionMode = null
|
actionMode = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import app.omnivore.omnivore.networking.*
|
|||||||
import app.omnivore.omnivore.persistence.entities.SavedItem
|
import app.omnivore.omnivore.persistence.entities.SavedItem
|
||||||
import app.omnivore.omnivore.persistence.entities.SavedItemAndSavedItemLabelCrossRef
|
import app.omnivore.omnivore.persistence.entities.SavedItemAndSavedItemLabelCrossRef
|
||||||
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
|
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
|
||||||
|
import app.omnivore.omnivore.ui.components.HighlightColor
|
||||||
import app.omnivore.omnivore.ui.library.SavedItemAction
|
import app.omnivore.omnivore.ui.library.SavedItemAction
|
||||||
import com.apollographql.apollo3.api.Optional
|
import com.apollographql.apollo3.api.Optional
|
||||||
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
|
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
|
||||||
@ -77,7 +78,10 @@ class WebReaderViewModel @Inject constructor(
|
|||||||
var lastTapCoordinates: TapCoordinates? = null
|
var lastTapCoordinates: TapCoordinates? = null
|
||||||
private var isLoading = false
|
private var isLoading = false
|
||||||
private var slug: String? = null
|
private var slug: String? = null
|
||||||
|
|
||||||
|
val showHighlightColorPalette = MutableLiveData(false)
|
||||||
|
val highlightColor = MutableLiveData(HighlightColor())
|
||||||
|
|
||||||
fun loadItem(slug: String?, requestID: String?) {
|
fun loadItem(slug: String?, requestID: String?) {
|
||||||
this.slug = slug
|
this.slug = slug
|
||||||
if (isLoading || webReaderParamsLiveData.value != null) { return }
|
if (isLoading || webReaderParamsLiveData.value != null) { return }
|
||||||
@ -297,11 +301,30 @@ class WebReaderViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun showHighlightColorPalette() {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
showHighlightColorPalette.postValue(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideHighlightColorPalette() {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
showHighlightColorPalette.postValue(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setHighlightColor(color: HighlightColor) {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
highlightColor.postValue(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleIncomingWebMessage(actionID: String, jsonString: String) {
|
fun handleIncomingWebMessage(actionID: String, jsonString: String) {
|
||||||
when (actionID) {
|
when (actionID) {
|
||||||
"createHighlight" -> {
|
"createHighlight" -> {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataService.createWebHighlight(jsonString)
|
dataService.createWebHighlight(jsonString, highlightColor.value?.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"deleteHighlight" -> {
|
"deleteHighlight" -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user