235 lines
8.5 KiB
QML
235 lines
8.5 KiB
QML
// The window.
|
|
//
|
|
// Qt Quick Controls' Material style, following the session's own light/dark
|
|
// preference, with a navigation rail on the left and the pages stacked on the
|
|
// right. Everything it shows comes from `controller`, the Python object bound
|
|
// into the QML context — there is no second copy of the state here.
|
|
import QtQuick
|
|
import QtQuick.Controls.Material
|
|
import QtQuick.Layouts
|
|
import QtQuick.Window
|
|
|
|
import "components"
|
|
|
|
ApplicationWindow {
|
|
id: window
|
|
|
|
width: 1100
|
|
height: 760
|
|
minimumWidth: 420
|
|
minimumHeight: 560
|
|
visible: false
|
|
title: controller.running
|
|
? controller.clockText + " · " + controller.kindLabel + " — Kadence"
|
|
: "Kadence"
|
|
|
|
Material.theme: Material.System
|
|
Material.primary: "#6750A4"
|
|
Material.accent: "#6750A4"
|
|
|
|
readonly property color workColor: "#2a78d6"
|
|
readonly property color shortBreakColor: "#eb6834"
|
|
readonly property color longBreakColor: "#1baf7a"
|
|
readonly property bool wide: width >= 820
|
|
property int page: 0
|
|
|
|
function kindColor(kind) {
|
|
if (kind === "short_break") return shortBreakColor
|
|
if (kind === "long_break") return longBreakColor
|
|
return workColor
|
|
}
|
|
|
|
// Closing the window keeps the app alive in the tray — the timer is still
|
|
// running, and that is the whole point of a tray icon.
|
|
onClosing: function (close) {
|
|
if (controller.config.close_to_tray && controller.config.tray_icon) {
|
|
close.accepted = false
|
|
window.hide()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: controller
|
|
function onToast(message) { snackbar.show(message) }
|
|
function onShowWindowRequested() { window.show(); window.raise(); window.requestActivate() }
|
|
}
|
|
|
|
// ---- shortcuts ---------------------------------------------------------
|
|
Shortcut { sequence: "Space"; onActivated: controller.toggle() }
|
|
Shortcut { sequence: "Ctrl+Return"; onActivated: controller.complete() }
|
|
Shortcut { sequence: "Ctrl+E"; onActivated: controller.extend(5) }
|
|
Shortcut { sequence: "Ctrl+R"; onActivated: controller.refreshSources() }
|
|
Shortcut { sequence: "Ctrl+1"; onActivated: window.page = 0 }
|
|
Shortcut { sequence: "Ctrl+2"; onActivated: window.page = 1 }
|
|
Shortcut { sequence: "Ctrl+3"; onActivated: window.page = 2 }
|
|
Shortcut { sequence: "Ctrl+4"; onActivated: window.page = 3 }
|
|
Shortcut { sequence: "Ctrl+5"; onActivated: window.page = 4 }
|
|
Shortcut { sequence: "Ctrl+W"; onActivated: window.hide() }
|
|
|
|
header: ToolBar {
|
|
visible: !window.wide
|
|
Material.background: Material.backgroundColor
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 12
|
|
anchors.rightMargin: 12
|
|
Label {
|
|
text: ["Timer", "Tasks", "Blocker", "Report", "Settings"][window.page]
|
|
font.pixelSize: 18
|
|
font.weight: Font.Medium
|
|
Layout.fillWidth: true
|
|
}
|
|
StatusPill {
|
|
running: controller.running
|
|
paused: controller.status === "paused"
|
|
connected: controller.connected
|
|
text: controller.connected ? controller.clockText : "offline"
|
|
accent: window.kindColor(controller.kind)
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
spacing: 0
|
|
|
|
// ---- navigation rail ------------------------------------------------
|
|
Pane {
|
|
visible: window.wide
|
|
Layout.fillHeight: true
|
|
Layout.preferredWidth: 212
|
|
Material.elevation: 0
|
|
padding: 12
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
spacing: 4
|
|
|
|
RowLayout {
|
|
Layout.bottomMargin: 16
|
|
Layout.leftMargin: 8
|
|
spacing: 10
|
|
Rectangle {
|
|
width: 36; height: 36; radius: 18
|
|
color: Material.accent
|
|
Label { anchors.centerIn: parent; text: "🍅"; font.pixelSize: 18 }
|
|
}
|
|
ColumnLayout {
|
|
spacing: 0
|
|
Label { text: "Kadence"; font.pixelSize: 17; font.weight: Font.DemiBold }
|
|
Label {
|
|
text: controller.connected ? "connected" : "offline"
|
|
font.pixelSize: 11
|
|
opacity: 0.6
|
|
}
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: [
|
|
{ label: "Timer", icon: "⏱" },
|
|
{ label: "Tasks", icon: "✓" },
|
|
{ label: "Blocker", icon: "🛡" },
|
|
{ label: "Report", icon: "📊" },
|
|
{ label: "Settings", icon: "⚙" }
|
|
]
|
|
delegate: ItemDelegate {
|
|
required property int index
|
|
required property var modelData
|
|
Layout.fillWidth: true
|
|
height: 44
|
|
highlighted: window.page === index
|
|
onClicked: window.page = index
|
|
|
|
background: Rectangle {
|
|
radius: 22
|
|
color: parent.highlighted ? Material.accent
|
|
: (parent.hovered ? Material.listHighlightColor : "transparent")
|
|
opacity: parent.highlighted ? 0.18 : 1
|
|
}
|
|
contentItem: RowLayout {
|
|
spacing: 12
|
|
Label { text: modelData.icon; font.pixelSize: 16 }
|
|
Label {
|
|
text: modelData.label
|
|
font.weight: parent.parent.highlighted ? Font.DemiBold : Font.Normal
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item { Layout.fillHeight: true }
|
|
|
|
StatusPill {
|
|
Layout.fillWidth: true
|
|
running: controller.running
|
|
paused: controller.status === "paused"
|
|
connected: controller.connected
|
|
text: controller.connected ? (controller.running ? controller.clockText : "idle") : "offline"
|
|
accent: window.kindColor(controller.kind)
|
|
}
|
|
Label {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 6
|
|
text: controller.completedToday + "/" + controller.goal + " today"
|
|
font.pixelSize: 12
|
|
opacity: 0.7
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
Label {
|
|
Layout.fillWidth: true
|
|
visible: controller.blockerActive
|
|
text: "🛡 blocking is on"
|
|
font.pixelSize: 12
|
|
color: "#e34948"
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- pages ----------------------------------------------------------
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
spacing: 0
|
|
|
|
Banner {
|
|
Layout.fillWidth: true
|
|
visible: !controller.connected
|
|
text: controller.connectionError !== ""
|
|
? controller.connectionError
|
|
: "Cannot reach the backend. Start it with `docker compose up`."
|
|
actionText: "Settings"
|
|
onActionClicked: window.page = 4
|
|
}
|
|
|
|
StackLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
currentIndex: window.page
|
|
|
|
TimerPage {}
|
|
TasksPage {}
|
|
BlockerPage {}
|
|
ReportPage {}
|
|
SettingsPage {}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- footer: bottom navigation on a narrow window ----------------------
|
|
footer: TabBar {
|
|
visible: !window.wide
|
|
currentIndex: window.page
|
|
onCurrentIndexChanged: window.page = currentIndex
|
|
TabButton { text: "⏱" }
|
|
TabButton { text: "✓" }
|
|
TabButton { text: "🛡" }
|
|
TabButton { text: "📊" }
|
|
TabButton { text: "⚙" }
|
|
}
|
|
|
|
Snackbar { id: snackbar }
|
|
}
|