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,25 +1,32 @@
|
||||
// The countdown ring, drawn with Canvas.
|
||||
// The dial.
|
||||
//
|
||||
// A hero number inside a progress arc: the number is what you read, the arc is
|
||||
// the glance. The arc colour is the only thing that says focus vs break.
|
||||
// A hairline ring with one tick per minute inside it: the ticks light up as the
|
||||
// minutes are spent, so the dial reads as "how much is left" at a glance and as
|
||||
// "how long was this?" on a second look. The time is the hero, set in the
|
||||
// display serif.
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Controls
|
||||
|
||||
import ".."
|
||||
|
||||
Item {
|
||||
id: dial
|
||||
property real progress: 0 // 0..1 elapsed
|
||||
property real progress: 0 // 0..1 elapsed
|
||||
property string clockText: "00:00"
|
||||
property string label: ""
|
||||
property string sublabel: ""
|
||||
property color accent: "#2a78d6"
|
||||
property color accent: Theme.work
|
||||
property bool paused: false
|
||||
property bool idle: false
|
||||
property int minutes: 25
|
||||
|
||||
implicitWidth: 260
|
||||
implicitHeight: 260
|
||||
implicitWidth: 280
|
||||
implicitHeight: 280
|
||||
|
||||
onProgressChanged: canvas.requestPaint()
|
||||
onAccentChanged: canvas.requestPaint()
|
||||
onPausedChanged: canvas.requestPaint()
|
||||
onIdleChanged: canvas.requestPaint()
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
@@ -29,59 +36,85 @@ Item {
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
var centre = Math.min(width, height) / 2
|
||||
var stroke = Math.max(10, centre * 0.11)
|
||||
var radius = centre - stroke
|
||||
|
||||
ctx.lineCap = "round"
|
||||
ctx.lineWidth = stroke
|
||||
var c = Math.min(width, height) / 2
|
||||
var radius = c - 26
|
||||
|
||||
// The ring.
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.strokeStyle = Theme.rule
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = Qt.rgba(0.5, 0.5, 0.5, 0.18)
|
||||
ctx.arc(centre, centre, radius, 0, Math.PI * 2)
|
||||
ctx.arc(c, c, radius, 0, Math.PI * 2)
|
||||
ctx.stroke()
|
||||
|
||||
var remaining = 1 - Math.max(0, Math.min(1, dial.progress))
|
||||
if (remaining > 0) {
|
||||
// Minute ticks, inside the ring.
|
||||
var count = dial.minutes <= 60 ? Math.max(1, dial.minutes) : Math.round(dial.minutes / 5)
|
||||
var spent = Math.floor(count * Math.max(0, Math.min(1, dial.progress)))
|
||||
for (var i = 0; i < count; i++) {
|
||||
var angle = (i / count) * Math.PI * 2 - Math.PI / 2
|
||||
var major = i % 5 === 0
|
||||
var outer = radius - 7
|
||||
var inner = outer - (major ? 9 : 5)
|
||||
ctx.beginPath()
|
||||
ctx.globalAlpha = dial.paused ? 0.45 : 1
|
||||
ctx.strokeStyle = dial.accent
|
||||
ctx.arc(centre, centre, radius, -Math.PI / 2, -Math.PI / 2 + remaining * Math.PI * 2)
|
||||
ctx.lineWidth = major ? 1.4 : 1
|
||||
ctx.strokeStyle = (i < spent && !dial.idle) ? dial.accent : Theme.rule
|
||||
ctx.moveTo(c + inner * Math.cos(angle), c + inner * Math.sin(angle))
|
||||
ctx.lineTo(c + outer * Math.cos(angle), c + outer * Math.sin(angle))
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// The remaining arc.
|
||||
if (!dial.idle) {
|
||||
var remaining = 1 - Math.max(0, Math.min(1, dial.progress))
|
||||
if (remaining > 0) {
|
||||
ctx.beginPath()
|
||||
ctx.lineWidth = 2.5
|
||||
ctx.lineCap = "round"
|
||||
ctx.globalAlpha = dial.paused ? 0.4 : 1
|
||||
ctx.strokeStyle = dial.accent
|
||||
ctx.arc(c, c, radius, -Math.PI / 2, -Math.PI / 2 + remaining * Math.PI * 2)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
width: parent.width * 0.72
|
||||
spacing: 6
|
||||
width: parent.width * 0.7
|
||||
|
||||
Label {
|
||||
width: parent.width
|
||||
text: dial.label.toUpperCase()
|
||||
font.pixelSize: 11
|
||||
font.letterSpacing: 1.5
|
||||
opacity: 0.65
|
||||
color: dial.idle ? Theme.muted : dial.accent
|
||||
font.pixelSize: 10
|
||||
font.letterSpacing: 1.4
|
||||
font.weight: Font.DemiBold
|
||||
font.family: Theme.sansFamily
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
Label {
|
||||
width: parent.width
|
||||
text: dial.clockText
|
||||
font.pixelSize: Math.round(dial.width * 0.2)
|
||||
font.weight: Font.Light
|
||||
font.family: "Roboto Mono, monospace"
|
||||
opacity: dial.paused ? 0.6 : 1
|
||||
color: Theme.ink
|
||||
opacity: dial.paused ? 0.55 : 1
|
||||
font.pixelSize: Math.round(dial.width * 0.21)
|
||||
font.family: Theme.displayFamily
|
||||
font.letterSpacing: -1.5
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Behavior on opacity { NumberAnimation { duration: 180 } }
|
||||
}
|
||||
Label {
|
||||
width: parent.width
|
||||
text: dial.sublabel
|
||||
visible: dial.sublabel !== ""
|
||||
font.pixelSize: 13
|
||||
opacity: 0.75
|
||||
elide: Text.ElideRight
|
||||
text: dial.sublabel
|
||||
color: Theme.muted
|
||||
font.pixelSize: 12
|
||||
font.family: Theme.sansFamily
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user