fix(chat): gedrückte Bubble scharf über Blur + Emoji-Ring entfernt

- MessageActionMenu: scharfe Preview-Kopie der gedrückten Bubble am Anker
  (bleibt über dem Blur sichtbar, WhatsApp-Stil) statt mitgeblurrt
- Reaktions-Leiste: kein Ring/Hintergrund mehr, aktives Emoji nur leicht größer
- Reaction-Pills: plain Emoji + Count ohne Hintergrund/Border

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-05-30 11:44:41 +02:00
parent f83a13ba60
commit 89775838bc
2 changed files with 60 additions and 30 deletions

View File

@ -131,6 +131,26 @@ export function ChatBubble({
function copyContent() {
if (msg.content) Clipboard.setStringAsync(msg.content);
}
// Scharfe Kopie der Bubble fürs Kontextmenü (bleibt über dem Blur sichtbar).
const previewNode = (
<View
style={[
styles.bubble,
msg.isOwn ? ownBubbleRadius : otherBubbleRadius,
{ backgroundColor: bubbleBg },
!msg.isOwn && styles.bubbleOtherBorder,
isImageOnly && { padding: 4 },
]}
>
{msg.attachmentUrl && msg.attachmentType === 'image' ? (
<Image source={{ uri: msg.attachmentUrl }} style={styles.image} contentFit="cover" />
) : msg.content !== '' ? (
<Text style={[styles.content, { color: bubbleText }]}>{msg.content}</Text>
) : null}
</View>
);
return (
<>
<View
@ -350,16 +370,10 @@ export function ChatBubble({
<TouchableOpacity
key={r.emoji}
onPress={() => onReact?.(msg, r.emoji)}
activeOpacity={0.7}
style={[
styles.reactionPill,
{
backgroundColor: colors.surfaceElevated,
borderColor: r.mine ? colors.brandOrange : colors.border,
},
]}
activeOpacity={0.6}
style={styles.reactionPill}
>
<Text style={{ fontSize: 13 }}>{r.emoji}</Text>
<Text style={{ fontSize: 16 }}>{r.emoji}</Text>
{r.count > 1 && <Text style={styles.reactionPillCount}>{r.count}</Text>}
</TouchableOpacity>
))}
@ -374,6 +388,7 @@ export function ChatBubble({
isOwn={msg.isOwn}
hasContent={hasContent}
myReaction={myReaction}
preview={previewNode}
onClose={() => setMenuVisible(false)}
onReact={(emoji) => onReact?.(msg, emoji)}
onReply={() => onReply(msg)}
@ -405,10 +420,7 @@ function makeStyles(colors: ReturnType<typeof useColors>) {
reactionPill: {
flexDirection: 'row',
alignItems: 'center',
borderRadius: 12,
borderWidth: StyleSheet.hairlineWidth,
paddingHorizontal: 7,
paddingVertical: 2,
paddingHorizontal: 2,
},
reactionPillCount: {
fontSize: 11,

View File

@ -8,7 +8,7 @@
* Blur-Backdrop (iOS) / semi-transparent (Android). Smart-Position: Menü unter
* der Bubble, oder darüber wenn unten kein Platz ist.
*/
import { useMemo } from 'react';
import { useMemo, type ReactNode } from 'react';
import {
Dimensions,
Modal,
@ -37,6 +37,8 @@ type Props = {
hasContent: boolean;
/** Aktuelles eigenes Reaktions-Emoji auf dieser Message (für Highlight). */
myReaction?: string | null;
/** Scharfe Kopie der gedrückten Bubble — bleibt über dem Blur sichtbar (WA-Stil). */
preview?: ReactNode;
onClose: () => void;
onReact: (emoji: string) => void;
onReply: () => void;
@ -50,6 +52,7 @@ export function MessageActionMenu({
isOwn,
hasContent,
myReaction,
preview,
onClose,
onReact,
onReply,
@ -117,25 +120,39 @@ export function MessageActionMenu({
<View style={[StyleSheet.absoluteFill, { backgroundColor: 'rgba(0,0,0,0.28)' }]} />
)}
{/* Scharfe Kopie der gedrückten Bubble — bleibt über dem Blur sichtbar (WA-Stil) */}
{preview && (
<View
pointerEvents="none"
style={[
{ position: 'absolute', top: anchor.y, width: anchor.width },
isOwn
? { right: Math.max(12, screenW - (anchor.x + anchor.width)) }
: { left: Math.max(12, anchor.x) },
]}
>
{preview}
</View>
)}
{/* Emoji-Reaktions-Leiste (nur fremde Nachrichten) */}
{showReactions && (
<View style={[styles.reactionBar, sideStyle, { top: barTop, backgroundColor: colors.surface }]}>
{REACTION_EMOJIS.map((emoji) => {
const active = myReaction === emoji;
return (
{REACTION_EMOJIS.map((emoji) => (
<TouchableOpacity
key={emoji}
activeOpacity={0.6}
activeOpacity={0.5}
onPress={() => {
onReact(emoji);
onClose();
}}
style={[styles.reactionBtn, active && { backgroundColor: colors.surfaceElevated }]}
style={styles.reactionBtn}
>
<Text style={styles.reactionEmoji}>{emoji}</Text>
<Text style={[styles.reactionEmoji, myReaction === emoji && styles.reactionEmojiActive]}>
{emoji}
</Text>
</TouchableOpacity>
);
})}
))}
</View>
)}
@ -194,6 +211,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
reactionEmoji: { fontSize: 26 },
reactionEmojiActive: { fontSize: 30 },
menu: {
position: 'absolute',
minWidth: 200,