refactor: enhance TimerPage layout and functionality; update theme and tokens for improved design consistency

- 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.
This commit is contained in:
timka123 committed 2026-10-18 23:57:36 +03:00
1 parent 17a0a3c4c9
commit 77b26757bf
84 files changed
+4501 -2682

No files matched your search

+163 -109
View File
@@ -1,12 +1,19 @@
// Everything you owe, from every source, grouped by deadline.
// Everything you owe, grouped by deadline.
//
// There is no checkbox: a task is closed in Jira, Todoist or Google Tasks, and
// the next refresh notices. Two places to tick something off is two places to
// be wrong.
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Controls
import QtQuick.Layouts
import "."
import "components"
Item {
ScrollView {
id: page
contentWidth: availableWidth
clip: true
function bucketTasks(bucket) {
var out = []
@@ -17,130 +24,170 @@ Item {
return out
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 12
function bucketColor(bucket) {
if (bucket === "overdue") return Theme.clay
if (bucket === "today") return Theme.amber
if (bucket === "active") return Theme.pine
if (bucket === "upcoming") return Theme.faint
return Theme.rule
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Label {
text: controller.tasks.length + " open"
font.pixelSize: 13
opacity: 0.7
}
Item { Layout.fillWidth: true }
Button { text: "+ Add"; onClicked: addDialog.open() }
PrimaryButton { text: "⟳ Refresh sources"; onClicked: controller.refreshSources() }
ColumnLayout {
width: page.availableWidth - 56
x: 28
y: 26
spacing: 22
PageHeader {
eyebrow: "From Jira, Todoist, Google Tasks and your own list"
title: "Tasks"
caption: "Close them where they live — the next refresh credits them to today."
GhostButton { text: "Add"; onClicked: addDialog.open() }
PrimaryButton { text: "Refresh sources"; onClicked: controller.refreshSources() }
}
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
contentWidth: availableWidth
clip: true
Repeater {
model: [
{ key: "overdue", label: "Overdue" },
{ key: "today", label: "Due today" },
{ key: "active", label: "In progress" },
{ key: "upcoming", label: "Coming up" },
{ key: "someday", label: "No deadline" }
]
delegate: ColumnLayout {
required property var modelData
property var items: page.bucketTasks(modelData.key)
visible: items.length > 0
Layout.fillWidth: true
spacing: 2
ColumnLayout {
width: parent.parent.availableWidth
spacing: 12
SectionTitle { text: modelData.label; trailing: "" + parent.items.length }
Repeater {
model: [
{ key: "overdue", label: "Overdue", mark: "🔴" },
{ key: "today", label: "Due today", mark: "🟡" },
{ key: "upcoming", label: "Coming up", mark: "🔵" },
{ key: "someday", label: "No deadline", mark: "⚪" }
]
delegate: Card {
model: parent.items
delegate: Item {
required property var modelData
property var items: page.bucketTasks(modelData.key)
visible: items.length > 0
title: modelData.mark + " " + modelData.label + " (" + items.length + ")"
Layout.fillWidth: true
implicitHeight: row.implicitHeight + 20
Repeater {
model: parent.parent.items
delegate: RowLayout {
required property var modelData
Rectangle {
anchors.bottom: parent.bottom
width: parent.width
height: 1
color: Theme.rule
}
RowLayout {
id: row
anchors.fill: parent
anchors.topMargin: 10
anchors.bottomMargin: 10
spacing: 14
Rectangle {
width: 2
Layout.fillHeight: true
radius: 1
color: page.bucketColor(modelData.bucket)
}
ColumnLayout {
Layout.fillWidth: true
spacing: 10
CheckBox {
checked: modelData.is_done
onToggled: controller.setTaskDone(modelData.id, checked)
}
ColumnLayout {
spacing: 3
Label {
text: modelData.title
color: Theme.ink
font.pixelSize: 14
font.weight: modelData.pinned ? Font.DemiBold : Font.Normal
font.family: Theme.sansFamily
elide: Text.ElideRight
Layout.fillWidth: true
spacing: 2
}
RowLayout {
spacing: 12
Label {
text: modelData.title
font.pixelSize: 14
elide: Text.ElideRight
Layout.fillWidth: true
text: modelData.source_label
color: Theme.faint; font.pixelSize: 11
font.family: Theme.sansFamily
}
RowLayout {
spacing: 6
Label {
text: modelData.source_label
font.pixelSize: 10
opacity: 0.65
}
Label {
visible: !!modelData.project
text: "· " + modelData.project
font.pixelSize: 10
opacity: 0.65
}
Label {
visible: modelData.days_overdue > 0
text: "· " + modelData.days_overdue + "d overdue"
font.pixelSize: 10
color: "#e34948"
}
Label {
visible: !!modelData.due_date && modelData.days_overdue === 0
text: "· " + modelData.due_date
font.pixelSize: 10
opacity: 0.65
}
Label {
visible: !!modelData.priority
text: "· " + modelData.priority
font.pixelSize: 10
opacity: 0.65
}
Label {
visible: !!modelData.project && modelData.project !== modelData.source_label
text: modelData.project || ""
color: Theme.faint; font.pixelSize: 11
font.family: Theme.sansFamily
}
Label {
visible: !!modelData.status
text: modelData.status || ""
color: Theme.faint; font.pixelSize: 11
font.family: Theme.sansFamily
}
Label {
visible: modelData.days_overdue > 0
text: modelData.days_overdue + "d overdue"
color: Theme.clay; font.pixelSize: 11
font.weight: Font.DemiBold
font.family: Theme.sansFamily
}
Label {
visible: !!modelData.due_date && modelData.days_overdue === 0
text: "due " + (modelData.due_date || "")
color: Theme.faint; font.pixelSize: 11
font.family: Theme.sansFamily
}
}
ToolButton {
text: "↗"
visible: !!modelData.url
onClicked: controller.openUrl(modelData.url)
ToolTip.visible: hovered
ToolTip.text: "Open in " + modelData.source_label
}
ToolButton {
text: "▶"
onClicked: { controller.attachTask(modelData.id); controller.startWork(); window.page = 0 }
ToolTip.visible: hovered
ToolTip.text: "Start a pomodoro on this"
}
GhostButton {
text: modelData.pinned ? "Unpin" : "Pin"
implicitHeight: 28
onClicked: controller.setTaskPinned(modelData.id, !modelData.pinned)
}
GhostButton {
visible: !!modelData.url
text: "Open"
implicitHeight: 28
onClicked: controller.openUrl(modelData.url)
}
GhostButton {
text: "Focus"
implicitHeight: 28
onClicked: {
controller.attachTask(modelData.id)
controller.startWork()
window.page = 0
}
}
}
}
}
Card {
visible: controller.tasks.length === 0
title: "Nothing on the list"
subtitle: "Connect Jira, Todoist or Google Tasks in Settings → Sources, "
+ "or add something here yourself."
Button {
text: "Open settings"
onClicked: window.page = 4
}
}
}
}
Panel {
visible: controller.tasks.length === 0
ColumnLayout {
spacing: 8
Label {
text: "Nothing on the list"
color: Theme.ink
font.pixelSize: 17
font.family: Theme.displayFamily
}
Label {
text: "Connect Jira, Todoist or Google Tasks in Settings, or add something yourself."
color: Theme.muted
font.pixelSize: 12
font.family: Theme.sansFamily
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
GhostButton { text: "Open settings"; onClicked: window.page = 4 }
}
}
Item { Layout.preferredHeight: 10 }
}
Dialog {
@@ -153,7 +200,7 @@ Item {
ColumnLayout {
anchors.fill: parent
spacing: 10
spacing: 12
TextField {
id: titleField
Layout.fillWidth: true
@@ -162,8 +209,15 @@ Item {
TextField {
id: dueField
Layout.fillWidth: true
placeholderText: "Due date (YYYY-MM-DD, optional)"
inputMask: ""
placeholderText: "Due date — YYYY-MM-DD (optional)"
}
Label {
text: "Local tasks live only here, so they are the one kind you can delete."
color: Theme.faint
font.pixelSize: 11
font.family: Theme.sansFamily
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
PrimaryButton {
text: "Add"