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:
@@ -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 } }
|
||||
|
||||
Reference in New Issue
Block a user