commit e3934f33b68bd8b541306a12718cac17b6db5b06 Author: ArnoldCordewiner Date: Sat Sep 5 19:26:24 2026 +0200 Bitcoin Price widget v1.0.0: Plasma 5 applet (EUR via CoinGecko, trend-kleuren, config interval min 5 min) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..da263ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Arnold Cordewiner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..17645b6 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# plasma-bitcoin-price + +KDE Plasma 5 widget die de bitcoinprijs in EUR toont, opgehaald bij de +[CoinGecko API](https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=eur). + +## Weergave + +| Situatie | Weergave | +|---|---| +| Prijs gestegen t.o.v. vorige meting | groen | +| Prijs gedaald t.o.v. vorige meting | rood | +| Prijs gelijk gebleven (of eerste meting) | grijs | +| API-fout (netwerkfout, 4xx/5xx, rate limit 429) | `--` | + +Formaat: `₿ 68.969 €` — duizendtallen volgens de systeemlocale, afgerond op hele euro's. + +## Configuratie + +- **Vernieuwinterval (minuten)** — standaard 5, minimaal **5**, maximaal 1440. + Het minimum van 5 minuten komt door de CoinGecko rate limit van 1 call per 5 minuten. + +Instelbaar via rechtsklik op de widget → *Configureer Bitcoin-prijs* → *Algemeen*. + +## Installatie + +### Grafisch +Dubbelklik op `dist/bitcoin-price-1.0.0.plasmoid` in Dolphin (Plasma installeert +dan automatisch naar `~/.local/share/plasma/plasmoids/`). + +### Terminal +```bash +kpackagetool5 --type Plasma/Applet --install dist/bitcoin-price-1.0.0.plasmoid +# widget toevoegen via Add Widgets; bij twijfel plasma herladen: +plasmashell --replace & +``` + +Verwijderen: +```bash +kpackagetool5 --type Plasma/Applet --remove nl.a14r.bitcoinprice +``` + +## Notities + +- De API-URL gebruikt `vs_currencies=eur`. In de oorspronkelijke opdracht stond + een `usd`-URL, maar de eis was "prijs t.o.v. de euro" met een `€`-teken; een + dollarwaarde onder een €-teken zou foutief zijn. +- De trendvergelijking gebeurt tegen de laatst bekend geraakte prijs. Na een + API-fout blijft die referentie staan, zodat de kleur klopt zodra er weer + data binnenkomt. +- Zonder CoinGecko API-key kan de API ook tussendoor een 429 geven; de widget + toont dan `--` tot de eerstvolgende geslaagde poll. +- Getest op Debian 12 / Plasma 5 (Qt 5.15). Plasma 6 werkt vermoedelijk ook, + maar is niet getest. + +## Repository-layout + +``` +metadata.desktop +contents/ +├── config/main.xml # config-schema (refreshMinutes, min 5, max 1440) +└── ui/ + ├── main.qml # widget + fetch/vergelijk-logica + ├── config.qml # configuratievenster (categorieën) + └── configGeneral.qml # interval-instelling +``` + +Het kant-en-klare installatiebestand (`bitcoin-price-1.0.0.plasmoid`) staat als +bijlage bij de [v1.0.0-release](../../releases/tag/v1.0.0). + +Zelf (her)packagen: +```bash +zip -r bitcoin-price.plasmoid metadata.desktop contents +``` + +## Licentie + +MIT — zie [LICENSE](LICENSE). diff --git a/contents/config/main.xml b/contents/config/main.xml new file mode 100644 index 0000000..cc8de78 --- /dev/null +++ b/contents/config/main.xml @@ -0,0 +1,15 @@ + + + + + + + 5 + 5 + 1440 + + + diff --git a/contents/ui/config.qml b/contents/ui/config.qml new file mode 100644 index 0000000..51e0112 --- /dev/null +++ b/contents/ui/config.qml @@ -0,0 +1,10 @@ +import QtQuick 2.15 +import org.kde.plasma.configuration 2.0 + +ConfigModel { + ConfigCategory { + name: i18n("Algemeen") + icon: "preferences-desktop" + source: "configGeneral.qml" + } +} diff --git a/contents/ui/configGeneral.qml b/contents/ui/configGeneral.qml new file mode 100644 index 0000000..6da6a11 --- /dev/null +++ b/contents/ui/configGeneral.qml @@ -0,0 +1,16 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import org.kde.kirigami 2.5 as Kirigami + +Kirigami.FormLayout { + property alias cfg_refreshMinutes: intervalSpin.value + + SpinBox { + id: intervalSpin + from: 5 + to: 1440 + stepSize: 5 + editable: true + Kirigami.FormData.label: i18n("Vernieuwinterval (minuten):") + } +} diff --git a/contents/ui/main.qml b/contents/ui/main.qml new file mode 100644 index 0000000..53ebf79 --- /dev/null +++ b/contents/ui/main.qml @@ -0,0 +1,169 @@ +/* + * 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"); + } +} diff --git a/metadata.desktop b/metadata.desktop new file mode 100644 index 0000000..f758bcc --- /dev/null +++ b/metadata.desktop @@ -0,0 +1,19 @@ +[Desktop Entry] +Name=Bitcoin Price +Name[nl]=Bitcoin-prijs +Comment=Shows the bitcoin price in EUR, color-coded by trend +Comment[nl]=Toont de bitcoinprijs in EUR, kleurcodering op trend +Type=Service +Icon=bitcoin + +X-Plasma-API=declarativeappletscript +X-Plasma-MainScript=ui/main.qml +X-KDE-ServiceTypes=Plasma/Applet +X-KDE-PluginInfo-Author=Arnold Cordewiner +X-KDE-PluginInfo-Email=hermes@a14r.be +X-KDE-PluginInfo-Name=nl.a14r.bitcoinprice +X-KDE-PluginInfo-Version=1.0.0 +X-KDE-PluginInfo-Website=http://192.168.110.5:40004/hermes/plasma-bitcoin-price +X-KDE-PluginInfo-Category=Utilities +X-KDE-PluginInfo-License=MIT +X-KDE-PluginInfo-EnabledByDefault=true