// SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (C) 2026 Aether Contributors // // This file is part of Aether. // // Aether is free software: you can redistribute it and/or modify it // under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Aether is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public // License for more details. // // You should have received a copy of the GNU Affero General Public License // along with Aether. If not, see . import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Controls.Material 2.15 import QtQuick.Layouts 1.15 import Aether 1.0 /// Scrollable list of chat messages. /// Groups consecutive messages from the same author (compact display). Item { id: root // ── Placeholder message data ────────────────────────────────────────────── ListModel { id: messagesModel ListElement { author: "alice" avatarColor: "#7c6af7" content: "Hey everyone! Welcome to the Aether client. 🎉" timestamp: "Today at 09:00" isFirst: true } ListElement { author: "bob" avatarColor: "#a6e3a1" content: "This looks amazing! Really clean interface." timestamp: "Today at 09:02" isFirst: true } ListElement { author: "bob" avatarColor: "#a6e3a1" content: "Can't wait to connect it to a real Stoat server." timestamp: "Today at 09:02" isFirst: false } ListElement { author: "charlie" avatarColor: "#f38ba8" content: "The dark theme is 🔥. Love the purple accent." timestamp: "Today at 09:05" isFirst: true } ListElement { author: "alice" avatarColor: "#7c6af7" content: "Thanks! Built with Qt 6 + QML + Material. Fully native." timestamp: "Today at 09:07" isFirst: true } ListElement { author: "alice" avatarColor: "#7c6af7" content: "AGPL-3.0 licensed, open source, and self-hostable." timestamp: "Today at 09:07" isFirst: false } ListElement { author: "diana" avatarColor: "#fab387" content: "This is exactly what the self-hosting community needed. 💜" timestamp: "Today at 09:10" isFirst: true } } ListView { id: listView anchors.fill: parent model: messagesModel clip: true topMargin: Theme.sp16 bottomMargin: Theme.sp8 // Auto-scroll to bottom when new messages arrive onCountChanged: Qt.callLater(() => listView.positionViewAtEnd()) ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } delegate: Item { id: msgItem width: listView.width height: msgLayout.implicitHeight + (model.isFirst ? Theme.sp16 : Theme.sp2) readonly property bool isHovered: msgHover.hovered // Hover highlight Rectangle { anchors.fill: parent color: msgItem.isHovered ? Theme.bgHover : "transparent" Behavior on color { ColorAnimation { duration: Theme.animFast } } } RowLayout { id: msgLayout anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.topMargin: model.isFirst ? Theme.sp16 : Theme.sp2 anchors.leftMargin: Theme.sp16 anchors.rightMargin: Theme.sp16 spacing: Theme.sp12 // ── Avatar (only on first message in a group) ───────────────── Item { width: Constants.avatarMd height: Constants.avatarMd Layout.alignment: Qt.AlignTop Rectangle { anchors.fill: parent radius: Constants.avatarMd / 2 color: model.avatarColor visible: model.isFirst Text { anchors.centerIn: parent text: model.author.charAt(0).toUpperCase() font.pixelSize: Theme.font16 font.weight: Font.Bold color: "#ffffff" } } // Hover timestamp for compact messages (no avatar) Text { anchors.centerIn: parent text: model.timestamp.split(" ").slice(-1)[0] // HH:MM font.pixelSize: Theme.font10 color: Theme.textMuted visible: !model.isFirst && msgItem.isHovered } } // ── Message body ────────────────────────────────────────────── ColumnLayout { Layout.fillWidth: true spacing: Theme.sp2 // Author + timestamp (first message only) RowLayout { visible: model.isFirst spacing: Theme.sp8 Text { text: model.author font.pixelSize: Theme.font14 font.weight: Font.DemiBold color: Theme.textPrimary } Text { text: model.timestamp font.pixelSize: Theme.font11 color: Theme.textMuted } } // Message content Text { Layout.fillWidth: true text: model.content font.pixelSize: Theme.font14 color: Theme.textSecondary wrapMode: Text.Wrap lineHeight: 1.375 } } } HoverHandler { id: msgHover } } } }