- Refactored TimerPage component to improve layout and user experience. - Updated theme configuration to utilize design tokens for better maintainability. - Introduced new tokens for colors and typography to align with design principles. - Simplified state management and improved timer functionality. - Enhanced accessibility and responsiveness of UI elements.
261 lines
9.8 KiB
QML
261 lines
9.8 KiB
QML
// The window.
|
|
//
|
|
// A quiet left column — wordmark, navigation as words, and the two things worth
|
|
// knowing from anywhere (what the timer is doing, whether the blocker is on) —
|
|
// with the pages on the right. Qt Quick Controls supply the behaviour; the
|
|
// chrome comes from `Theme`, so the desktop app and the web app are the same
|
|
// product rather than two takes on it.
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Controls.Material
|
|
import QtQuick.Layouts
|
|
|
|
import "."
|
|
import "components"
|
|
|
|
ApplicationWindow {
|
|
id: window
|
|
|
|
width: 1180
|
|
height: 820
|
|
minimumWidth: 460
|
|
minimumHeight: 600
|
|
visible: false
|
|
color: Theme.paper
|
|
title: controller.running
|
|
? controller.clockText + " · " + controller.kindLabel + " — Kadence"
|
|
: "Kadence"
|
|
|
|
Material.theme: Theme.dark ? Material.Dark : Material.Light
|
|
Material.primary: Theme.ember
|
|
Material.accent: Theme.ember
|
|
Material.background: Theme.paper
|
|
Material.foreground: Theme.ink
|
|
|
|
readonly property bool wide: width >= 820
|
|
property int page: 0
|
|
|
|
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() }
|
|
}
|
|
|
|
Shortcut { sequence: "Space"; onActivated: if (controller.timerEnabled) controller.toggle() }
|
|
Shortcut { sequence: "Ctrl+Return"; onActivated: if (controller.timerEnabled) controller.complete() }
|
|
Shortcut { sequence: "Ctrl+E"; onActivated: if (controller.timerEnabled) 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() }
|
|
Shortcut { sequence: "Ctrl+M"; onActivated: controller.toggleMini() }
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
spacing: 0
|
|
|
|
// ---- the rail --------------------------------------------------------
|
|
Item {
|
|
visible: window.wide
|
|
Layout.fillHeight: true
|
|
Layout.preferredWidth: 212
|
|
|
|
Rectangle {
|
|
anchors.right: parent.right
|
|
width: 1
|
|
height: parent.height
|
|
color: Theme.rule
|
|
}
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 22
|
|
spacing: 2
|
|
|
|
ColumnLayout {
|
|
Layout.bottomMargin: 26
|
|
spacing: 0
|
|
Label {
|
|
text: "Kadence"
|
|
color: Theme.ink
|
|
font.pixelSize: 23
|
|
font.family: Theme.displayFamily
|
|
font.letterSpacing: -0.5
|
|
}
|
|
Label {
|
|
text: "find your rhythm"
|
|
color: Theme.muted
|
|
font.pixelSize: 11
|
|
font.family: Theme.sansFamily
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: ["Timer", "Tasks", "Blocker", "Report", "Settings"]
|
|
delegate: Item {
|
|
required property int index
|
|
required property string modelData
|
|
Layout.fillWidth: true
|
|
implicitHeight: 34
|
|
|
|
Rectangle {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: 2
|
|
height: 16
|
|
radius: 1
|
|
color: Theme.ember
|
|
visible: window.page === index
|
|
}
|
|
Label {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 14
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: modelData
|
|
color: window.page === index ? Theme.ink : (mouse.containsMouse ? Theme.ink : Theme.muted)
|
|
font.pixelSize: 14
|
|
font.weight: window.page === index ? Font.DemiBold : Font.Normal
|
|
font.family: Theme.sansFamily
|
|
}
|
|
MouseArea {
|
|
id: mouse
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: window.page = index
|
|
}
|
|
}
|
|
}
|
|
|
|
Item { Layout.fillHeight: true }
|
|
|
|
Rectangle { Layout.fillWidth: true; height: 1; color: Theme.rule; Layout.bottomMargin: 10 }
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 8
|
|
Rectangle {
|
|
width: 7; height: 7; radius: 3.5
|
|
color: controller.running ? Theme.kindColor(controller.kind) : Theme.faint
|
|
SequentialAnimation on opacity {
|
|
running: controller.running && controller.status === "running"
|
|
loops: Animation.Infinite
|
|
NumberAnimation { to: 0.35; duration: 900 }
|
|
NumberAnimation { to: 1.0; duration: 900 }
|
|
}
|
|
}
|
|
Label {
|
|
text: controller.connected
|
|
? (controller.timerEnabled
|
|
? (controller.running ? controller.clockText : "Idle")
|
|
: "View mode")
|
|
: "Offline"
|
|
color: Theme.ink
|
|
font.pixelSize: 13
|
|
font.weight: Font.Medium
|
|
font.family: Theme.monoFamily
|
|
}
|
|
Item { Layout.fillWidth: true }
|
|
Label {
|
|
text: controller.completedToday + "/" + controller.goal
|
|
color: Theme.muted
|
|
font.pixelSize: 12
|
|
font.family: Theme.sansFamily
|
|
}
|
|
}
|
|
Label {
|
|
visible: controller.blockerActive
|
|
text: "● BLOCKING"
|
|
color: Theme.clay
|
|
font.pixelSize: 10
|
|
font.letterSpacing: 1.1
|
|
font.weight: Font.DemiBold
|
|
font.family: Theme.sansFamily
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- pages -----------------------------------------------------------
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
spacing: 0
|
|
|
|
ToolBar {
|
|
visible: !window.wide
|
|
Layout.fillWidth: true
|
|
background: Rectangle {
|
|
color: Theme.paper
|
|
Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: Theme.rule }
|
|
}
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 16
|
|
anchors.rightMargin: 16
|
|
Label {
|
|
text: "Kadence"
|
|
color: Theme.ink
|
|
font.pixelSize: 18
|
|
font.family: Theme.displayFamily
|
|
Layout.fillWidth: true
|
|
}
|
|
Label {
|
|
text: controller.connected
|
|
? (controller.timerEnabled
|
|
? (controller.running ? controller.clockText : "Idle")
|
|
: "View mode")
|
|
: "Offline"
|
|
color: Theme.ink
|
|
font.pixelSize: 13
|
|
font.family: Theme.monoFamily
|
|
}
|
|
}
|
|
}
|
|
|
|
StackLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
currentIndex: window.page
|
|
|
|
TimerPage {}
|
|
TasksPage {}
|
|
BlockerPage {}
|
|
ReportPage {}
|
|
SettingsPage {}
|
|
}
|
|
|
|
TabBar {
|
|
visible: !window.wide
|
|
Layout.fillWidth: true
|
|
currentIndex: window.page
|
|
onCurrentIndexChanged: window.page = currentIndex
|
|
background: Rectangle {
|
|
color: Theme.paper
|
|
Rectangle { width: parent.width; height: 1; color: Theme.rule }
|
|
}
|
|
TabButton { text: "Timer" }
|
|
TabButton { text: "Tasks" }
|
|
TabButton { text: "Blocker" }
|
|
TabButton { text: "Report" }
|
|
TabButton { text: "Settings" }
|
|
}
|
|
}
|
|
}
|
|
|
|
Snackbar { id: snackbar }
|
|
|
|
// The floating mini timer is created by app.py as its own top-level window:
|
|
// nested inside this one it would be transient, and a transient window
|
|
// cannot be shown while its parent is hidden — which is precisely when the
|
|
// mini timer earns its keep.
|
|
}
|