// The timer: the dial, the controls, and what you are working on. import QtQuick import QtQuick.Controls import QtQuick.Layouts import "." import "components" ScrollView { id: page contentWidth: availableWidth clip: true readonly 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 } function suggestion() { var buckets = ["overdue", "today", "upcoming", "someday"] for (var b = 0; b < buckets.length; b++) for (var i = 0; i < openTasks.length; i++) if (openTasks[i].pinned || openTasks[i].bucket === buckets[b]) return openTasks[i] return null } ColumnLayout { width: page.availableWidth - 56 x: 28 y: 26 spacing: 22 PageHeader { eyebrow: Qt.formatDate(new Date(), "dddd, d MMMM") title: !controller.timerEnabled ? "View mode" : (controller.running ? controller.kindLabel : "Ready when you are") caption: !controller.timerEnabled ? "Timer is disabled · view-only mode" : (controller.taskTitle !== "" ? "Tracking “" + controller.taskTitle + "”" : "Cycle " + controller.cyclePosition + " of " + controller.cycleLength + " before a long break") GhostButton { visible: controller.timerEnabled && controller.running && controller.status !== "paused" text: "Pause" onClicked: controller.pause() } GhostButton { visible: controller.timerEnabled && controller.running && controller.status === "paused" text: "Resume" onClicked: controller.resume() } PrimaryButton { visible: controller.timerEnabled && controller.running text: "Finish" onClicked: controller.complete() } PrimaryButton { visible: controller.timerEnabled && !controller.running text: "Start focus" onClicked: controller.startWork() } GhostButton { visible: controller.timerEnabled && !controller.running text: controller.nextKindLabel onClicked: controller.startBreak() } GhostButton { text: controller.timerEnabled ? "Turn off timer" : "Turn on timer" onClicked: controller.setTimerEnabled(!controller.timerEnabled) } } GridLayout { Layout.fillWidth: true columns: page.width > 940 ? 2 : 1 columnSpacing: 22 rowSpacing: 22 Panel { Layout.fillWidth: true Layout.preferredWidth: 1 padding: 28 TimerDial { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: Math.min(290, page.availableWidth * 0.62) Layout.preferredHeight: Layout.preferredWidth progress: controller.timerEnabled ? controller.progress : 0 clockText: controller.timerEnabled ? controller.clockText : "—" minutes: Math.max(1, Math.round(controller.plannedSeconds / 60)) label: !controller.timerEnabled ? "View mode" : (controller.running ? controller.kindLabel : "Next · " + controller.nextKindLabel) sublabel: !controller.timerEnabled ? "Timer disabled" : (controller.taskTitle !== "" ? controller.taskTitle : (controller.running ? "" : "Pick something below, or just begin")) accent: controller.timerEnabled ? Theme.kindColor(controller.kind) : Theme.muted paused: controller.status === "paused" idle: !controller.timerEnabled || !controller.running } RowLayout { visible: controller.timerEnabled && controller.running Layout.alignment: Qt.AlignHCenter Layout.topMargin: 14 spacing: 6 GhostButton { text: "+5 min" onClicked: controller.extend(5) } GhostButton { text: "Interruption" onClicked: controller.interrupt() ToolTip.visible: hovered ToolTip.text: "Count a distraction without stopping the clock" } GhostButton { text: "Abandon" onClicked: abandonDialog.open() } } Label { Layout.alignment: Qt.AlignHCenter visible: controller.timerEnabled && controller.interruptions > 0 text: controller.interruptions + " interruption(s) so far" color: Theme.faint font.pixelSize: 11 font.family: Theme.sansFamily } Label { Layout.alignment: Qt.AlignHCenter visible: controller.timerEnabled text: "Space start/pause · Ctrl+Enter finish · Ctrl+E +5 min" color: Theme.faint font.pixelSize: 10 font.family: Theme.sansFamily } } ColumnLayout { Layout.fillWidth: true Layout.preferredWidth: 1 Layout.alignment: Qt.AlignTop spacing: 20 ColumnLayout { Layout.fillWidth: true spacing: 10 SectionTitle { text: "Today" } RowLayout { spacing: 8 Label { text: controller.completedToday color: Theme.ink font.pixelSize: 40 font.family: Theme.displayFamily font.letterSpacing: -1 } Label { text: "of " + controller.goal + " pomodoros" color: Theme.muted font.pixelSize: 14 font.family: Theme.sansFamily Layout.alignment: Qt.AlignBottom Layout.bottomMargin: 7 } } Flow { Layout.fillWidth: true spacing: 6 Repeater { model: Math.max(controller.goal, controller.completedToday) delegate: Rectangle { required property int index width: 11; height: 11; radius: 5.5 color: index < controller.completedToday ? Theme.work : "transparent" border.width: index < controller.completedToday ? 0 : 1 border.color: Theme.rule } } } Label { Layout.fillWidth: true text: controller.stats.today ? controller.stats.today.focus_minutes + "m focused · " + controller.stats.today.break_minutes + "m rested" + (controller.stats.today.longest_stretch_minutes > 0 ? " · longest run " + controller.stats.today.longest_stretch_minutes + "m" : "") : "" color: Theme.muted font.pixelSize: 12 font.family: Theme.sansFamily wrapMode: Text.WordWrap } } ColumnLayout { Layout.fillWidth: true spacing: 10 SectionTitle { text: "Working on" } ComboBox { id: taskPicker Layout.fillWidth: true font.pixelSize: 13 font.family: Theme.sansFamily model: { var names = ["Nothing in particular"] 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) } } RowLayout { Layout.fillWidth: true spacing: 14 Label { text: (controller.taskCounts.overdue || 0) + " overdue" color: (controller.taskCounts.overdue || 0) > 0 ? Theme.clay : Theme.muted font.pixelSize: 11 font.family: Theme.sansFamily } Label { text: (controller.taskCounts.today || 0) + " due today" color: Theme.muted font.pixelSize: 11 font.family: Theme.sansFamily } Item { Layout.fillWidth: true } GhostButton { text: "All tasks"; onClicked: window.page = 1 } } } } } // ---- the day's numbers ------------------------------------------------- Panel { padding: 0 RowLayout { Layout.fillWidth: true spacing: 0 Repeater { model: [ { label: "Focus today", value: controller.stats.today ? controller.stats.today.focus_minutes + "m" : "—", caption: controller.completedToday + " intervals" }, { label: "Interruptions", value: controller.stats.today ? "" + controller.stats.today.interruptions : "—", caption: controller.stats.today && controller.stats.today.abandoned > 0 ? controller.stats.today.abandoned + " abandoned" : "none dropped" }, { label: "Closed today", value: controller.stats.today ? "" + controller.stats.today.tasks_completed_count : "—", caption: "in your own tools" }, { label: "Blocked", value: controller.stats.today ? "" + controller.stats.today.blocked_attempts : "—", caption: "temptations turned away" } ] delegate: RowLayout { required property int index required property var modelData Layout.fillWidth: true spacing: 0 Rectangle { visible: index > 0 width: 1 Layout.fillHeight: true Layout.preferredHeight: 70 color: Theme.rule } Stat { Layout.fillWidth: true Layout.margins: 18 label: modelData.label value: modelData.value caption: modelData.caption } } } } } Item { Layout.preferredHeight: 10 } } Dialog { id: abandonDialog anchors.centerIn: Overlay.overlay width: 380 modal: true title: "Abandon this interval?" standardButtons: Dialog.Cancel ColumnLayout { anchors.fill: parent spacing: 12 Label { text: controller.clockText + " left. It will be recorded as abandoned." color: Theme.muted font.pixelSize: 12 font.family: Theme.sansFamily wrapMode: Text.WordWrap Layout.fillWidth: true } TextField { id: reasonField Layout.fillWidth: true placeholderText: "What happened? (optional)" } PrimaryButton { text: "Abandon" Layout.alignment: Qt.AlignRight onClicked: { controller.abandon(reasonField.text); reasonField.text = ""; abandonDialog.close() } } } } }