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:
84 files changed
+4501
-2682
No files matched your search
@@ -1,8 +1,9 @@
|
||||
// The day, summarised — the same payload the Telegram message is built from.
|
||||
// The day in numbers — the same payload the Telegram message is built from.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import "."
|
||||
import "components"
|
||||
|
||||
ScrollView {
|
||||
@@ -14,166 +15,214 @@ ScrollView {
|
||||
readonly property var metrics: payload ? payload.metrics : null
|
||||
|
||||
ColumnLayout {
|
||||
width: page.availableWidth
|
||||
spacing: 16
|
||||
width: page.availableWidth - 56
|
||||
x: 28
|
||||
y: 26
|
||||
spacing: 22
|
||||
|
||||
PageHeader {
|
||||
eyebrow: controller.report.sent_at ? "sent to Telegram" : "not sent yet"
|
||||
title: page.payload ? "Report · " + page.payload.date : "Report"
|
||||
caption: page.payload ? page.payload.verdict : ""
|
||||
|
||||
GhostButton { text: "Reload"; onClicked: controller.loadReport() }
|
||||
PrimaryButton { text: "Send to Telegram"; onClicked: controller.sendReport() }
|
||||
}
|
||||
|
||||
Panel {
|
||||
padding: 0
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: [
|
||||
{ label: "Pomodoros",
|
||||
value: page.metrics ? page.metrics.pomodoros + "/" + page.metrics.goal : "—",
|
||||
caption: page.metrics && page.metrics.goal_met ? "goal met" : "goal not met yet",
|
||||
accent: true },
|
||||
{ label: "Focus",
|
||||
value: page.metrics ? page.metrics.focus_minutes + "m" : "—",
|
||||
caption: page.metrics ? page.metrics.break_minutes + "m resting" : "" },
|
||||
{ label: "Score",
|
||||
value: page.payload ? "" + Math.round(page.payload.score) : "—",
|
||||
caption: "out of 100" },
|
||||
{ label: "Streak",
|
||||
value: page.payload ? page.payload.streak.current + "d" : "—",
|
||||
caption: page.payload ? "best " + page.payload.streak.best : "" }
|
||||
]
|
||||
delegate: RowLayout {
|
||||
required property int index
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
Rectangle {
|
||||
visible: index > 0
|
||||
width: 1
|
||||
Layout.preferredHeight: 84
|
||||
color: Theme.rule
|
||||
}
|
||||
Stat {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
label: modelData.label
|
||||
value: modelData.value
|
||||
caption: modelData.caption
|
||||
size: 38
|
||||
accent: modelData.accent ? Theme.work : Theme.ink
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 20
|
||||
spacing: 16
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
text: page.payload ? "Report for " + page.payload.date : "Report"
|
||||
font.pixelSize: 20
|
||||
font.weight: Font.Medium
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button { text: "⟳"; flat: true; onClicked: controller.loadReport() }
|
||||
PrimaryButton { text: "Send to Telegram"; onClicked: controller.sendReport() }
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
columns: page.width > 760 ? 4 : 2
|
||||
columnSpacing: 12
|
||||
rowSpacing: 12
|
||||
|
||||
StatTile {
|
||||
label: "Pomodoros"
|
||||
value: page.metrics ? page.metrics.pomodoros + "/" + page.metrics.goal : "—"
|
||||
caption: page.metrics && page.metrics.goal_met ? "goal met 🎯" : "keep going"
|
||||
accent: window.workColor
|
||||
}
|
||||
StatTile {
|
||||
label: "Focus"
|
||||
value: page.metrics ? page.metrics.focus_minutes + "m" : "—"
|
||||
caption: page.metrics ? page.metrics.break_minutes + "m resting" : ""
|
||||
}
|
||||
StatTile {
|
||||
label: "Score"
|
||||
value: page.payload ? Math.round(page.payload.score) : "—"
|
||||
caption: "out of 100"
|
||||
}
|
||||
StatTile {
|
||||
label: "Streak"
|
||||
value: page.payload ? page.payload.streak.current + "d" : "—"
|
||||
caption: page.payload ? "best " + page.payload.streak.best : ""
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
title: "Last 30 days"
|
||||
subtitle: "Goal line is the dashed row; darker bars cleared it."
|
||||
spacing: 10
|
||||
SectionTitle { text: "Last 30 days"; trailing: "goal line dashed" }
|
||||
Panel {
|
||||
BarRow {
|
||||
Layout.fillWidth: true
|
||||
days: controller.stats.range ? controller.stats.range : []
|
||||
goal: controller.goal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
visible: page.payload && page.payload.achievements.length > 0
|
||||
title: "Achievements"
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
Repeater {
|
||||
model: page.payload ? page.payload.achievements : []
|
||||
delegate: Pane {
|
||||
required property var modelData
|
||||
Material.elevation: 0
|
||||
Material.roundedScale: Material.SmallScale
|
||||
padding: 10
|
||||
background: Rectangle {
|
||||
radius: 10
|
||||
color: Material.accent
|
||||
opacity: 0.14
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: page.payload && page.payload.achievements.length > 0
|
||||
spacing: 10
|
||||
SectionTitle { text: "Achievements" }
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
Repeater {
|
||||
model: page.payload ? page.payload.achievements : []
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
implicitWidth: badge.implicitWidth + 24
|
||||
implicitHeight: badge.implicitHeight + 18
|
||||
radius: 10
|
||||
color: Qt.alpha(Theme.ember, 0.08)
|
||||
border.width: 1
|
||||
border.color: Qt.alpha(Theme.ember, 0.22)
|
||||
|
||||
ColumnLayout {
|
||||
id: badge
|
||||
anchors.centerIn: parent
|
||||
spacing: 1
|
||||
Label {
|
||||
text: modelData.emoji + " " + modelData.title
|
||||
color: Theme.ink
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.DemiBold
|
||||
font.family: Theme.sansFamily
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 1
|
||||
Label {
|
||||
text: modelData.emoji + " " + modelData.title
|
||||
font.weight: Font.DemiBold
|
||||
font.pixelSize: 13
|
||||
}
|
||||
Label {
|
||||
text: modelData.detail
|
||||
visible: modelData.detail !== ""
|
||||
font.pixelSize: 11
|
||||
opacity: 0.75
|
||||
}
|
||||
Label {
|
||||
visible: modelData.detail !== ""
|
||||
text: modelData.detail
|
||||
color: Theme.muted
|
||||
font.pixelSize: 11
|
||||
font.family: Theme.sansFamily
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
visible: page.payload && page.payload.fun_facts.length > 0
|
||||
title: "The funny part"
|
||||
Repeater {
|
||||
model: page.payload ? page.payload.fun_facts : []
|
||||
delegate: Label {
|
||||
required property var modelData
|
||||
text: modelData
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
visible: page.metrics && page.metrics.tasks_completed.length > 0
|
||||
title: "Closed today"
|
||||
Repeater {
|
||||
model: page.metrics ? page.metrics.tasks_completed : []
|
||||
delegate: RowLayout {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
Label { text: "✅"; font.pixelSize: 12 }
|
||||
Label { text: modelData.title; Layout.fillWidth: true; elide: Text.ElideRight }
|
||||
Label { text: modelData.source; font.pixelSize: 11; opacity: 0.6 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
title: "All time"
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: page.payload && page.payload.fun_facts.length > 0
|
||||
spacing: 8
|
||||
SectionTitle { text: "The funny part" }
|
||||
Repeater {
|
||||
model: page.payload ? page.payload.fun_facts : []
|
||||
delegate: Label {
|
||||
required property var modelData
|
||||
text: modelData
|
||||
color: Theme.ink
|
||||
font.pixelSize: 13
|
||||
font.family: Theme.sansFamily
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
StatTile {
|
||||
label: "Pomodoros"
|
||||
value: controller.stats.lifetime ? controller.stats.lifetime.pomodoros : "—"
|
||||
}
|
||||
StatTile {
|
||||
label: "Hours focused"
|
||||
value: controller.stats.lifetime ? controller.stats.lifetime.focus_hours : "—"
|
||||
}
|
||||
StatTile {
|
||||
label: "Tasks closed"
|
||||
value: controller.stats.lifetime ? controller.stats.lifetime.tasks_completed : "—"
|
||||
}
|
||||
StatTile {
|
||||
label: "Blocked"
|
||||
value: controller.stats.lifetime ? controller.stats.lifetime.blocked_attempts : "—"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: page.payload && page.payload.verdict !== ""
|
||||
text: page.payload ? page.payload.verdict : ""
|
||||
font.italic: true
|
||||
font.pixelSize: 14
|
||||
opacity: 0.85
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: page.metrics && page.metrics.tasks_completed.length > 0
|
||||
spacing: 6
|
||||
SectionTitle { text: "Closed today" }
|
||||
Repeater {
|
||||
model: page.metrics ? page.metrics.tasks_completed : []
|
||||
delegate: RowLayout {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
Label {
|
||||
text: modelData.title
|
||||
color: Theme.ink
|
||||
font.pixelSize: 13
|
||||
font.family: Theme.sansFamily
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: modelData.source
|
||||
color: Theme.faint
|
||||
font.pixelSize: 11
|
||||
font.family: Theme.sansFamily
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
SectionTitle { text: "All time" }
|
||||
Panel {
|
||||
padding: 0
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: [
|
||||
{ label: "Pomodoros",
|
||||
value: controller.stats.lifetime ? "" + controller.stats.lifetime.pomodoros : "—" },
|
||||
{ label: "Hours focused",
|
||||
value: controller.stats.lifetime ? "" + controller.stats.lifetime.focus_hours : "—" },
|
||||
{ label: "Tasks closed",
|
||||
value: controller.stats.lifetime ? "" + controller.stats.lifetime.tasks_completed : "—" },
|
||||
{ label: "Blocked",
|
||||
value: controller.stats.lifetime ? "" + controller.stats.lifetime.blocked_attempts : "—" }
|
||||
]
|
||||
delegate: RowLayout {
|
||||
required property int index
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
Rectangle {
|
||||
visible: index > 0
|
||||
width: 1
|
||||
Layout.preferredHeight: 64
|
||||
color: Theme.rule
|
||||
}
|
||||
Stat {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 18
|
||||
label: modelData.label
|
||||
value: modelData.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 10 }
|
||||
}
|
||||
|
||||
Component.onCompleted: controller.loadReport()
|
||||
|
||||
Reference in new issue
Block a user