Files
z-bar-qt/Modules/Notifications/Sidebar/Chat/ChatContent.qml
T
2026-08-26 17:10:34 +02:00

213 lines
4.2 KiB
QML

import QtQuick
import QtQuick.Layouts
import ZShell.Components
import ZShell.Config
import ZShell.Llm
import qs.Modules.Notifications.Sidebar.Chat.Content
import qs.Components
import qs.Services
Item {
id: root
property ChatSession chatData
property bool following: true
signal requestClose
function scrollToBottom(): void {
Qt.callLater(() => listLoader.item?.positionViewAtBeginning());
}
function send(text: string): void {
if (text.trim() === "")
return;
following = true;
chatData.sendMessage(text);
input.text = "";
}
RowLayout {
id: header
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Tokens.spacing.large
IconButton {
icon: "arrow_back"
inactiveColor: Colors.tPalette.m3surfaceContainerHigh
inactiveOnColor: Colors.palette.m3onSurfaceVariant
isRound: true
type: IconButton.Tonal
onClicked: root.requestClose()
}
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Tokens.font.size.larger
text: qsTr(root.chatData.title)
}
}
Loader {
id: listLoader
anchors.bottom: input.top
anchors.bottomMargin: Tokens.spacing.medium
anchors.left: parent.left
anchors.right: parent.right
anchors.top: header.bottom
anchors.topMargin: Tokens.spacing.medium
active: root.chatData && root.chatData.loaded
sourceComponent: ListView {
id: list
property bool userScrolledUp: false
function scrollToBottom(): void {
Qt.callLater(() => positionViewAtBeginning());
}
cacheBuffer: height * 20
clip: true
// fadeAmount: 0.05
// fadeThreshold: Tokens.padding.medium
model: root.chatData.messagesModel
spacing: 0
rotation: 180
// add: Transition {
// Anim {
// from: 10
// property: "y"
// }
// }
delegate: MessageDelegate {
rotation: 180
}
// displaced: Transition {
// Anim {
// property: "y"
// }
// }
// move: Transition {
// Anim {
// property: "y"
// }
// }
Component.onCompleted: {
positionViewAtBeginning();
forceActiveFocus();
}
onAtYEndChanged: {
if (atYEnd)
userScrolledUp = false;
}
onContentHeightChanged: {
if (!userScrolledUp && atYEnd)
scrollToBottom();
}
onCountChanged: {
if (!userScrolledUp)
scrollToBottom();
}
onMovingChanged: {
if (moving)
userScrolledUp = !atYEnd;
}
Anim {
id: scrollAnim
property: "contentY"
target: list
type: Anim.DefaultEffects
}
WheelInverter {
target: list
}
}
// Trigger the load even before we're ready to show the list.
Component.onCompleted: if (root.chatData)
root.chatData.messagesModel
}
EmptyBackground {
id: emptyState
anchors.fill: parent
spacing: Tokens.spacing.small
visible: !root.chatData
}
Item {
anchors.bottom: input.top
anchors.bottomMargin: Tokens.spacing.extraLarge
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: scrollToBottom.implicitHeight
implicitWidth: scrollToBottom.implicitWidth
scale: !listLoader.item.atYBeginning && listLoader.item.contentHeight > listLoader.item.height ? 1 : 0
visible: scale > 0
Behavior on scale {
Anim {}
}
Elevation {
anchors.fill: parent
level: 2
radius: scrollToBottom.radius
}
IconButton {
id: scrollToBottom
anchors.centerIn: parent
font.pointSize: Tokens.font.size.large
icon: "arrow_downward"
isRound: true
padding: Tokens.padding.extraSmall
type: IconButton.Tonal
onClicked: {
listLoader.item.positionViewAtBeginning();
}
}
}
ChatInput {
id: input
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
bg.border.color: Colors.palette.m3outlineVariant
bg.color: Colors.tPalette.m3surfaceContainerLowest
font.pointSize: Tokens.font.size.normal
implicitHeight: Math.min(root.height / 5, contentHeight + topPadding + bottomPadding)
placeholderText: qsTr("Send a message")
sendIcon.font.pointSize: Tokens.font.size.large
sendIcon.icon: "arrow_upward"
sendIcon.padding: Tokens.padding.extraSmall
Keys.onPressed: e => {
if (e.key == Qt.Key_Return) {
if (!(e.modifiers & Qt.ShiftModifier)) {
root.send(text);
e.accepted = true;
}
}
}
onSendPressed: root.send(text)
}
}