- 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.
235 lines
9.1 KiB
QML
235 lines
9.1 KiB
QML
// 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
|
|
import QtQuick.Layouts
|
|
|
|
import "."
|
|
import "components"
|
|
|
|
ScrollView {
|
|
id: page
|
|
contentWidth: availableWidth
|
|
clip: true
|
|
|
|
function bucketTasks(bucket) {
|
|
var out = []
|
|
for (var i = 0; i < controller.tasks.length; i++) {
|
|
var task = controller.tasks[i]
|
|
if (!task.is_done && task.bucket === bucket) out.push(task)
|
|
}
|
|
return out
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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() }
|
|
}
|
|
|
|
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
|
|
|
|
SectionTitle { text: modelData.label; trailing: "" + parent.items.length }
|
|
|
|
Repeater {
|
|
model: parent.items
|
|
delegate: Item {
|
|
required property var modelData
|
|
Layout.fillWidth: true
|
|
implicitHeight: row.implicitHeight + 20
|
|
|
|
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: 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
|
|
}
|
|
RowLayout {
|
|
spacing: 12
|
|
Label {
|
|
text: modelData.source_label
|
|
color: Theme.faint; font.pixelSize: 11
|
|
font.family: Theme.sansFamily
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
id: addDialog
|
|
anchors.centerIn: Overlay.overlay
|
|
width: 380
|
|
modal: true
|
|
title: "New task"
|
|
standardButtons: Dialog.Cancel
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
spacing: 12
|
|
TextField {
|
|
id: titleField
|
|
Layout.fillWidth: true
|
|
placeholderText: "What needs doing?"
|
|
}
|
|
TextField {
|
|
id: dueField
|
|
Layout.fillWidth: true
|
|
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"
|
|
enabled: titleField.text.trim() !== ""
|
|
Layout.alignment: Qt.AlignRight
|
|
onClicked: {
|
|
controller.createTask(titleField.text.trim(), dueField.text.trim())
|
|
titleField.text = ""; dueField.text = ""
|
|
addDialog.close()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|