170 lines
5.7 KiB
QML
170 lines
5.7 KiB
QML
/*
|
|
* nl.a14r.bitcoinprice — KDE Plasma 5 widget
|
|
*
|
|
* Toont de bitcoinprijs in EUR (CoinGecko API).
|
|
* - Groen : prijs gestegen t.o.v. vorige meting
|
|
* - Rood : prijs gedaald t.o.v. vorige meting
|
|
* - Grijs : gelijk gebleven (of eerste meting)
|
|
* - "--" : API-fout (netwerkfout, 4xx/5xx, rate limit 429)
|
|
*
|
|
* Vernieuwinterval: configureerbaar, minimaal 5 minuten
|
|
* (CoinGecko limiet: 1 call per 5 minuten).
|
|
*/
|
|
|
|
import QtQuick 2.15
|
|
import QtQuick.Layouts 1.15
|
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
|
import org.kde.plasma.plasmoid 2.0
|
|
|
|
Item {
|
|
id: root
|
|
|
|
readonly property int refreshMinutes: Math.max(5, plasmoid.configuration.refreshMinutes)
|
|
readonly property string apiUrl: "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=eur"
|
|
|
|
// -1 = gedaald, 0 = gelijk / nog geen vorige meting, 1 = gestegen
|
|
property int trend: 0
|
|
property double lastPrice: NaN // laatste succesvol opgehaalde prijs
|
|
property string state: "init" // init | ok | error
|
|
property string lastUpdated: "" // HH:mm:ss van laatste succesvolle update
|
|
|
|
readonly property string displayText: {
|
|
if (state !== "ok" || isNaN(lastPrice)) {
|
|
return "--";
|
|
}
|
|
return "\u20BF " + Number(lastPrice).toLocaleString(Qt.locale(), "f", 0) + " \u20AC";
|
|
}
|
|
|
|
readonly property color priceColor: {
|
|
if (state !== "ok" || isNaN(lastPrice)) {
|
|
return PlasmaCore.Theme.disabledTextColor; // "--" bij fout/init
|
|
}
|
|
if (trend > 0) {
|
|
return "#2ecc71"; // groen
|
|
}
|
|
if (trend < 0) {
|
|
return "#e74c3c"; // rood
|
|
}
|
|
return "#9e9e9e"; // grijs
|
|
}
|
|
|
|
readonly property string statusText: {
|
|
if (state === "error") {
|
|
return lastUpdated === ""
|
|
? i18n("API-fout")
|
|
: i18n("API-fout — laatste geldige prijs: %1", lastUpdated);
|
|
}
|
|
if (lastUpdated === "") {
|
|
return i18n("Bezig met ophalen\u2026");
|
|
}
|
|
return i18n("Laatst bijgewerkt: %1", lastUpdated);
|
|
}
|
|
|
|
Plasmoid.backgroundHints: PlasmaCore.Types.StandardBackground
|
|
Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation
|
|
|
|
Plasmoid.toolTipMainText: "Bitcoin / EUR"
|
|
Plasmoid.toolTipSubText: statusText
|
|
|
|
Plasmoid.compactRepresentation: Component {
|
|
Item {
|
|
id: compact
|
|
|
|
Layout.minimumWidth: priceLabel.implicitWidth + PlasmaCore.Units.smallSpacing * 2
|
|
Layout.minimumHeight: PlasmaCore.Units.iconSizes.smallMedium
|
|
|
|
Text {
|
|
id: priceLabel
|
|
anchors.centerIn: parent
|
|
text: root.displayText
|
|
color: root.priceColor
|
|
textFormat: Text.PlainText
|
|
elide: Text.ElideRight
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
font.family: PlasmaCore.Theme.defaultFont.family
|
|
font.pixelSize: compact.height > PlasmaCore.Units.iconSizes.small
|
|
? Math.round(compact.height * 0.6)
|
|
: PlasmaCore.Theme.defaultFont.pixelSize
|
|
}
|
|
}
|
|
}
|
|
|
|
Plasmoid.fullRepresentation: Component {
|
|
Item {
|
|
id: fullRep
|
|
|
|
implicitWidth: PlasmaCore.Units.gridUnit * 14
|
|
implicitHeight: PlasmaCore.Units.gridUnit * 7
|
|
|
|
Column {
|
|
anchors.centerIn: parent
|
|
spacing: PlasmaCore.Units.smallSpacing
|
|
|
|
Text {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: root.displayText
|
|
color: root.priceColor
|
|
font.family: PlasmaCore.Theme.defaultFont.family
|
|
font.pixelSize: PlasmaCore.Units.gridUnit * 2
|
|
}
|
|
|
|
Text {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: root.statusText
|
|
color: PlasmaCore.Theme.disabledTextColor
|
|
font.family: PlasmaCore.Theme.defaultFont.family
|
|
font.pixelSize: PlasmaCore.Theme.smallestFont.pixelSize
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: refreshTimer
|
|
interval: root.refreshMinutes * 60 * 1000
|
|
running: true
|
|
repeat: true
|
|
triggeredOnStart: true
|
|
onTriggered: root.fetchPrice()
|
|
}
|
|
|
|
function fetchPrice() {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("GET", root.apiUrl);
|
|
xhr.setRequestHeader("Accept", "application/json");
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState !== XMLHttpRequest.DONE) {
|
|
return;
|
|
}
|
|
if (xhr.status === 200) {
|
|
try {
|
|
var price = JSON.parse(xhr.responseText).bitcoin.eur;
|
|
if (typeof price === "number" && !isNaN(price)) {
|
|
root.applyPrice(price);
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
// ongeldige JSON → val door naar de foutafhandeling
|
|
}
|
|
}
|
|
// API-fout (netwerk, 4xx/5xx, rate limit 429, onverwachte payload)
|
|
root.state = "error";
|
|
};
|
|
xhr.send();
|
|
}
|
|
|
|
function applyPrice(price) {
|
|
if (isNaN(root.lastPrice) || price === root.lastPrice) {
|
|
root.trend = 0;
|
|
} else if (price > root.lastPrice) {
|
|
root.trend = 1;
|
|
} else {
|
|
root.trend = -1;
|
|
}
|
|
root.lastPrice = price;
|
|
root.state = "ok";
|
|
root.lastUpdated = Qt.formatDateTime(new Date(), "HH:mm:ss");
|
|
}
|
|
}
|