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:
parent
f83a13ba60
commit
89775838bc
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user