splitted the code base into more components and implemented safety measures to Crypto.qml, also added missing themes and removed hardcoded values

This commit is contained in:
SomeElse
2026-05-17 01:17:01 +00:00
parent c8343398f9
commit 262fd8afa0
26 changed files with 344 additions and 295 deletions

View File

@@ -151,6 +151,16 @@ ModuleChip {
return val.toFixed(2)
}
function safeNumber(val, fallback) {
const n = Number(val)
return (typeof n === "number" && isFinite(n)) ? n : fallback
}
function safeString(val, maxLen, fallback) {
if (typeof val !== "string") return fallback
return val.slice(0, maxLen).replace(/[\x00-\x1f\x7f-\x9f]/g, "")
}
function fetchPrice() {
if (loading) return
loading = true
@@ -164,26 +174,30 @@ ModuleChip {
"&sparkline=false&price_change_percentage=24h"
const xhr = new XMLHttpRequest()
xhr.timeout = 15000
xhr.onreadystatechange = function () {
if (xhr.readyState !== XMLHttpRequest.DONE) return
loading = false
if (xhr.status !== 200) { hasError = true; return }
try {
const data = JSON.parse(xhr.responseText)
if (!data || data.length === 0) { hasError = true; return }
const coin = data[0]
price = coin.current_price ?? -1
change24h = coin.price_change_percentage_24h ?? 0
high24h = coin.high_24h ?? 0
low24h = coin.low_24h ?? 0
marketCap = coin.market_cap ?? 0
volume24h = coin.total_volume ?? 0
rank = coin.market_cap_rank ?? 0
circulatingSupply = coin.circulating_supply ?? 0
ath = coin.ath ?? 0
athChange = coin.ath_change_percentage ?? 0
coinSymbol = (coin.symbol ?? localCoinId).toUpperCase()
coinName = coin.name ?? ""
const text = xhr.responseText
if (text.length > 65536) { hasError = true; return }
const data = JSON.parse(text)
if (!Array.isArray(data) || data.length === 0) { hasError = true; return }
const coin = data[0]
if (typeof coin !== "object" || coin === null) { hasError = true; return }
price = safeNumber(coin.current_price, -1)
change24h = safeNumber(coin.price_change_percentage_24h, 0)
high24h = safeNumber(coin.high_24h, 0)
low24h = safeNumber(coin.low_24h, 0)
marketCap = safeNumber(coin.market_cap, 0)
volume24h = safeNumber(coin.total_volume, 0)
rank = Math.trunc(safeNumber(coin.market_cap_rank, 0))
circulatingSupply = safeNumber(coin.circulating_supply, 0)
ath = safeNumber(coin.ath, 0)
athChange = safeNumber(coin.ath_change_percentage, 0)
coinSymbol = safeString(coin.symbol, 8, localCoinId).toUpperCase()
coinName = safeString(coin.name, 50, "")
lastUpdated = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss")
root.displayCurrency = root.localActiveCurrency
root.activeCoinIndex = (root.activeCoinIndex + 1) % Math.max(1, root.localPairs.length)
@@ -282,24 +296,13 @@ ModuleChip {
_doOpen()
}
Rectangle {
PopupCard {
id: card
width: parent.width
popupOpen: cryptoPopup.popupOpen
isTop: root.isTop
animEnabled: cryptoPopup.visible
height: cardCol.implicitHeight + 24
anchors.top: root.isTop ? parent.top : undefined
anchors.bottom: root.isTop ? undefined : parent.bottom
transform: Translate {
y: cryptoPopup.popupOpen ? 0 : (root.isTop ? -card.height : card.height)
Behavior on y {
NumberAnimation { duration: Cfg.Config.popupAnimDuration; easing.type: Easing.OutExpo }
enabled: cryptoPopup.visible
}
}
opacity: cryptoPopup.popupOpen ? 1.0 : 0.0
Behavior on opacity { NumberAnimation { duration: 200 } }
Behavior on height { NumberAnimation { duration: 220; easing.type: Easing.OutCubic } }
color: t.cryptoPopupBg ?? t.calCardBg ?? "#cc0c0e12"
@@ -308,8 +311,6 @@ ModuleChip {
border.width: Cfg.Config.popupBorderWidth
layer.enabled: true
MouseArea { anchors.fill: parent; propagateComposedEvents: false }
property int activeTab: 0
ColumnLayout {
@@ -319,11 +320,11 @@ ModuleChip {
anchors.topMargin: 16
spacing: 10
Rectangle {
Layout.fillWidth: true
height: 30
radius: 10
color: "#15ffffff"
Rectangle {
Layout.fillWidth: true
height: 30
radius: 10
color: t.cryptoFieldBg ?? "#15ffffff"
RowLayout {
anchors.fill: parent
@@ -340,13 +341,13 @@ ModuleChip {
readonly property bool active: card.activeTab === tabIndex
color: active
? (t.accent ?? "#ffc19375")
: (tm.containsMouse ? "#22ffffff" : "transparent")
: (tm.containsMouse ? (t.cryptoFieldBgFocus ?? "#22ffffff") : "transparent")
Behavior on color { ColorAnimation { duration: 130 } }
Text {
anchors.centerIn: parent
text: parent.label
color: parent.active ? "#ff0c0e12" : (t.cryptoPopupDim ?? t.textDim ?? "#997a8996")
color: parent.active ? (t.cryptoTextOnAccent ?? "#ff0c0e12") : (t.cryptoPopupDim ?? t.textDim ?? "#997a8996")
font.pixelSize: 11
font.bold: parent.active
Behavior on color { ColorAnimation { duration: 130 } }
@@ -412,7 +413,7 @@ ModuleChip {
Rectangle {
width: badge.implicitWidth + 12
height: 22; radius: 11
color: root.change24h >= 0 ? "#22b4c9a1" : "#22f38ba8"
color: root.change24h >= 0 ? (t.cryptoUpBg ?? "#22b4c9a1") : (t.cryptoDownBg ?? "#22f38ba8")
Text {
id: badge
@@ -495,7 +496,7 @@ ModuleChip {
Rectangle {
anchors.fill: parent
radius: 3
color: "#1affffff"
color: t.cryptoRangeTrack ?? "#1affffff"
}
Rectangle {
@@ -548,7 +549,7 @@ ModuleChip {
id: refreshBtn
width: 26; height: 26; radius: 8
color: rMouse.containsMouse
? (rMouse.pressed ? (t.accent ?? "#ffc19375") : "#22ffffff")
? (rMouse.pressed ? (t.accent ?? "#ffc19375") : (t.cryptoFieldBgFocus ?? "#22ffffff"))
: "transparent"
Behavior on color { ColorAnimation { duration: 120 } }
@@ -608,7 +609,7 @@ ModuleChip {
Layout.fillWidth: true
height: 30
radius: 8
color: fi.activeFocus ? "#22ffffff" : "#15ffffff"
color: fi.activeFocus ? (t.cryptoFieldBgFocus ?? "#22ffffff") : (t.cryptoFieldBg ?? "#15ffffff")
border.color: fi.activeFocus ? (t.accent ?? "#ffc19375") : "transparent"
border.width: 1
Behavior on color { ColorAnimation { duration: 120 } }
@@ -626,7 +627,7 @@ ModuleChip {
Text {
anchors.fill: parent
text: parent.parent.parent.placeholder
color: "#55" + (t.textDim ?? "#7a8996").slice(1)
color: t.cryptoPlaceholder ?? "#557a8996"
font.pixelSize: 12
font.family: "monospace"
visible: fi.text === "" && !fi.activeFocus
@@ -648,20 +649,20 @@ ModuleChip {
Rectangle {
width: 24; height: 24; radius: 7
color: mm.containsMouse ? (mm.pressed ? (t.accent ?? "#ffc19375") : "#22ffffff") : "#15ffffff"
color: mm.containsMouse ? (mm.pressed ? (t.accent ?? "#ffc19375") : (t.cryptoFieldBgFocus ?? "#22ffffff")) : (t.cryptoFieldBg ?? "#15ffffff")
Behavior on color { ColorAnimation { duration: 100 } }
Text { anchors.centerIn: parent; text: ""; font.pixelSize: 14; font.bold: true; color: t.cryptoPopupText ?? t.textMain ?? "#ffe8eef2" }
MouseArea { id: mm; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: if (value > minVal) stepped(value - 1) }
}
Rectangle {
width: 54; height: 24; radius: 6; color: "#15ffffff"
width: 54; height: 24; radius: 6; color: t.cryptoFieldBg ?? "#15ffffff"
Text { anchors.centerIn: parent; text: value; color: t.cryptoPopupText ?? t.textMain ?? "#ffe8eef2"; font.pixelSize: 12; font.bold: true; font.family: "monospace" }
}
Rectangle {
width: 24; height: 24; radius: 7
color: pm.containsMouse ? (pm.pressed ? (t.accent ?? "#ffc19375") : "#22ffffff") : "#15ffffff"
color: pm.containsMouse ? (pm.pressed ? (t.accent ?? "#ffc19375") : (t.cryptoFieldBgFocus ?? "#22ffffff")) : (t.cryptoFieldBg ?? "#15ffffff")
Behavior on color { ColorAnimation { duration: 100 } }
Text { anchors.centerIn: parent; text: "+"; font.pixelSize: 14; font.bold: true; color: t.cryptoPopupText ?? t.textMain ?? "#ffe8eef2" }
MouseArea { id: pm; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onClicked: if (value < maxVal) stepped(value + 1) }
@@ -678,7 +679,7 @@ ModuleChip {
Rectangle {
width: 36; height: 20; radius: 10
color: checked ? (t.accent ?? "#ffc19375") : "#22ffffff"
color: checked ? (t.accent ?? "#ffc19375") : (t.cryptoFieldBgFocus ?? "#22ffffff")
Behavior on color { ColorAnimation { duration: 150 } }
Rectangle {
@@ -783,7 +784,7 @@ ModuleChip {
anchors { right: parent.right
verticalCenter: parent.verticalCenter }
width: 14; height: 14; radius: 4
color: rmChipMouse.containsMouse ? "#44f38ba8" : "transparent"
color: rmChipMouse.containsMouse ? (t.cryptoChipRemoveBg ?? "#44f38ba8") : "transparent"
Behavior on color { ColorAnimation { duration: 100 } }
Text {
anchors.centerIn: parent
@@ -817,7 +818,7 @@ ModuleChip {
Rectangle {
anchors.fill: parent
radius: 2
color: "#15ffffff"
color: t.cryptoFieldBg ?? "#15ffffff"
}
Rectangle {
@@ -853,7 +854,7 @@ ModuleChip {
Rectangle {
Layout.fillWidth: true
height: 30; radius: 8
color: addCoinInput.activeFocus ? "#22ffffff" : "#15ffffff"
color: addCoinInput.activeFocus ? (t.cryptoFieldBgFocus ?? "#22ffffff") : (t.cryptoFieldBg ?? "#15ffffff")
border.color: addCoinInput.activeFocus ? (t.accent ?? "#ffc19375") : "transparent"
border.width: 1
Behavior on color { ColorAnimation { duration: 120 } }
@@ -870,7 +871,7 @@ ModuleChip {
Text {
anchors.fill: parent
text: "e.g. bitcoin, monero, solana"
color: "#55" + (t.textDim ?? "#7a8996").slice(1)
color: t.cryptoPlaceholder ?? "#557a8996"
font.pixelSize: 12; font.family: "monospace"
visible: parent.text === "" && !parent.activeFocus
verticalAlignment: Text.AlignVCenter
@@ -899,7 +900,7 @@ ModuleChip {
Rectangle {
Layout.fillWidth: true
height: 30; radius: 8
color: addCurrencyInput.activeFocus ? "#22ffffff" : "#15ffffff"
color: addCurrencyInput.activeFocus ? (t.cryptoFieldBgFocus ?? "#22ffffff") : (t.cryptoFieldBg ?? "#15ffffff")
border.color: addCurrencyInput.activeFocus ? (t.accent ?? "#ffc19375") : "transparent"
border.width: 1
Behavior on color { ColorAnimation { duration: 120 } }
@@ -916,7 +917,7 @@ ModuleChip {
Text {
anchors.fill: parent
text: "e.g. usd, eur, brl"
color: "#55" + (t.textDim ?? "#7a8996").slice(1)
color: t.cryptoPlaceholder ?? "#557a8996"
font.pixelSize: 12; font.family: "monospace"
visible: parent.text === "" && !parent.activeFocus
verticalAlignment: Text.AlignVCenter
@@ -937,8 +938,8 @@ ModuleChip {
Rectangle {
width: 30; height: 30; radius: 8
color: addPairMouse.containsMouse
? (addPairMouse.pressed ? (t.accent ?? "#ffc19375") : "#22ffffff")
: "#15ffffff"
? (addPairMouse.pressed ? (t.accent ?? "#ffc19375") : (t.cryptoFieldBgFocus ?? "#22ffffff"))
: (t.cryptoFieldBg ?? "#15ffffff")
Behavior on color { ColorAnimation { duration: 100 } }
Text {
anchors.centerIn: parent; text: "+"
@@ -985,8 +986,8 @@ ModuleChip {
Rectangle {
width: 24; height: 24; radius: 7
color: decMouse.containsMouse
? (decMouse.pressed ? (t.accent ?? "#ffc19375") : "#22ffffff")
: "#15ffffff"
? (decMouse.pressed ? (t.accent ?? "#ffc19375") : (t.cryptoFieldBgFocus ?? "#22ffffff"))
: (t.cryptoFieldBg ?? "#15ffffff")
Behavior on color { ColorAnimation { duration: 100 } }
Text { anchors.centerIn: parent; text: ""; font.pixelSize: 14; font.bold: true; color: t.cryptoPopupText ?? t.textMain ?? "#ffe8eef2" }
MouseArea {
@@ -998,7 +999,7 @@ ModuleChip {
Rectangle {
width: 54; height: 24; radius: 6
color: refreshInput.activeFocus ? "#22ffffff" : "#15ffffff"
color: refreshInput.activeFocus ? (t.cryptoFieldBgFocus ?? "#22ffffff") : (t.cryptoFieldBg ?? "#15ffffff")
border.color: refreshInput.activeFocus ? (t.accent ?? "#ffc19375") : "transparent"
border.width: 1
Behavior on color { ColorAnimation { duration: 120 } }
@@ -1047,8 +1048,8 @@ ModuleChip {
Rectangle {
width: 24; height: 24; radius: 7
color: incMouse.containsMouse
? (incMouse.pressed ? (t.accent ?? "#ffc19375") : "#22ffffff")
: "#15ffffff"
? (incMouse.pressed ? (t.accent ?? "#ffc19375") : (t.cryptoFieldBgFocus ?? "#22ffffff"))
: (t.cryptoFieldBg ?? "#15ffffff")
Behavior on color { ColorAnimation { duration: 100 } }
Text { anchors.centerIn: parent; text: "+"; font.pixelSize: 14; font.bold: true; color: t.cryptoPopupText ?? t.textMain ?? "#ffe8eef2" }
MouseArea {
@@ -1081,7 +1082,7 @@ ModuleChip {
Text {
anchors.centerIn: parent
text: "Apply & Refresh"
color: applyMouse.containsMouse ? "#ff0c0e12" : (t.cryptoPopupText ?? t.textMain ?? "#ffe8eef2")
color: applyMouse.containsMouse ? (t.cryptoTextOnAccent ?? "#ff0c0e12") : (t.cryptoPopupText ?? t.textMain ?? "#ffe8eef2")
font.pixelSize: 12
font.bold: true
Behavior on color { ColorAnimation { duration: 130 } }