import QtQuick import QtQuick.Layouts import Caelestia.Config import Caelestia.Internal import qs.components import qs.components.misc import qs.services StyledRect { id: root color: Colours.tPalette.m3surfaceContainer radius: Tokens.rounding.extraLarge implicitWidth: Tokens.sizes.dashboard.perfNetworkCardWidth implicitHeight: Tokens.sizes.dashboard.perfNetworkCardHeight Ref { service: NetworkUsage } ColumnLayout { id: layout anchors.fill: parent anchors.margins: Tokens.padding.large anchors.bottomMargin: Tokens.padding.medium spacing: 0 RowLayout { spacing: Tokens.spacing.small NsIcon { icon: "swap_vert" color: Colours.palette.m3primary fontStyle: Tokens.font.icon.medium } StyledText { text: qsTr("Network") font: Tokens.font.title.medium } } // Sparkline graph Item { Layout.topMargin: Tokens.spacing.medium Layout.bottomMargin: Tokens.spacing.small Layout.fillWidth: true Layout.fillHeight: true SparklineItem { id: sparkline property real targetMax: 1024 property real smoothMax: targetMax anchors.fill: parent line1: NetworkUsage.uploadBuffer // qmllint disable missing-type line1Color: Colours.palette.m3secondary line1FillAlpha: 0.15 line2: NetworkUsage.downloadBuffer // qmllint disable missing-type line2Color: Colours.palette.m3tertiary line2FillAlpha: 0.2 maxValue: smoothMax historyLength: NetworkUsage.historyLength Connections { function onValuesChanged(): void { sparkline.targetMax = Math.max(NetworkUsage.downloadBuffer.maximum, NetworkUsage.uploadBuffer.maximum, 1024); slideAnim.restart(); } target: NetworkUsage.downloadBuffer } NumberAnimation { id: slideAnim target: sparkline property: "slideProgress" from: 0 to: 1 easing.type: Easing.Linear duration: GlobalConfig.dashboard.resourceUpdateInterval } Behavior on smoothMax { Anim {} } } // "Collecting data" placeholder StyledText { anchors.centerIn: parent text: qsTr("Collecting data...") font: Tokens.font.body.small color: Colours.palette.m3outline visible: NetworkUsage.downloadBuffer.count < 2 } } // Download row RowLayout { Layout.fillWidth: true spacing: Tokens.spacing.small NsIcon { icon: "download" color: Colours.palette.m3tertiary fontStyle: Tokens.font.icon.medium } StyledText { text: qsTr("Download") font: Tokens.font.body.small color: Colours.palette.m3onSurfaceVariant } Item { Layout.fillWidth: true } StyledText { text: { const fmt = NetworkUsage.formatBytes(NetworkUsage.downloadSpeed ?? 0); return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s"; } font: Tokens.font.body.builders.medium.weight(Font.Medium).build() color: Colours.palette.m3tertiary } } // Upload row RowLayout { Layout.fillWidth: true spacing: Tokens.spacing.small NsIcon { icon: "upload" color: Colours.palette.m3secondary fontStyle: Tokens.font.icon.medium } StyledText { text: qsTr("Upload") font: Tokens.font.body.small color: Colours.palette.m3onSurfaceVariant } Item { Layout.fillWidth: true } StyledText { text: { const fmt = NetworkUsage.formatBytes(NetworkUsage.uploadSpeed ?? 0); return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s"; } font: Tokens.font.body.builders.medium.weight(Font.Medium).build() color: Colours.palette.m3secondary } } // Session totals RowLayout { Layout.fillWidth: true spacing: Tokens.spacing.small NsIcon { icon: "history" color: Colours.palette.m3onSurfaceVariant fontStyle: Tokens.font.icon.medium } StyledText { text: qsTr("Total") font: Tokens.font.body.small color: Colours.palette.m3onSurfaceVariant } Item { Layout.fillWidth: true } StyledText { text: { const down = NetworkUsage.formatBytesTotal(NetworkUsage.downloadTotal ?? 0); const up = NetworkUsage.formatBytesTotal(NetworkUsage.uploadTotal ?? 0); return (down && up) ? `↓${down.value.toFixed(1)}${down.unit} ↑${up.value.toFixed(1)}${up.unit}` : "↓0.0B ↑0.0B"; } font: Tokens.font.body.small color: Colours.palette.m3onSurfaceVariant } } } }