// The timer page: the dial, the controls, and what you are working on. import QtQuick import QtQuick.Controls.Material import QtQuick.Layouts import "components" ScrollView { id: page contentWidth: availableWidth clip: true property var openTasks: { var list = [] for (var i = 0; i < controller.tasks.length; i++) if (!controller.tasks[i].is_done) list.push(controller.tasks[i]) return list } ColumnLayout { width: page.availableWidth spacing: 16 anchors.margins: 20 GridLayout { Layout.fillWidth: true Layout.margins: 20 columns: page.width > 900 ? 2 : 1 columnSpacing: 16 rowSpacing: 16 // ---- dial + controls -------------------------------------------- Card { Layout.fillWidth: true Layout.preferredWidth: 1 TimerDial { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: Math.min(300, page.availableWidth * 0.7) Layout.preferredHeight: Layout.preferredWidth progress: controller.progress clockText: controller.clockText label: controller.running ? controller.kindLabel : "Next: " + controller.nextKindLabel sublabel: controller.running ? (controller.taskTitle !== "" ? controller.taskTitle : " ") : "Press start when you are ready" accent: window.kindColor(controller.kind) paused: controller.status === "paused" } Flow { Layout.alignment: Qt.AlignHCenter Layout.topMargin: 8 spacing: 8 PrimaryButton { visible: !controller.running text: "▶ Start focus" onClicked: controller.startWork() } Button { visible: !controller.running text: "☕ " + controller.nextKindLabel onClicked: controller.startBreak() } Button { visible: controller.running && controller.status !== "paused" text: "⏸ Pause" onClicked: controller.pause() } PrimaryButton { visible: controller.running && controller.status === "paused" text: "▶ Resume" onClicked: controller.resume() } PrimaryButton { visible: controller.running text: "✓ Done" onClicked: controller.complete() } Button { visible: controller.running text: "+5" onClicked: controller.extend(5) ToolTip.visible: hovered ToolTip.text: "Add five minutes" } Button { visible: controller.running text: "🔔" onClicked: controller.interrupt() ToolTip.visible: hovered ToolTip.text: "Count an interruption without stopping" } Button { visible: controller.running text: "⏹" flat: true Material.foreground: "#e34948" onClicked: abandonDialog.open() ToolTip.visible: hovered ToolTip.text: "Abandon this interval" } } Label { Layout.alignment: Qt.AlignHCenter visible: controller.interruptions > 0 text: controller.interruptions + " interruption(s) this interval" font.pixelSize: 11 opacity: 0.6 } Label { Layout.alignment: Qt.AlignHCenter text: "Space start/pause · Ctrl+Enter finish · Ctrl+E +5 min" font.pixelSize: 10 opacity: 0.45 } } // ---- today + task picker ---------------------------------------- ColumnLayout { Layout.fillWidth: true Layout.preferredWidth: 1 Layout.alignment: Qt.AlignTop spacing: 16 Card { title: "Today" subtitle: "Cycle " + controller.cyclePosition + " of " + controller.cycleLength + " before a long break" RowLayout { Layout.fillWidth: true spacing: 8 Label { text: controller.completedToday font.pixelSize: 34 font.weight: Font.Light } Label { text: "/ " + controller.goal + " pomodoros" font.pixelSize: 16 opacity: 0.7 Layout.alignment: Qt.AlignBottom Layout.bottomMargin: 6 } Item { Layout.fillWidth: true } } ProgressBar { Layout.fillWidth: true from: 0 to: Math.max(controller.goal, 1) value: Math.min(controller.completedToday, controller.goal) } Flow { Layout.fillWidth: true spacing: 6 Repeater { model: Math.max(controller.goal, controller.completedToday) delegate: Rectangle { required property int index width: 14; height: 14; radius: 7 color: index < controller.completedToday ? window.workColor : "transparent" border.width: index < controller.completedToday ? 0 : 1 border.color: Material.hintTextColor } } } } Card { title: "What are you working on?" ComboBox { id: taskPicker Layout.fillWidth: true model: { var names = ["No task"] for (var i = 0; i < page.openTasks.length; i++) names.push((page.openTasks[i].pinned ? "📌 " : "") + page.openTasks[i].title + (page.openTasks[i].due_date ? " · " + page.openTasks[i].due_date : "")) return names } onActivated: function (index) { controller.attachTask(index === 0 ? -1 : page.openTasks[index - 1].id) } } Label { Layout.fillWidth: true wrapMode: Text.WordWrap font.pixelSize: 12 opacity: 0.7 text: controller.taskTitle !== "" ? "This interval is tracked against “" + controller.taskTitle + "”." : "Pick a task so the daily report knows where the time went." } RowLayout { Layout.fillWidth: true spacing: 8 Label { text: "🔴 " + (controller.taskCounts.overdue || 0) + " overdue" font.pixelSize: 12 } Label { text: "🟡 " + (controller.taskCounts.today || 0) + " due today" font.pixelSize: 12 } Item { Layout.fillWidth: true } Button { text: "Tasks"; flat: true; onClicked: window.page = 1 } } } } } // ---- the day's numbers ------------------------------------------------ GridLayout { Layout.fillWidth: true Layout.leftMargin: 20 Layout.rightMargin: 20 Layout.bottomMargin: 20 columns: page.width > 760 ? 4 : 2 columnSpacing: 12 rowSpacing: 12 StatTile { label: "Focus today" value: controller.stats.today ? controller.stats.today.focus_minutes + "m" : "—" caption: controller.completedToday + " pomodoros" } StatTile { label: "Longest stretch" value: controller.stats.today ? controller.stats.today.longest_stretch_minutes + "m" : "—" caption: "unbroken focus" } StatTile { label: "Streak" value: controller.stats.streak ? controller.stats.streak.current + "d" : "—" caption: controller.stats.streak ? "best " + controller.stats.streak.best : "" } StatTile { label: "Blocked" value: controller.stats.today ? controller.stats.today.blocked_attempts : "—" caption: "temptations turned away" } } } Dialog { id: abandonDialog anchors.centerIn: Overlay.overlay width: 380 modal: true title: "Abandon this interval?" standardButtons: Dialog.Cancel ColumnLayout { anchors.fill: parent spacing: 10 Label { text: controller.clockText + " left. The interval is recorded as abandoned." wrapMode: Text.WordWrap Layout.fillWidth: true } TextField { id: reasonField Layout.fillWidth: true placeholderText: "What happened? (optional)" } Button { text: "Abandon" Material.background: "#e34948" Material.foreground: "white" Layout.alignment: Qt.AlignRight onClicked: { controller.abandon(reasonField.text); reasonField.text = ""; abandonDialog.close() } } } } }