Don't use Popup for EmojiAction

Popup would leave leftover dead popups forever after screen orientation change
This commit is contained in:
Aleksandras Kostarevas 2024-01-11 22:27:38 +02:00
parent 93ae0c6338
commit fda0052c55

View File

@ -1,7 +1,6 @@
package org.futo.inputmethod.latin.uix.actions package org.futo.inputmethod.latin.uix.actions
import android.content.Context import android.content.Context
import android.graphics.Rect
import android.view.ViewGroup import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import androidx.annotation.UiThread import androidx.annotation.UiThread
@ -9,6 +8,7 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@ -26,26 +26,23 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import androidx.compose.ui.window.PopupProperties
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -102,9 +99,7 @@ class EmojiGridAdapter(
emojiView.isLongClickable = emoji.skinTones emojiView.isLongClickable = emoji.skinTones
if (emoji.skinTones) { if (emoji.skinTones) {
emojiView.setOnLongClickListener { emojiView.setOnLongClickListener {
var rect = Rect() onSelectSkinTone(PopupInfo(emoji, it.x.roundToInt() + it.width / 2, it.y.roundToInt() + it.height / 2))
it.getGlobalVisibleRect(rect)
onSelectSkinTone(PopupInfo(emoji, rect.centerX(), rect.centerY()))
emojiView.isLongClickable emojiView.isLongClickable
} }
} }
@ -167,7 +162,7 @@ fun Emojis(
} }
} }
var activePopup: PopupInfo? by remember { mutableStateOf(null) } var activePopup: PopupInfo? by rememberSaveable { mutableStateOf(null) }
val emojiAdapter = remember { val emojiAdapter = remember {
EmojiGridAdapter( EmojiGridAdapter(
@ -177,59 +172,83 @@ fun Emojis(
emojiWidth emojiWidth
) )
} }
var viewWidth by remember { mutableStateOf(0) } var viewWidth by remember { mutableStateOf(0) }
var viewHeight by remember { mutableStateOf(0) }
activePopup?.let { popupInfo -> Box(modifier = modifier) {
Popup( AndroidView(
onDismissRequest = { factory = { context ->
activePopup = null RecyclerView(context).apply {
}, layoutManager = GridLayoutManager(context, 8)
properties = PopupProperties( adapter = emojiAdapter
clippingEnabled = false
),
popupPositionProvider = object : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize
): IntOffset {
val posX = popupInfo.x - popupContentSize.width / 2
val posY = popupInfo.y - popupContentSize.height
// Calculate the maximum possible x and y values
val maxX = windowSize.width - popupContentSize.width
val maxY = windowSize.height - popupContentSize.height
// Calculate the x and y values, clamping them to the maximum values if necessary
val x = min(maxX, max(0, posX))
val y = min(maxY, max(0, posY))
return IntOffset(x, y)
} }
} },
) { update = {
Surface( if (viewWidth > 0) {
color = MaterialTheme.colorScheme.primaryContainer, (it.layoutManager as GridLayoutManager).spanCount = viewWidth / emojiWidth
shape = RoundedCornerShape(8.dp) }
) { },
Row { modifier = Modifier.fillMaxSize()
generateSkinToneVariants(popupInfo.emoji.emoji, emojiMap).map { emoji -> .clipToBounds()
IconButton(onClick = { .onSizeChanged {
onClick( viewWidth = it.width
EmojiItem( viewHeight = it.height
emoji = emoji, }.pointerInput(Unit) {
description = popupInfo.emoji.description, awaitPointerEventScope {
category = popupInfo.emoji.category, while (true) {
skinTones = false val event = awaitPointerEvent()
) if(event.type == PointerEventType.Press) {
) activePopup = null
activePopup = null }
}, modifier = Modifier }
.width(42.dp) }
.height(42.dp)) { }
Box { )
Text(emoji, modifier = Modifier.align(Alignment.Center))
activePopup?.let { popupInfo ->
var popupSize by remember { mutableStateOf(IntSize(0, 0)) }
val posX = popupInfo.x - popupSize.width / 2
val posY = popupInfo.y - popupSize.height
// Calculate the maximum possible x and y values
val maxX = viewWidth - popupSize.width
val maxY = viewHeight - popupSize.height
// Calculate the x and y values, clamping them to the maximum values if necessary
val x = min(maxX, max(0, posX))
val y = min(maxY, max(0, posY))
Box(modifier = Modifier.onSizeChanged {
popupSize = it
}.absoluteOffset {
IntOffset(x, y)
}) {
Surface(
color = MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(8.dp)
) {
Row {
generateSkinToneVariants(popupInfo.emoji.emoji, emojiMap).map { emoji ->
IconButton(
onClick = {
onClick(
EmojiItem(
emoji = emoji,
description = popupInfo.emoji.description,
category = popupInfo.emoji.category,
skinTones = false
)
)
activePopup = null
}, modifier = Modifier
.width(42.dp)
.height(42.dp)
) {
Box {
Text(emoji, modifier = Modifier.align(Alignment.Center))
}
} }
} }
} }
@ -237,25 +256,6 @@ fun Emojis(
} }
} }
} }
AndroidView(
factory = { context ->
RecyclerView(context).apply {
layoutManager = GridLayoutManager(context, 8)
adapter = emojiAdapter
}
},
update = {
if (viewWidth > 0) {
(it.layoutManager as GridLayoutManager).spanCount = viewWidth / emojiWidth
}
},
modifier = modifier
.clipToBounds()
.onSizeChanged {
viewWidth = it.width
}
)
} }
@Composable @Composable