193 lines
7 KiB
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 }
|
|
}
|
|
}
|
|
}
|