diff --git a/README.md b/README.md index 17645b6..e83bc70 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,92 @@ -# plasma-bitcoin-price +# Bitcoin Price (Plasma 5 widget) -KDE Plasma 5 widget die de bitcoinprijs in EUR toont, opgehaald bij de +KDE Plasma 5 widget that shows the bitcoin price in EUR, fetched from the [CoinGecko API](https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=eur). -## Weergave +## Display -| Situatie | Weergave | +| Situation | Display | |---|---| -| 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) | `--` | +| Price up compared to the previous reading | green | +| Price down compared to the previous reading | red | +| Price unchanged (or first reading) | grey | +| API failure (network error, 4xx/5xx, rate limit 429) | `--` | -Formaat: `₿ 68.969 €` — duizendtallen volgens de systeemlocale, afgerond op hele euro's. +Format: `68,969 €` (thousands separators follow the system locale, rounded to +whole euros) preceded by the bundled Bitcoin icon in the compact and full +representations. -## Configuratie +## Configuration -- **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. +- **Refresh interval (minutes)** — default 5, minimum **5**, maximum 1440. + The 5-minute floor exists because the CoinGecko API allows one request per + 5 minutes. A hint text in the config dialog explains this. -Instelbaar via rechtsklik op de widget → *Configureer Bitcoin-prijs* → *Algemeen*. +Configurable via right-click on the widget → *Configure Bitcoin Price…* → *General*. -## Installatie +## Installation -### Grafisch -Dubbelklik op `dist/bitcoin-price-1.0.0.plasmoid` in Dolphin (Plasma installeert -dan automatisch naar `~/.local/share/plasma/plasmoids/`). +### Graphical +Double-click `bitcoin-price-1.1.0.plasmoid` in Dolphin (Plasma installs it to +`~/.local/share/plasma/plasmoids/` automatically). The latest build is attached +to the [v1.1.0 release](../../releases/tag/v1.1.0). ### Terminal ```bash -kpackagetool5 --type Plasma/Applet --install dist/bitcoin-price-1.0.0.plasmoid -# widget toevoegen via Add Widgets; bij twijfel plasma herladen: +kpackagetool5 --type Plasma/Applet --upgrade bitcoin-price-1.1.0.plasmoid +# Then restart the shell if needed: plasmashell --replace & ``` -Verwijderen: +Upgrading an already-installed widget requires `--upgrade` (`--install` fails +when the plugin id already exists). + +Uninstall: ```bash kpackagetool5 --type Plasma/Applet --remove nl.a14r.bitcoinprice ``` -## Notities +## Notes -- 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. +- The API URL uses `vs_currencies=eur`. The original request referenced a + `usd` URL, but the requirement was "price versus the euro" with a €-sign; + showing a dollar amount under a € sign would be wrong. +- The trend comparison is made against the last known price. After an API + failure that reference is kept, so the color is correct again as soon as + fresh data arrives. +- Without a CoinGecko API key the API may occasionally return 429; the widget + shows `--` until the next successful poll. +- Tested on Plasma 5.27 / Qt 5.15. Plasma 6 is expected to work but is untested. -## Repository-layout +## Debugging + +The widget logs to the plasmashell journal: + +```bash +journalctl --user -u plasma-plasmashell.service | grep bitcoinprice +``` + +Expected lines: `started, refresh interval: N minutes`, `fetched EUR `, +or `fetch failed, HTTP status: `. + +## Repository layout ``` metadata.desktop contents/ -├── config/main.xml # config-schema (refreshMinutes, min 5, max 1440) +├── config/main.xml # config schema (refreshMinutes, min 5, max 1440) +├── images/bitcoin.svg # bundled Bitcoin icon └── ui/ - ├── main.qml # widget + fetch/vergelijk-logica - ├── config.qml # configuratievenster (categorieën) - └── configGeneral.qml # interval-instelling + ├── main.qml # widget + fetch/compare logic + ├── config.qml # config dialog (categories) + └── configGeneral.qml # interval setting ``` -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: +Repackage yourself: ```bash zip -r bitcoin-price.plasmoid metadata.desktop contents ``` -## Licentie +## License -MIT — zie [LICENSE](LICENSE). +MIT — see [LICENSE](LICENSE). diff --git a/bitcoin-price-1.1.0.plasmoid b/bitcoin-price-1.1.0.plasmoid new file mode 100644 index 0000000..103dcac Binary files /dev/null and b/bitcoin-price-1.1.0.plasmoid differ diff --git a/contents/images/bitcoin.svg b/contents/images/bitcoin.svg new file mode 100644 index 0000000..02a385e --- /dev/null +++ b/contents/images/bitcoin.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/contents/ui/config.qml b/contents/ui/config.qml index 51e0112..f9558d9 100644 --- a/contents/ui/config.qml +++ b/contents/ui/config.qml @@ -3,7 +3,7 @@ import org.kde.plasma.configuration 2.0 ConfigModel { ConfigCategory { - name: i18n("Algemeen") + name: i18n("General") icon: "preferences-desktop" source: "configGeneral.qml" } diff --git a/contents/ui/configGeneral.qml b/contents/ui/configGeneral.qml index 6da6a11..bfa6cee 100644 --- a/contents/ui/configGeneral.qml +++ b/contents/ui/configGeneral.qml @@ -1,16 +1,35 @@ +/* + * Config page for nl.a14r.bitcoinprice. + * Follows the canonical Plasma 5 config dialog pattern: + * Kirigami.FormLayout + cfg_ prefixed property aliases. + */ + import QtQuick 2.15 -import QtQuick.Controls 2.15 +import QtQuick.Controls 2.15 as QQC2 import org.kde.kirigami 2.5 as Kirigami Kirigami.FormLayout { + id: generalPage + + // The cfg_ prefix is what Plasma uses to read/write plasmoid.configuration property alias cfg_refreshMinutes: intervalSpin.value - SpinBox { + Item { + Kirigami.FormData.isSection: true + } + + QQC2.SpinBox { id: intervalSpin + Kirigami.FormData.label: i18n("Refresh interval (minutes):") from: 5 to: 1440 stepSize: 5 editable: true - Kirigami.FormData.label: i18n("Vernieuwinterval (minuten):") + } + + QQC2.Label { + text: i18n("CoinGecko allows one request per 5 minutes; lower values are not possible.") + font.italic: true + opacity: 0.7 } } diff --git a/contents/ui/main.qml b/contents/ui/main.qml index 53ebf79..c0da2a3 100644 --- a/contents/ui/main.qml +++ b/contents/ui/main.qml @@ -1,14 +1,14 @@ /* * 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) + * Shows the bitcoin price in EUR (CoinGecko API). + * - Green : price up compared to the previous reading + * - Red : price down compared to the previous reading + * - Grey : unchanged (or first reading) + * - "--" : API failure (network error, 4xx/5xx, 429 rate limit) * - * Vernieuwinterval: configureerbaar, minimaal 5 minuten - * (CoinGecko limiet: 1 call per 5 minuten). + * Refresh interval: configurable, minimum 5 minutes + * (CoinGecko limit: 1 call per 5 minutes). */ import QtQuick 2.15 @@ -22,42 +22,47 @@ Item { 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 + // -1 = down, 0 = unchanged / no previous reading yet, 1 = up property int trend: 0 - property double lastPrice: NaN // laatste succesvol opgehaalde prijs + property double lastPrice: NaN // last successfully fetched price property string state: "init" // init | ok | error - property string lastUpdated: "" // HH:mm:ss van laatste succesvolle update + property string lastUpdated: "" // HH:mm:ss of the last successful update readonly property string displayText: { if (state !== "ok" || isNaN(lastPrice)) { return "--"; } - return "\u20BF " + Number(lastPrice).toLocaleString(Qt.locale(), "f", 0) + " \u20AC"; + return Number(lastPrice).toLocaleString(Qt.locale(), "f", 0) + " \u20AC"; } readonly property color priceColor: { if (state !== "ok" || isNaN(lastPrice)) { - return PlasmaCore.Theme.disabledTextColor; // "--" bij fout/init + return PlasmaCore.Theme.disabledTextColor; // "--" on error/init } if (trend > 0) { - return "#2ecc71"; // groen + return "#2ecc71"; // green } if (trend < 0) { - return "#e74c3c"; // rood + return "#e74c3c"; // red } - return "#9e9e9e"; // grijs + return "#9e9e9e"; // grey } readonly property string statusText: { if (state === "error") { return lastUpdated === "" - ? i18n("API-fout") - : i18n("API-fout — laatste geldige prijs: %1", lastUpdated); + ? i18n("API error") + : i18n("API error — last known price: %1", lastUpdated); } if (lastUpdated === "") { - return i18n("Bezig met ophalen\u2026"); + return i18n("Fetching\u2026"); } - return i18n("Laatst bijgewerkt: %1", lastUpdated); + return i18n("Last updated: %1", lastUpdated); + } + + Component.onCompleted: { + console.log("[bitcoinprice] started, refresh interval:", + refreshMinutes, "minutes"); } Plasmoid.backgroundHints: PlasmaCore.Types.StandardBackground @@ -70,22 +75,38 @@ Item { Item { id: compact - Layout.minimumWidth: priceLabel.implicitWidth + PlasmaCore.Units.smallSpacing * 2 + Layout.minimumWidth: row.implicitWidth + PlasmaCore.Units.smallSpacing * 2 Layout.minimumHeight: PlasmaCore.Units.iconSizes.smallMedium - Text { - id: priceLabel + Row { + id: row 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 + spacing: PlasmaCore.Units.smallSpacing + + Image { + id: btcIcon + source: "../images/bitcoin.svg" + sourceSize.width: PlasmaCore.Units.iconSizes.smallMedium + sourceSize.height: PlasmaCore.Units.iconSizes.smallMedium + anchors.verticalCenter: parent.verticalCenter + fillMode: Image.PreserveAspectFit + visible: root.state === "ok" + } + + Text { + id: priceLabel + anchors.verticalCenter: parent.verticalCenter + 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 + } } } } @@ -95,12 +116,21 @@ Item { id: fullRep implicitWidth: PlasmaCore.Units.gridUnit * 14 - implicitHeight: PlasmaCore.Units.gridUnit * 7 + implicitHeight: PlasmaCore.Units.gridUnit * 9 Column { anchors.centerIn: parent spacing: PlasmaCore.Units.smallSpacing + Image { + anchors.horizontalCenter: parent.horizontalCenter + source: "../images/bitcoin.svg" + sourceSize.width: PlasmaCore.Units.iconSizes.large + sourceSize.height: PlasmaCore.Units.iconSizes.large + fillMode: Image.PreserveAspectFit + visible: root.state === "ok" + } + Text { anchors.horizontalCenter: parent.horizontalCenter text: root.displayText @@ -141,14 +171,15 @@ Item { try { var price = JSON.parse(xhr.responseText).bitcoin.eur; if (typeof price === "number" && !isNaN(price)) { + console.log("[bitcoinprice] fetched EUR", price); root.applyPrice(price); return; } } catch (e) { - // ongeldige JSON → val door naar de foutafhandeling + // invalid JSON → fall through to error handling } } - // API-fout (netwerk, 4xx/5xx, rate limit 429, onverwachte payload) + console.log("[bitcoinprice] fetch failed, HTTP status:", xhr.status); root.state = "error"; }; xhr.send(); diff --git a/dist/bitcoin-price-1.0.0.plasmoid b/dist/bitcoin-price-1.0.0.plasmoid new file mode 100644 index 0000000..e87d85d Binary files /dev/null and b/dist/bitcoin-price-1.0.0.plasmoid differ diff --git a/metadata.desktop b/metadata.desktop index f758bcc..cc817c6 100644 --- a/metadata.desktop +++ b/metadata.desktop @@ -1,8 +1,6 @@ [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 @@ -12,8 +10,8 @@ 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-Version=1.1.0 +X-KDE-PluginInfo-Website=https://gitea.a14r.be/hermes/plasma-bitcoin-price X-KDE-PluginInfo-Category=Utilities X-KDE-PluginInfo-License=MIT X-KDE-PluginInfo-EnabledByDefault=true