Files

206 lines
7.5 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Services
import qs.DCommon.Widgets
import qs.Modules.DDash
import "../../../DCommon/Common/WheelInput.js" as WheelInput
Item {
id: root
LayoutMirroring.enabled: false
LayoutMirroring.childrenInherit: true
property bool daily: false
property int startIndex: 0
property int visibleCount: daily ? DashMetrics.dailyVisibleCount : DashMetrics.chartHourlyCount
signal pageRequested(int delta)
readonly property var source: (daily ? WeatherService.weather.forecast : WeatherService.weather.hourlyForecast) ?? []
readonly property int maxStart: Math.max(0, source.length - visibleCount)
readonly property int start: Math.max(0, Math.min(maxStart, startIndex))
readonly property var slice: source.slice(start, start + visibleCount)
readonly property var temps: slice.map(f => Number(daily ? f.tempMax : f.temp) || 0)
readonly property var lows: daily ? slice.map(f => Number(f.tempMin) || 0) : []
readonly property int nowIndex: (daily ? 0 : (WeatherService.weather.currentHourIndex ?? new Date().getHours())) - start
readonly property real columnWidth: slice.length > 0 ? (width - Theme.spacingM * 2) / slice.length : 0
readonly property real chartTop: Theme.spacingM + header.height + Theme.spacingS
readonly property real chartBottom: height - Theme.spacingM - footer.height - Theme.spacingS
function visualIndex(index) {
return I18n.isRtl ? slice.length - 1 - index : index;
}
function step(delta) {
const next = Math.max(0, Math.min(maxStart, start + delta));
if (next === start)
return false;
startIndex = next;
return true;
}
function labelFor(f, index) {
if (daily)
return WeatherService.formatForecastDay(f.rawDate || f.rawSunrise, start + index);
if (!f.rawTime)
return f.time ?? "--";
return new Date(f.rawTime).toLocaleTimeString(I18n.locale(), SettingsData.use24HourClock ? "HH:mm" : "h AP");
}
StyledText {
anchors.centerIn: parent
visible: root.slice.length === 0
text: I18n.tr("Forecast Not Available")
color: Theme.onSurfaceVariant
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: wheel => {
if (!WheelInput.isHorizontal(wheel) || root.maxStart === 0) {
wheel.accepted = false;
return;
}
const delta = WheelInput.dominantDelta(wheel.angleDelta);
const step = delta > 0 ? -1 : 1;
const next = root.start + step;
if (delta !== 0 && next >= 0 && next <= root.maxStart)
root.pageRequested(step);
wheel.accepted = true;
}
}
Rectangle {
x: Theme.spacingM + root.visualIndex(root.nowIndex) * root.columnWidth
y: Theme.spacingS
width: root.columnWidth
height: parent.height - Theme.spacingS * 2
radius: Theme.cornerRadiusM
color: Theme.withAlpha(Theme.primary, Theme.stateLayerHover)
visible: root.nowIndex >= 0 && root.nowIndex < root.slice.length
}
Item {
id: header
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingM
height: Theme.iconSize + Theme.fontSizeSmall * 2 + Theme.spacingXS * 2
Repeater {
model: root.slice
Column {
id: column
required property var modelData
required property int index
readonly property bool current: index === root.nowIndex
x: root.visualIndex(index) * root.columnWidth
width: root.columnWidth
spacing: Theme.spacingXXS
StyledText {
width: parent.width
text: root.labelFor(column.modelData, column.index)
font.pixelSize: Theme.fontSizeSmall
font.weight: column.current ? Theme.fontWeightBold : Theme.fontWeightMedium
color: column.current ? Theme.primary : Theme.onSurfaceVariant
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
DIcon {
anchors.horizontalCenter: parent.horizontalCenter
name: WeatherService.getWeatherIcon(column.modelData.wCode || 0, column.modelData.isDay ?? true)
size: Theme.iconSize
color: column.current ? Theme.primary : Theme.onSurfaceVariant
}
StyledText {
width: parent.width
text: WeatherService.formatTemp(root.daily ? column.modelData.tempMax : column.modelData.temp) ?? "--"
font.pixelSize: Theme.fontSizeSmall
font.weight: Theme.fontWeightMedium
font.features: ({
"tnum": 1
})
color: column.current ? Theme.primary : Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
}
}
}
DSparkline {
x: Theme.spacingM + root.columnWidth / 2
y: root.chartTop
width: Math.max(0, root.width - Theme.spacingM * 2 - root.columnWidth)
height: Math.max(0, root.chartBottom - root.chartTop)
values: I18n.isRtl ? root.temps.slice().reverse() : root.temps
secondaryValues: I18n.isRtl ? root.lows.slice().reverse() : root.lows
autoRange: true
showDots: true
dotRadius: DashMetrics.chartDotRadius
lineWidth: Theme.outlineWidthFocused
lineColor: Theme.primary
secondaryLineColor: Theme.tertiary
fillOpacity: 0
edgeExtension: root.columnWidth / 2
insetTop: dotRadius * 2
insetBottom: dotRadius * 2
}
Item {
id: footer
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Theme.spacingM
height: Theme.fontSizeSmall + Theme.spacingXS
Repeater {
model: root.slice
Item {
id: footerColumn
required property var modelData
required property int index
x: root.visualIndex(index) * root.columnWidth
width: root.columnWidth
height: footer.height
Row {
anchors.centerIn: parent
spacing: Theme.spacingXXS
DIcon {
visible: !root.daily
name: "rainy"
size: Theme.iconSizeSmall
color: Theme.onSurfaceVariant
}
StyledText {
text: root.daily ? (WeatherService.formatTemp(footerColumn.modelData.tempMin) ?? "--") : (WeatherService.formatPercent(footerColumn.modelData.precipitationProbability) ?? "--")
font.pixelSize: Theme.fontSizeSmall
font.weight: footerColumn.index === root.nowIndex ? Theme.fontWeightBold : Theme.fontWeightMedium
color: footerColumn.index === root.nowIndex ? Theme.primary : Theme.onSurfaceVariant
}
}
}
}
}
}