Bitcoin Price widget v1.0.0: Plasma 5 applet (EUR via CoinGecko, trend-kleuren, config interval min 5 min)
This commit is contained in:
@@ -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.
|
||||||
@@ -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).
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
|
||||||
|
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
|
||||||
|
<kcfgfile name=""/>
|
||||||
|
<group name="General">
|
||||||
|
<entry name="refreshMinutes" type="Int">
|
||||||
|
<label>Refresh interval in minutes (minimum 5 because of the CoinGecko rate limit)</label>
|
||||||
|
<default>5</default>
|
||||||
|
<min>5</min>
|
||||||
|
<max>1440</max>
|
||||||
|
</entry>
|
||||||
|
</group>
|
||||||
|
</kcfg>
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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):")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user