Aether/src/qml/components/MessageList.qml

193 lines
7 KiB
QML

// 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 <https://www.gnu.org/licenses/>.
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 }
}
}
}