This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
powersync-app/nymea-app/ui/mainviews/airconditioning/TemperatureScheduleEditor.qml
2023-05-20 12:59:01 +02:00

407 lines
16 KiB
QML

import QtQuick 2.3
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.1
import Nymea 1.0
import Nymea.AirConditioning 1.0
import "qrc:/ui/components"
ColumnLayout {
id: root
Layout.minimumHeight: 100
property string title: ""
property ZoneInfo zone: null
property TemperatureDaySchedule daySchedule: null
property TemperatureDaySchedule scheduleClipboard: null
signal copyClicked()
RowLayout {
Layout.fillWidth: true
Label {
Layout.fillWidth: true
text: root.title
}
ProgressButton {
imageSource: paste ? "edit-paste" : "edit-copy"
color: root.scheduleClipboard == root.daySchedule ? Style.accentColor : Style.iconColor
property bool paste: root.scheduleClipboard != null && root.scheduleClipboard != root.daySchedule
onClicked: {
if (paste) {
daySchedule.clear();
for (var i = 0; i < root.scheduleClipboard.count; i++) {
var schedule = root.scheduleClipboard.get(i)
daySchedule.createSchedule(schedule.startTime, schedule.endTime, schedule.temperature)
}
} else {
root.copyClicked()
}
}
}
}
QtObject {
id: d
property var freeBlocks: {
var ret = []
for (var i = 0; i < daySchedule.count; i++) {
var previous = i == 0 ? null : daySchedule.get(i - 1)
var previousMins = previous ? previous.endTime.getHours() * 60 + previous.endTime.getMinutes() : 0
var schedule = daySchedule.get(i)
var startMins = schedule.startTime.getHours() * 60 + schedule.startTime.getMinutes()
var endMins = schedule.endTime.getHours() * 60 + schedule.endTime.getMinutes()
if (startMins > previousMins + 60 * 3) {
ret.push({startMins: previousMins, endMins: startMins})
}
}
var last = daySchedule.count > 0 ? daySchedule.get(daySchedule.count - 1) : null
var lastMins = last ? last.endTime.getHours() * 60 + last.endTime.getMinutes() : 0
if (lastMins < (21*60)) {
ret.push({startMins: lastMins, endMins: 24*60})
}
return ret
}
}
Rectangle {
id: slider
Layout.fillWidth: true
Layout.fillHeight: true
color: Style.tileBackgroundColor
radius: Style.cornerRadius
clip: true
Repeater {
model: 24
delegate: Rectangle {
height: parent.height
width: 1
color: Style.tileOverlayColor
x: slider.width / 24 * index
visible: index > 0
}
}
Repeater {
model: root.daySchedule
delegate: Rectangle {
id: blockDelegate
readonly property TemperatureSchedule schedule: root.daySchedule.get(index)
readonly property int startMinutes: schedule.startTime.getHours() * 60 + schedule.startTime.getMinutes()
readonly property int endMinutes: schedule.endTime.getHours() * 60 + schedule.endTime.getMinutes()
readonly property int totalMinutes: 24 * 60
height: slider.height
width: endMinutes * slider.width / totalMinutes - x
x: startMinutes * slider.width / totalMinutes
radius: Style.cornerRadius
color: schedule.temperature >= zone.standbySetpoint ? Style.red : Style.blue
Rectangle {
anchors {
left: parent.left;
leftMargin: Style.extraSmallMargins
verticalCenter: parent.verticalCenter
}
height: Style.font.pixelSize
width: 2
radius: width / 2
color: Style.white
}
Rectangle {
anchors {
right: parent.right
rightMargin: Style.extraSmallMargins
verticalCenter: parent.verticalCenter
}
height: Style.font.pixelSize
width: 2
radius: width / 2
color: Style.white
}
Label {
anchors { left: parent.left; right: parent.right; margins: Style.extraSmallMargins; top: parent.top }
anchors.leftMargin: parent.width >= implicitWidth + Style.smallMargins ? Style.extraSmallMargins : -(implicitWidth + Style.extraSmallMargins)
horizontalAlignment: Text.AlignLeft
font: Style.extraSmallFont
text: blockDelegate.schedule.startTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
}
Label {
anchors { left: parent.left; right: parent.right; margins: Style.smallMargins; verticalCenter: parent.verticalCenter }
horizontalAlignment: Text.AlignHCenter
font: Style.smallFont
text: Types.toUiValue(blockDelegate.schedule.temperature, Types.UnitDegreeCelsius) + "°"
elide: Text.ElideRight
}
Label {
anchors { left: parent.left; right: parent.right; margins: Style.extraSmallMargins; bottom: parent.bottom }
anchors.rightMargin: parent.width >= implicitWidth + Style.smallMargins ? Style.extraSmallMargins : -(implicitWidth + Style.extraSmallMargins)
horizontalAlignment: Text.AlignRight
font: Style.extraSmallFont
text: blockDelegate.schedule.endTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
// elide: Text.ElideRight
}
}
}
MouseArea {
anchors.fill: parent
property bool moveStart: false
property bool moveEnd: false
property TemperatureSchedule previousSchedule: null
property TemperatureSchedule movedSchedule: null
property TemperatureSchedule nextSchedule: null
property int startMouseX: 0
property int startMins: 0
property int endMins: 0
onPressed: {
movedSchedule = null
startMouseX = mouseX
var totalMins = 24 * 60
for (var i = 0; i < root.daySchedule.count; i++) {
var schedule = root.daySchedule.get(i)
print("schedule:", schedule.startTime, schedule.endTime)
var startMin = schedule.startTime.getHours() * 60 + schedule.startTime.getMinutes()
var startPos = startMin * slider.width / totalMins
var endMin = schedule.endTime.getHours() * 60 + schedule.endTime.getMinutes()
var endPos = endMin * slider.width / totalMins
if (Math.abs(startPos - mouseX) < 10) {
moveStart = true;
print("start")
} else if (Math.abs(endPos - mouseX) < 10) {
moveEnd = true
print("end")
} else if (mouseX > startPos && mouseX < endPos) {
moveStart = true
moveEnd = true
print("middle")
} else {
continue
}
startMins = startMin
endMins = endMin
previousSchedule = i > 0 ? root.daySchedule.get(i-1) : null
movedSchedule = schedule
nextSchedule = i < root.daySchedule.count - 1 ? root.daySchedule.get(i+1) : null
break;
}
}
onReleased: {
moveStart = false;
moveEnd = false;
preventStealing = false;
}
onClicked: {
print("clicked")
if (movedSchedule != null && Math.abs(mouseX - startMouseX) < 5) {
print("opening")
var dialog = editDialogComponent.createObject(root.parent, {schedule: movedSchedule})
dialog.open()
}
}
onPositionChanged: {
var totalMins = 24 * 60
var diffX = mouseX - startMouseX
// dY : height = dM : total
var diffMins = diffX * totalMins / slider.width
print("diffX", diffX, "diffMins", diffMins, startMins, startMins + diffMins)
var newStart = new Date(movedSchedule.startTime);
var newEnd = new Date(movedSchedule.endTime);
var newStartMins = startMins + (moveStart ? diffMins : 0);
var newEndMins = endMins + (moveEnd ? diffMins : 0);
var snapMinutes = 30
var leftLimit = previousSchedule ? previousSchedule.endTime.getHours() * 60 + previousSchedule.endTime.getMinutes() + snapMinutes : 0
var rightLimit = nextSchedule ? nextSchedule.startTime.getHours() * 60 + nextSchedule.startTime.getMinutes() - snapMinutes : totalMins
if (moveStart && !moveEnd) {
newStartMins = Math.max(leftLimit, newStartMins)
newStartMins = Math.min(newEndMins - 60, newStartMins)
} else if (moveEnd && !moveStart) {
newEndMins = Math.min(rightLimit, newEndMins)
newEndMins = Math.max(newStartMins + 60, newEndMins)
} else if (moveStart && moveEnd) {
newStartMins = Math.max(leftLimit, newStartMins)
newEndMins = Math.min(rightLimit, newEndMins)
var blockSize = endMins - startMins
newStartMins = Math.max(leftLimit, Math.min(newEndMins - blockSize, newStartMins))
newEndMins = Math.max(newStartMins + blockSize, newEndMins)
}
var startSnapOffset = newStartMins % snapMinutes
if (startSnapOffset < snapMinutes / 2) {
newStartMins -= startSnapOffset
} else {
newStartMins += (snapMinutes - startSnapOffset)
}
var endSnapOffset = newEndMins % snapMinutes
if (endSnapOffset < snapMinutes / 2) {
newEndMins -= endSnapOffset
} else {
newEndMins += (snapMinutes - endSnapOffset)
}
print("startSnapOffset", startSnapOffset, "endSnapOff", endSnapOffset, "nes start", newStartMins, newEndMins)
if (newEndMins == totalMins) {
newEndMins -= 1
}
newStart.setHours(0, newStartMins)
newEnd.setHours(0, newEndMins)
if (movedSchedule.startTime.getTime() !== newStart.getTime() || movedSchedule.endTime.getTime() !== newEnd.getTime()) {
preventStealing = true;
}
movedSchedule.startTime = newStart
movedSchedule.endTime = newEnd
print("start time is new", newStart.toLocaleTimeString())
print("end time is new", newEnd.toLocaleTimeString())
}
}
Repeater {
model: d.freeBlocks
delegate: Item {
id: freeBlockDelegate
property var block: d.freeBlocks[index]
x: block.startMins * slider.width / (24*60)
width: block.endMins * slider.width / (24*60) - x
height: slider.height
ProgressButton {
anchors.centerIn: parent
imageSource: "add"
onClicked: {
var startTime = new Date()
var endTime = new Date()
if (root.daySchedule.count == 0) {
startTime.setHours(6, 0, 0)
endTime.setHours(18, 0, 0)
} else {
startTime.setHours(0, freeBlockDelegate.block.startMins + 60, 0)
endTime.setHours(0, freeBlockDelegate.block.endMins - 60, 0)
}
root.daySchedule.createSchedule(startTime, endTime, 21)
}
}
}
}
}
Component {
id: editDialogComponent
NymeaDialog {
id: editDialog
x: (parent.width - width) / 2
property TemperatureSchedule schedule: null
standardButtons: Dialog.NoButton
Dial {
id: dial
Layout.fillWidth: true
Layout.preferredHeight: width
activeValue: root.zone.standbySetpoint
minValue: 10
maxValue: 30
precision: 0.5
value: editDialog.schedule.temperature
onMoved: editDialog.schedule.temperature = value
color: activeValue <= value ? Style.red : Style.blue
ColumnLayout {
anchors.centerIn: parent
anchors.verticalCenterOffset: -Style.smallMargins
width: parent.contentItem.width * 0.6
Label {
Layout.fillWidth: true
text: Types.toUiUnit(Types.UnitDegreeCelsius)
font.pixelSize: Math.min(Style.smallFont.pixelSize, dial.height / 16)
horizontalAlignment: Text.AlignHCenter
}
Label {
Layout.fillWidth: true
text: Types.toUiValue(editDialog.schedule.temperature, Types.UnitDegreeCelsius).toFixed(1)
font.pixelSize: Math.min(Style.hugeFont.pixelSize, dial.height / 8)
horizontalAlignment: Text.AlignHCenter
color: zone.currentSetpoint > zone.standbySetpoint
? Style.red
: zone.currentSetpoint < zone.standbySetpoint
? Style.blue
: Style.foregroundColor
}
Label {
Layout.fillWidth: true
text: Types.toUiValue(zone.standbySetpoint, Types.UnitDegreeCelsius).toFixed(1)
font.pixelSize: Math.min(Style.largeFont.pixelSize, dial.height / 12)
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
}
}
Label {
Layout.fillWidth: true
text: editDialog.schedule.startTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
+ " - "
+ editDialog.schedule.endTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
horizontalAlignment: Text.AlignHCenter
}
RowLayout {
Button {
text: qsTr("Remove")
Layout.fillWidth: true
onClicked: {
root.daySchedule.removeSchedule(editDialog.schedule)
editDialog.close()
}
}
Button {
text: qsTr("OK")
Layout.fillWidth: true
onClicked: {
editDialog.close()
}
}
}
}
}
}