init
This commit is contained in:
121 files changed
+17641
No files matched your search
@@ -0,0 +1,284 @@
|
||||
// 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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user