132 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			132 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout
 | 
						|
from PySide6.QtGui import QPixmap, QPainter, QColor, QBrush
 | 
						|
from PySide6.QtCore import Qt
 | 
						|
from app.core.theme import theme_manager
 | 
						|
 | 
						|
 | 
						|
class MessageBubbleWidget(QWidget):
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        text: str,
 | 
						|
        timestamp: str,
 | 
						|
        is_own: bool,
 | 
						|
        sender_name: str | None = None,
 | 
						|
        avatar_path: str | None = None,
 | 
						|
    ):
 | 
						|
        super().__init__()
 | 
						|
        self.is_own = is_own
 | 
						|
        self.sender_name = sender_name
 | 
						|
        self.avatar_path = avatar_path
 | 
						|
        self._build_ui(text, timestamp)
 | 
						|
        self.update_theme()
 | 
						|
        # React to theme changes
 | 
						|
        try:
 | 
						|
            theme_manager.theme_changed.connect(lambda *_: self.update_theme())
 | 
						|
        except Exception:
 | 
						|
            pass
 | 
						|
 | 
						|
    def _build_ui(self, text: str, timestamp: str):
 | 
						|
        # Main row controls left/right alignment
 | 
						|
        main_layout = QHBoxLayout(self)
 | 
						|
        main_layout.setContentsMargins(6, 2, 6, 2)
 | 
						|
        main_layout.setSpacing(8)
 | 
						|
 | 
						|
        # Avatar placeholder (visible for incoming)
 | 
						|
        self.avatar_label = QLabel()
 | 
						|
        self._set_avatar(self.avatar_path)
 | 
						|
 | 
						|
        # Stack with optional sender name and the bubble
 | 
						|
        stack_layout = QVBoxLayout()
 | 
						|
        stack_layout.setSpacing(2)
 | 
						|
        stack_layout.setContentsMargins(0, 0, 0, 0)
 | 
						|
 | 
						|
        # Sender name only for incoming messages (if provided)
 | 
						|
        self.name_label = QLabel(self.sender_name or "")
 | 
						|
        self.name_label.setVisible(bool(self.sender_name) and not self.is_own)
 | 
						|
 | 
						|
        # Bubble frame holds text and timestamp
 | 
						|
        self.bubble_frame = QWidget()
 | 
						|
        self.bubble_frame.setObjectName("Bubble")
 | 
						|
        bubble_layout = QVBoxLayout(self.bubble_frame)
 | 
						|
        bubble_layout.setContentsMargins(12, 8, 12, 6)
 | 
						|
        bubble_layout.setSpacing(3)
 | 
						|
 | 
						|
        self.text_label = QLabel(text)
 | 
						|
        self.text_label.setWordWrap(True)
 | 
						|
 | 
						|
        self.timestamp_label = QLabel(timestamp)
 | 
						|
 | 
						|
        bubble_layout.addWidget(self.text_label)
 | 
						|
        bubble_layout.addWidget(self.timestamp_label, 0, Qt.AlignRight if self.is_own else Qt.AlignLeft)
 | 
						|
 | 
						|
        stack_layout.addWidget(self.name_label)
 | 
						|
        stack_layout.addWidget(self.bubble_frame)
 | 
						|
 | 
						|
        # Arrange horizontally based on ownership: right for own, left for others
 | 
						|
        if self.is_own:
 | 
						|
            main_layout.addStretch(1)
 | 
						|
            main_layout.addLayout(stack_layout)
 | 
						|
            # Reserve place for avatar on the right, hidden for own
 | 
						|
            self.avatar_label.setVisible(False)
 | 
						|
            main_layout.addWidget(self.avatar_label)
 | 
						|
        else:
 | 
						|
            main_layout.addWidget(self.avatar_label)
 | 
						|
            main_layout.addLayout(stack_layout)
 | 
						|
            main_layout.addStretch(1)
 | 
						|
 | 
						|
        # Limit bubble width for readability
 | 
						|
        self.bubble_frame.setMaximumWidth(420)
 | 
						|
 | 
						|
    def _set_avatar(self, image_path: str | None):
 | 
						|
        size = 32
 | 
						|
        if image_path:
 | 
						|
            pixmap = QPixmap(image_path)
 | 
						|
            if pixmap.isNull():
 | 
						|
                pixmap = QPixmap(size, size)
 | 
						|
        else:
 | 
						|
            pixmap = QPixmap(size, size)
 | 
						|
 | 
						|
        if pixmap.width() != size or pixmap.height() != size:
 | 
						|
            pixmap = pixmap.scaled(size, size, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
 | 
						|
 | 
						|
        # Draw a simple circular placeholder if no image
 | 
						|
        if not image_path:
 | 
						|
            pixmap.fill(Qt.transparent)
 | 
						|
            painter = QPainter(pixmap)
 | 
						|
            painter.setRenderHint(QPainter.Antialiasing)
 | 
						|
            painter.setBrush(QBrush(QColor("#e0e0e0")))
 | 
						|
            painter.setPen(Qt.NoPen)
 | 
						|
            painter.drawEllipse(0, 0, size, size)
 | 
						|
            painter.end()
 | 
						|
 | 
						|
        self.avatar_label.setPixmap(pixmap)
 | 
						|
        self.avatar_label.setFixedSize(size, size)
 | 
						|
        self.avatar_label.setScaledContents(True)
 | 
						|
 | 
						|
    def update_theme(self):
 | 
						|
        palette = theme_manager.get_current_palette()
 | 
						|
 | 
						|
        if self.is_own:
 | 
						|
            bg_color = palette["accent"]
 | 
						|
            text_color = "#ffffff"
 | 
						|
            timestamp_color = "#e6e6e6"
 | 
						|
            name_color = palette["text_secondary"]
 | 
						|
            ts_align = Qt.AlignRight
 | 
						|
        else:
 | 
						|
            bg_color = palette["secondary"]
 | 
						|
            text_color = palette["text"]
 | 
						|
            timestamp_color = palette["text_secondary"]
 | 
						|
            name_color = palette["text_secondary"]
 | 
						|
            ts_align = Qt.AlignLeft
 | 
						|
 | 
						|
        # Style only the bubble frame
 | 
						|
        self.bubble_frame.setStyleSheet(
 | 
						|
            f"#Bubble {{ background-color: {bg_color}; border-radius: 10px; }}"
 | 
						|
        )
 | 
						|
        self.text_label.setStyleSheet(f"color: {text_color}; font-size: 10pt;")
 | 
						|
        self.timestamp_label.setStyleSheet(f"color: {timestamp_color}; font-size: 8pt;")
 | 
						|
        self.name_label.setStyleSheet(f"color: {name_color}; font-size: 8pt; font-weight: 600;")
 | 
						|
 | 
						|
        # Keep timestamp alignment in sync with side
 | 
						|
        self.timestamp_label.setAlignment(ts_align)
 |