feat: checkpoint QML view migration

This commit is contained in:
dela
2026-07-15 11:57:34 +08:00
parent 3002adb7bf
commit b117bbf99e
133 changed files with 23964 additions and 17074 deletions
+179
View File
@@ -0,0 +1,179 @@
import QtQuick
import QtQuick.Layouts
import qEmby 1.0
Item {
id: root
property int defaultDuration: 4000
property int maximumVisible: 5
property int nextToastId: 1
function show(message, kind, duration) {
const normalizedKind = ["info", "success", "warning", "error"].indexOf(kind) >= 0 ? kind : "info";
const timeout = duration === undefined ? defaultDuration : Math.max(0, duration);
const toastId = nextToastId++;
toastModel.append({
"toastId": toastId,
"message": String(message),
"kind": normalizedKind,
"timeoutMs": timeout
});
while (toastModel.count > maximumVisible)toastModel.remove(0)
return toastId;
}
function info(message, duration) {
return show(message, "info", duration);
}
function success(message, duration) {
return show(message, "success", duration);
}
function warning(message, duration) {
return show(message, "warning", duration);
}
function error(message, duration) {
return show(message, "error", duration);
}
function dismiss(toastId) {
for (let i = 0; i < toastModel.count; ++i) {
if (toastModel.get(i).toastId === toastId) {
toastModel.remove(i);
return ;
}
}
}
function clear() {
toastModel.clear();
}
ListModel {
id: toastModel
}
ListView {
id: toastView
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: Theme.spacingLg
anchors.rightMargin: Theme.spacingLg
width: Math.min(360, Math.max(240, root.width - Theme.spacingXl * 2))
height: Math.min(contentHeight, Math.max(0, root.height - Theme.spacingXl * 2))
spacing: Theme.spacingSm
interactive: contentHeight > height
clip: true
model: toastModel
z: 1000
add: Transition {
NumberAnimation {
properties: "opacity,x"
from: 0
duration: Theme.animationFast
easing.type: Easing.OutCubic
}
}
remove: Transition {
NumberAnimation {
property: "opacity"
to: 0
duration: Theme.animationFast
}
}
displaced: Transition {
NumberAnimation {
properties: "x,y"
duration: Theme.animationFast
easing.type: Easing.OutCubic
}
}
delegate: Rectangle {
id: toast
required property int toastId
required property string message
required property string kind
required property int timeoutMs
readonly property color statusColor: kind === "success" ? Theme.success : kind === "warning" ? Theme.warning : kind === "error" ? Theme.danger : Theme.accent
readonly property string statusIcon: kind === "success" ? "✓" : kind === "warning" ? "!" : kind === "error" ? "×" : "i"
readonly property string statusLabel: kind === "success" ? qsTr("Success") : kind === "warning" ? qsTr("Warning") : kind === "error" ? qsTr("Error") : qsTr("Information")
width: toastView.width
implicitHeight: toastLayout.implicitHeight + Theme.spacingMd * 2
radius: Theme.radiusLg
color: Theme.bgSurface
border.width: 1
border.color: Theme.alpha(statusColor, 0.72)
Accessible.role: Accessible.AlertMessage
Accessible.name: qsTr("%1: %2").arg(statusLabel).arg(message)
RowLayout {
id: toastLayout
anchors.fill: parent
anchors.margins: Theme.spacingMd
spacing: Theme.spacingMd
Rectangle {
color: toast.statusColor
radius: width / 2
Layout.preferredWidth: 24
Layout.preferredHeight: 24
Layout.alignment: Qt.AlignTop
Text {
anchors.centerIn: parent
text: toast.statusIcon
color: "#FFFFFF"
font.pixelSize: Theme.fontBody
font.weight: Font.Bold
}
}
Text {
text: toast.message
color: Theme.textBody
font.pixelSize: Theme.fontBody
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
ThemedButton {
text: "×"
implicitWidth: 28
implicitHeight: 28
leftPadding: 0
rightPadding: 0
Accessible.name: qsTr("Dismiss notification")
Layout.alignment: Qt.AlignTop
onClicked: root.dismiss(toast.toastId)
}
}
Timer {
interval: toast.timeoutMs
running: toast.timeoutMs > 0
repeat: false
onTriggered: root.dismiss(toast.toastId)
}
}
}
}