shell: own the right sidebar, move garden into the drawer
Garden was welded onto the notification list in CenterWidgetGroup — always on, unrelated, stealing space from notifications that said "Nothing". Moved it into the BottomWidgetGroup drawer as a fifth tab alongside Calendar, To Do, Pomodoro, and Stopwatch. CenterWidgetGroup is now just the notification list. Lens events (note_created, synced) emit through notify-send so they land in the notification system properly. Forked calendar, todo, pomodoro, notifications out of ii-base into our tree — the right sidebar no longer borrows any widget files from ii. Toggle dialogs (bluetooth, wifi, nightLight) still symlinked as infrastructure.
This commit is contained in:
parent
7e6633a290
commit
b8e76c4e9e
22 changed files with 2043 additions and 13 deletions
|
|
@ -136,7 +136,22 @@ modules/ii/sidebarRight/SidebarRight.qml souveraine/modules/ii/sidebarRight/Sid
|
||||||
modules/ii/sidebarRight/SidebarRightContent.qml souveraine/modules/ii/sidebarRight/SidebarRightContent.qml
|
modules/ii/sidebarRight/SidebarRightContent.qml souveraine/modules/ii/sidebarRight/SidebarRightContent.qml
|
||||||
modules/ii/sidebarRight/QuickSliders.qml souveraine/modules/ii/sidebarRight/QuickSliders.qml
|
modules/ii/sidebarRight/QuickSliders.qml souveraine/modules/ii/sidebarRight/QuickSliders.qml
|
||||||
modules/ii/sidebarRight/CenterWidgetGroup.qml souveraine/modules/ii/sidebarRight/CenterWidgetGroup.qml
|
modules/ii/sidebarRight/CenterWidgetGroup.qml souveraine/modules/ii/sidebarRight/CenterWidgetGroup.qml
|
||||||
|
modules/ii/sidebarRight/BottomWidgetGroup.qml souveraine/modules/ii/sidebarRight/BottomWidgetGroup.qml
|
||||||
modules/ii/sidebarRight/lens/LensWidget.qml souveraine/modules/ii/sidebarRight/lens/LensWidget.qml
|
modules/ii/sidebarRight/lens/LensWidget.qml souveraine/modules/ii/sidebarRight/lens/LensWidget.qml
|
||||||
|
modules/ii/sidebarRight/calendar/CalendarWidget.qml souveraine/modules/ii/sidebarRight/calendar/CalendarWidget.qml
|
||||||
|
modules/ii/sidebarRight/calendar/CalendarDayButton.qml souveraine/modules/ii/sidebarRight/calendar/CalendarDayButton.qml
|
||||||
|
modules/ii/sidebarRight/calendar/CalendarHeaderButton.qml souveraine/modules/ii/sidebarRight/calendar/CalendarHeaderButton.qml
|
||||||
|
modules/ii/sidebarRight/calendar/calendar_layout.js souveraine/modules/ii/sidebarRight/calendar/calendar_layout.js
|
||||||
|
modules/ii/sidebarRight/todo/TodoWidget.qml souveraine/modules/ii/sidebarRight/todo/TodoWidget.qml
|
||||||
|
modules/ii/sidebarRight/todo/TaskList.qml souveraine/modules/ii/sidebarRight/todo/TaskList.qml
|
||||||
|
modules/ii/sidebarRight/todo/TodoItemActionButton.qml souveraine/modules/ii/sidebarRight/todo/TodoItemActionButton.qml
|
||||||
|
modules/ii/sidebarRight/pomodoro/PomodoroWidget.qml souveraine/modules/ii/sidebarRight/pomodoro/PomodoroWidget.qml
|
||||||
|
modules/ii/sidebarRight/pomodoro/StopwatchWidget.qml souveraine/modules/ii/sidebarRight/pomodoro/StopwatchWidget.qml
|
||||||
|
modules/ii/sidebarRight/pomodoro/PomodoroTimer.qml souveraine/modules/ii/sidebarRight/pomodoro/PomodoroTimer.qml
|
||||||
|
modules/ii/sidebarRight/pomodoro/PomodoroSettings.qml souveraine/modules/ii/sidebarRight/pomodoro/PomodoroSettings.qml
|
||||||
|
modules/ii/sidebarRight/pomodoro/Stopwatch.qml souveraine/modules/ii/sidebarRight/pomodoro/Stopwatch.qml
|
||||||
|
modules/ii/sidebarRight/notifications/NotificationList.qml souveraine/modules/ii/sidebarRight/notifications/NotificationList.qml
|
||||||
|
modules/ii/sidebarRight/notifications/NotificationStatusButton.qml souveraine/modules/ii/sidebarRight/notifications/NotificationStatusButton.qml
|
||||||
modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml souveraine/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml
|
modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml souveraine/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml
|
||||||
modules/ii/bar/UtilButtons.qml souveraine/modules/ii/bar/UtilButtons.qml
|
modules/ii/bar/UtilButtons.qml souveraine/modules/ii/bar/UtilButtons.qml
|
||||||
modules/ii/bar/BarContent.qml souveraine/modules/ii/bar/BarContent.qml
|
modules/ii/bar/BarContent.qml souveraine/modules/ii/bar/BarContent.qml
|
||||||
|
|
@ -215,6 +230,10 @@ modules/ii/sessionScreen/qmldir souveraine/modules/ii/sessionScreen/qml
|
||||||
modules/ii/sidebarLeft/qmldir souveraine/modules/ii/sidebarLeft/qmldir
|
modules/ii/sidebarLeft/qmldir souveraine/modules/ii/sidebarLeft/qmldir
|
||||||
modules/ii/sidebarRight/qmldir souveraine/modules/ii/sidebarRight/qmldir
|
modules/ii/sidebarRight/qmldir souveraine/modules/ii/sidebarRight/qmldir
|
||||||
modules/ii/sidebarRight/volumeMixer/qmldir souveraine/modules/ii/sidebarRight/volumeMixer/qmldir
|
modules/ii/sidebarRight/volumeMixer/qmldir souveraine/modules/ii/sidebarRight/volumeMixer/qmldir
|
||||||
|
modules/ii/sidebarRight/calendar/qmldir souveraine/modules/ii/sidebarRight/calendar/qmldir
|
||||||
|
modules/ii/sidebarRight/todo/qmldir souveraine/modules/ii/sidebarRight/todo/qmldir
|
||||||
|
modules/ii/sidebarRight/pomodoro/qmldir souveraine/modules/ii/sidebarRight/pomodoro/qmldir
|
||||||
|
modules/ii/sidebarRight/notifications/qmldir souveraine/modules/ii/sidebarRight/notifications/qmldir
|
||||||
modules/settings/qmldir souveraine/modules/settings/qmldir
|
modules/settings/qmldir souveraine/modules/settings/qmldir
|
||||||
"
|
"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,294 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.ii.sidebarRight.calendar
|
||||||
|
import qs.modules.ii.sidebarRight.todo
|
||||||
|
import qs.modules.ii.sidebarRight.pomodoro
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
color: Appearance.colors.colLayer1
|
||||||
|
clip: true
|
||||||
|
implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : 430
|
||||||
|
property int selectedTab: Persistent.states.sidebar.bottomGroup.tab
|
||||||
|
property int previousIndex: -1
|
||||||
|
property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed
|
||||||
|
readonly property int collapsedHeight: Math.max(
|
||||||
|
Appearance.font.pixelSize.larger + 24,
|
||||||
|
Appearance.font.pixelSize.large + 24
|
||||||
|
)
|
||||||
|
property var tabs: [
|
||||||
|
{
|
||||||
|
"type": "calendar",
|
||||||
|
"name": Translation.tr("Calendar"),
|
||||||
|
"icon": "calendar_month",
|
||||||
|
"widget": "calendar/CalendarWidget.qml"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "todo",
|
||||||
|
"name": Translation.tr("To Do"),
|
||||||
|
"icon": "done_outline",
|
||||||
|
"widget": "todo/TodoWidget.qml"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pomodoro",
|
||||||
|
"name": Translation.tr("Pomodoro"),
|
||||||
|
"icon": "search_activity",
|
||||||
|
"widget": "pomodoro/PomodoroWidget.qml"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "stopwatch",
|
||||||
|
"name": Translation.tr("Stopwatch"),
|
||||||
|
"icon": "timer",
|
||||||
|
"widget": "pomodoro/StopwatchWidget.qml"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "garden",
|
||||||
|
"name": Translation.tr("Garden"),
|
||||||
|
"icon": "hub",
|
||||||
|
"widget": "lens/LensWidget.qml"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.animation.elementMove.duration
|
||||||
|
easing.type: Appearance.animation.elementMove.type
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCollapsed(state) {
|
||||||
|
Persistent.states.sidebar.bottomGroup.collapsed = state;
|
||||||
|
if (collapsed) {
|
||||||
|
bottomWidgetGroupRow.opacity = 0;
|
||||||
|
} else {
|
||||||
|
collapsedBottomWidgetGroupRow.opacity = 0;
|
||||||
|
}
|
||||||
|
collapseCleanFadeTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Persistent.states.sidebar.bottomGroup
|
||||||
|
|
||||||
|
function onTabChanged() {
|
||||||
|
root.selectedTab = Persistent.states.sidebar.bottomGroup.tab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: collapseCleanFadeTimer
|
||||||
|
interval: Appearance.animation.elementMove.duration / 2
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (collapsed)
|
||||||
|
collapsedBottomWidgetGroupRow.opacity = 1;
|
||||||
|
else
|
||||||
|
bottomWidgetGroupRow.opacity = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.ControlModifier) {
|
||||||
|
if (event.key === Qt.Key_PageDown) {
|
||||||
|
root.selectedTab = Math.min(root.selectedTab + 1, root.tabs.length - 1);
|
||||||
|
} else if (event.key === Qt.Key_PageUp) {
|
||||||
|
root.selectedTab = Math.max(root.selectedTab - 1, 0);
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: collapsedBottomWidgetGroupRow
|
||||||
|
opacity: collapsed ? 1 : 0
|
||||||
|
visible: opacity > 0
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
id: collapsedBottomWidgetGroupRowFade
|
||||||
|
duration: Appearance.animation.elementMove.duration / 2
|
||||||
|
easing.type: Appearance.animation.elementMove.type
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing: 15
|
||||||
|
|
||||||
|
CalendarHeaderButton {
|
||||||
|
Layout.margins: 10
|
||||||
|
Layout.rightMargin: 0
|
||||||
|
forceCircle: true
|
||||||
|
downAction: () => {
|
||||||
|
root.setCollapsed(false);
|
||||||
|
}
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
|
text: "keyboard_arrow_up"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
property int remainingTasks: Todo.list.filter(task => !task.done).length
|
||||||
|
Layout.margins: 10
|
||||||
|
Layout.leftMargin: 0
|
||||||
|
text: Translation.tr("%1 • %2 tasks").arg(DateTime.collapsedCalendarFormat).arg(remainingTasks)
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.large
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: bottomWidgetGroupRow
|
||||||
|
|
||||||
|
opacity: collapsed ? 0 : 1
|
||||||
|
visible: opacity > 0
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
id: bottomWidgetGroupRowFade
|
||||||
|
duration: Appearance.animation.elementMove.duration / 2
|
||||||
|
easing.type: Appearance.animation.elementMove.type
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 20
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: false
|
||||||
|
Layout.leftMargin: 10
|
||||||
|
Layout.topMargin: 10
|
||||||
|
implicitWidth: tabBar.implicitWidth
|
||||||
|
NavigationRailTabArray {
|
||||||
|
id: tabBar
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 5
|
||||||
|
currentIndex: root.selectedTab
|
||||||
|
expanded: false
|
||||||
|
Repeater {
|
||||||
|
model: root.tabs
|
||||||
|
NavigationRailButton {
|
||||||
|
required property int index
|
||||||
|
required property var modelData
|
||||||
|
showToggledHighlight: false
|
||||||
|
toggled: root.selectedTab == index
|
||||||
|
buttonText: modelData.name
|
||||||
|
buttonIcon: modelData.icon
|
||||||
|
onPressed: {
|
||||||
|
root.selectedTab = index;
|
||||||
|
Persistent.states.sidebar.bottomGroup.tab = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CalendarHeaderButton {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
forceCircle: true
|
||||||
|
downAction: () => {
|
||||||
|
root.setCollapsed(true);
|
||||||
|
}
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
|
text: "keyboard_arrow_down"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: tabStack
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.bottomMargin: -anchors.topMargin
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
tabStack.source = root.tabs[root.selectedTab].widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onSelectedTabChanged() {
|
||||||
|
if (root.selectedTab > root.previousIndex)
|
||||||
|
tabSwitchBehavior.animation.down = true;
|
||||||
|
else if (root.selectedTab < root.previousIndex)
|
||||||
|
tabSwitchBehavior.animation.down = false;
|
||||||
|
tabStack.source = root.tabs[root.selectedTab].widget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on source {
|
||||||
|
id: tabSwitchBehavior
|
||||||
|
animation: TabSwitchAnim {
|
||||||
|
id: upAnim
|
||||||
|
down: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component TabSwitchAnim: SequentialAnimation {
|
||||||
|
id: switchAnim
|
||||||
|
property bool down: false
|
||||||
|
ParallelAnimation {
|
||||||
|
PropertyAnimation {
|
||||||
|
target: tabStack
|
||||||
|
properties: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
PropertyAnimation {
|
||||||
|
target: tabStack.anchors
|
||||||
|
properties: "topMargin"
|
||||||
|
to: 10 * (switchAnim.down ? -1 : 1)
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: tabStack
|
||||||
|
property: "source"
|
||||||
|
value: root.tabs[root.selectedTab].widget
|
||||||
|
}
|
||||||
|
ParallelAnimation {
|
||||||
|
PropertyAnimation {
|
||||||
|
target: tabStack.anchors
|
||||||
|
properties: "topMargin"
|
||||||
|
from: 10 * -(switchAnim.down ? -1 : 1)
|
||||||
|
to: 0
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
|
||||||
|
}
|
||||||
|
PropertyAnimation {
|
||||||
|
target: tabStack
|
||||||
|
properties: "opacity"
|
||||||
|
to: 1
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScriptAction {
|
||||||
|
script: {
|
||||||
|
root.previousIndex = root.selectedTab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ import qs.modules.common
|
||||||
import qs.modules.common.widgets
|
import qs.modules.common.widgets
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.modules.ii.sidebarRight.notifications
|
import qs.modules.ii.sidebarRight.notifications
|
||||||
import qs.modules.ii.sidebarRight.lens
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
|
@ -12,19 +11,8 @@ Rectangle {
|
||||||
radius: Appearance.rounding.normal
|
radius: Appearance.rounding.normal
|
||||||
color: Appearance.colors.colLayer1
|
color: Appearance.colors.colLayer1
|
||||||
|
|
||||||
ColumnLayout {
|
NotificationList {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 5
|
anchors.margins: 5
|
||||||
spacing: 5
|
|
||||||
|
|
||||||
LensWidget {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationList {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
id: button
|
||||||
|
property string day
|
||||||
|
property int isToday
|
||||||
|
property bool bold
|
||||||
|
|
||||||
|
Layout.fillWidth: false
|
||||||
|
Layout.fillHeight: false
|
||||||
|
implicitWidth: 38;
|
||||||
|
implicitHeight: 38;
|
||||||
|
|
||||||
|
toggled: (isToday == 1)
|
||||||
|
buttonRadius: Appearance.rounding.small
|
||||||
|
|
||||||
|
contentItem: StyledText {
|
||||||
|
anchors.fill: parent
|
||||||
|
text: day
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.weight: bold ? Font.DemiBold : Font.Normal
|
||||||
|
color: (isToday == 1) ? Appearance.m3colors.m3onPrimary :
|
||||||
|
(isToday == 0) ? Appearance.colors.colOnLayer1 :
|
||||||
|
Appearance.colors.colOutlineVariant
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
id: button
|
||||||
|
property string buttonText: ""
|
||||||
|
property string tooltipText: ""
|
||||||
|
property bool forceCircle: false
|
||||||
|
|
||||||
|
implicitHeight: 30
|
||||||
|
implicitWidth: forceCircle ? implicitHeight : (contentItem.implicitWidth + 10 * 2)
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
SmoothedAnimation {
|
||||||
|
velocity: Appearance.animation.elementMove.velocity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background.anchors.fill: button
|
||||||
|
buttonRadius: Appearance.rounding.full
|
||||||
|
colBackground: Appearance.colors.colLayer2
|
||||||
|
colBackgroundHover: Appearance.colors.colLayer2Hover
|
||||||
|
colRipple: Appearance.colors.colLayer2Active
|
||||||
|
|
||||||
|
contentItem: StyledText {
|
||||||
|
text: buttonText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledToolTip {
|
||||||
|
text: tooltipText
|
||||||
|
extraVisibleCondition: tooltipText.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import "calendar_layout.js" as CalendarLayout
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
// Layout.topMargin: 10
|
||||||
|
anchors.topMargin: 10
|
||||||
|
property int monthShift: 0
|
||||||
|
property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift)
|
||||||
|
property var calendarLayout: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0)
|
||||||
|
width: calendarColumn.width
|
||||||
|
implicitHeight: calendarColumn.height + 10 * 2
|
||||||
|
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp)
|
||||||
|
&& event.modifiers === Qt.NoModifier) {
|
||||||
|
if (event.key === Qt.Key_PageDown) {
|
||||||
|
monthShift++;
|
||||||
|
} else if (event.key === Qt.Key_PageUp) {
|
||||||
|
monthShift--;
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onWheel: (event) => {
|
||||||
|
if (event.angleDelta.y > 0) {
|
||||||
|
monthShift--;
|
||||||
|
} else if (event.angleDelta.y < 0) {
|
||||||
|
monthShift++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: calendarColumn
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
// Calendar header
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 5
|
||||||
|
CalendarHeaderButton {
|
||||||
|
clip: true
|
||||||
|
buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}`
|
||||||
|
tooltipText: (monthShift === 0) ? "" : Translation.tr("Jump to current month")
|
||||||
|
downAction: () => {
|
||||||
|
monthShift = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: false
|
||||||
|
}
|
||||||
|
CalendarHeaderButton {
|
||||||
|
forceCircle: true
|
||||||
|
downAction: () => {
|
||||||
|
monthShift--;
|
||||||
|
}
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
|
text: "chevron_left"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CalendarHeaderButton {
|
||||||
|
forceCircle: true
|
||||||
|
downAction: () => {
|
||||||
|
monthShift++;
|
||||||
|
}
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
|
text: "chevron_right"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Week days row
|
||||||
|
RowLayout {
|
||||||
|
id: weekDaysRow
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.fillHeight: false
|
||||||
|
spacing: 5
|
||||||
|
Repeater {
|
||||||
|
model: CalendarLayout.weekDays
|
||||||
|
delegate: CalendarDayButton {
|
||||||
|
day: Translation.tr(modelData.day)
|
||||||
|
isToday: modelData.today
|
||||||
|
bold: true
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real week rows
|
||||||
|
Repeater {
|
||||||
|
id: calendarRows
|
||||||
|
// model: calendarLayout
|
||||||
|
model: 6
|
||||||
|
delegate: RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.fillHeight: false
|
||||||
|
spacing: 5
|
||||||
|
Repeater {
|
||||||
|
model: Array(7).fill(modelData)
|
||||||
|
delegate: CalendarDayButton {
|
||||||
|
day: calendarLayout[modelData][index].day
|
||||||
|
isToday: calendarLayout[modelData][index].today
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
const weekDays = [ // MONDAY IS THE FIRST DAY OF THE WEEK :HESRIGHTYOUKNOW:
|
||||||
|
{ day: 'Mo', today: 0 },
|
||||||
|
{ day: 'Tu', today: 0 },
|
||||||
|
{ day: 'We', today: 0 },
|
||||||
|
{ day: 'Th', today: 0 },
|
||||||
|
{ day: 'Fr', today: 0 },
|
||||||
|
{ day: 'Sa', today: 0 },
|
||||||
|
{ day: 'Su', today: 0 },
|
||||||
|
]
|
||||||
|
|
||||||
|
function checkLeapYear(year) {
|
||||||
|
return (
|
||||||
|
year % 400 == 0 ||
|
||||||
|
(year % 4 == 0 && year % 100 != 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMonthDays(month, year) {
|
||||||
|
const leapYear = checkLeapYear(year);
|
||||||
|
if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 31;
|
||||||
|
if (month == 2 && leapYear) return 29;
|
||||||
|
if (month == 2 && !leapYear) return 28;
|
||||||
|
return 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextMonthDays(month, year) {
|
||||||
|
const leapYear = checkLeapYear(year);
|
||||||
|
if (month == 1 && leapYear) return 29;
|
||||||
|
if (month == 1 && !leapYear) return 28;
|
||||||
|
if (month == 12) return 31;
|
||||||
|
if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;
|
||||||
|
return 31;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrevMonthDays(month, year) {
|
||||||
|
const leapYear = checkLeapYear(year);
|
||||||
|
if (month == 3 && leapYear) return 29;
|
||||||
|
if (month == 3 && !leapYear) return 28;
|
||||||
|
if (month == 1) return 31;
|
||||||
|
if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;
|
||||||
|
return 31;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDateInXMonthsTime(x) {
|
||||||
|
var currentDate = new Date(); // Get the current date
|
||||||
|
if (x == 0) return currentDate; // If x is 0, return the current date
|
||||||
|
|
||||||
|
var targetMonth = currentDate.getMonth() + x; // Calculate the target month
|
||||||
|
var targetYear = currentDate.getFullYear(); // Get the current year
|
||||||
|
|
||||||
|
// Adjust the year and month if necessary
|
||||||
|
targetYear += Math.floor(targetMonth / 12);
|
||||||
|
targetMonth = (targetMonth % 12 + 12) % 12;
|
||||||
|
|
||||||
|
// Create a new date object with the target year and month
|
||||||
|
var targetDate = new Date(targetYear, targetMonth, 1);
|
||||||
|
|
||||||
|
// Set the day to the last day of the month to get the desired date
|
||||||
|
// targetDate.setDate(0);
|
||||||
|
|
||||||
|
return targetDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCalendarLayout(dateObject, highlight) {
|
||||||
|
if (!dateObject) dateObject = new Date();
|
||||||
|
const weekday = (dateObject.getDay() + 6) % 7; // MONDAY IS THE FIRST DAY OF THE WEEK
|
||||||
|
const day = dateObject.getDate();
|
||||||
|
const month = dateObject.getMonth() + 1;
|
||||||
|
const year = dateObject.getFullYear();
|
||||||
|
const weekdayOfMonthFirst = (weekday + 35 - (day - 1)) % 7;
|
||||||
|
const daysInMonth = getMonthDays(month, year);
|
||||||
|
const daysInNextMonth = getNextMonthDays(month, year);
|
||||||
|
const daysInPrevMonth = getPrevMonthDays(month, year);
|
||||||
|
|
||||||
|
// Fill
|
||||||
|
var monthDiff = (weekdayOfMonthFirst == 0 ? 0 : -1);
|
||||||
|
var toFill, dim;
|
||||||
|
if (weekdayOfMonthFirst == 0) {
|
||||||
|
toFill = 1;
|
||||||
|
dim = daysInMonth;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toFill = (daysInPrevMonth - (weekdayOfMonthFirst - 1));
|
||||||
|
dim = daysInPrevMonth;
|
||||||
|
}
|
||||||
|
var calendar = [...Array(6)].map(() => Array(7));
|
||||||
|
var i = 0, j = 0;
|
||||||
|
while (i < 6 && j < 7) {
|
||||||
|
calendar[i][j] = {
|
||||||
|
"day": toFill,
|
||||||
|
"today": ((toFill == day && monthDiff == 0 && highlight) ? 1 : (
|
||||||
|
monthDiff == 0 ? 0 : -1
|
||||||
|
))
|
||||||
|
};
|
||||||
|
// Increment
|
||||||
|
toFill++;
|
||||||
|
if (toFill > dim) { // Next month?
|
||||||
|
monthDiff++;
|
||||||
|
if (monthDiff == 0)
|
||||||
|
dim = daysInMonth;
|
||||||
|
else if (monthDiff == 1)
|
||||||
|
dim = daysInNextMonth;
|
||||||
|
toFill = 1;
|
||||||
|
}
|
||||||
|
// Next tile
|
||||||
|
j++;
|
||||||
|
if (j == 7) {
|
||||||
|
j = 0;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return calendar;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
CalendarDayButton 1.0 CalendarDayButton.qml
|
||||||
|
CalendarHeaderButton 1.0 CalendarHeaderButton.qml
|
||||||
|
CalendarWidget 1.0 CalendarWidget.qml
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import qs.services
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
NotificationListView { // Scrollable window
|
||||||
|
id: listview
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: statusRow.top
|
||||||
|
anchors.bottomMargin: 5
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: listview.width
|
||||||
|
height: listview.height
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
popup: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder when list is empty
|
||||||
|
PagePlaceholder {
|
||||||
|
shown: Notifications.list.length === 0
|
||||||
|
icon: "notifications_active"
|
||||||
|
description: Translation.tr("Nothing")
|
||||||
|
shape: MaterialShape.Shape.Ghostish
|
||||||
|
descriptionHorizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonGroup {
|
||||||
|
id: statusRow
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationStatusButton {
|
||||||
|
Layout.fillWidth: false
|
||||||
|
buttonIcon: "notifications_paused"
|
||||||
|
toggled: Notifications.silent
|
||||||
|
onClicked: () => {
|
||||||
|
Notifications.silent = !Notifications.silent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NotificationStatusButton {
|
||||||
|
enabled: false
|
||||||
|
Layout.fillWidth: true
|
||||||
|
buttonText: Translation.tr("%1 notifications").arg(Notifications.list.length)
|
||||||
|
}
|
||||||
|
NotificationStatusButton {
|
||||||
|
Layout.fillWidth: false
|
||||||
|
buttonIcon: "delete_sweep"
|
||||||
|
onClicked: () => {
|
||||||
|
Notifications.discardAllNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
GroupButton {
|
||||||
|
id: button
|
||||||
|
property string buttonIcon: ""
|
||||||
|
property string buttonText: ""
|
||||||
|
|
||||||
|
baseHeight: 36
|
||||||
|
baseWidth: content.implicitWidth + 46
|
||||||
|
clickedWidth: baseWidth + 6
|
||||||
|
|
||||||
|
buttonRadius: baseHeight / 2
|
||||||
|
buttonRadiusPressed: Appearance.rounding.small
|
||||||
|
colBackground: Appearance.colors.colLayer2
|
||||||
|
colBackgroundHover: Appearance.colors.colLayer2Hover
|
||||||
|
colBackgroundActive: Appearance.colors.colLayer2Active
|
||||||
|
property color colText: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
id: content
|
||||||
|
anchors.fill: parent
|
||||||
|
implicitWidth: contentRowLayout.implicitWidth
|
||||||
|
implicitHeight: contentRowLayout.implicitHeight
|
||||||
|
RowLayout {
|
||||||
|
id: contentRowLayout
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 5
|
||||||
|
MaterialSymbol {
|
||||||
|
visible: buttonIcon !== ""
|
||||||
|
text: buttonIcon
|
||||||
|
iconSize: Appearance.font.pixelSize.huge
|
||||||
|
color: button.colText
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
visible: buttonText !== ""
|
||||||
|
text: buttonText
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
color: button.colText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
NotificationList 1.0 NotificationList.qml
|
||||||
|
NotificationStatusButton 1.0 NotificationStatusButton.qml
|
||||||
|
|
@ -0,0 +1,368 @@
|
||||||
|
import qs
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
StyledFlickable {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: settingsColumn.implicitHeight
|
||||||
|
|
||||||
|
function setMinutes(optionName, minutes) {
|
||||||
|
Config.options.time.pomodoro[optionName] = Math.max(1, minutes) * 60;
|
||||||
|
if (!TimerService.pomodoroRunning) {
|
||||||
|
TimerService.resetPomodoro();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function minutes(optionName) {
|
||||||
|
return Math.round(Config.options.time.pomodoro[optionName] / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPreset(focus, shortBreak, longBreak, cycles) {
|
||||||
|
Config.options.time.pomodoro.focus = focus * 60;
|
||||||
|
Config.options.time.pomodoro.breakTime = shortBreak * 60;
|
||||||
|
Config.options.time.pomodoro.longBreak = longBreak * 60;
|
||||||
|
Config.options.time.pomodoro.cyclesBeforeLongBreak = cycles;
|
||||||
|
if (!TimerService.pomodoroRunning) {
|
||||||
|
TimerService.resetPomodoro();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCycles(cycles) {
|
||||||
|
Config.options.time.pomodoro.cyclesBeforeLongBreak = cycles;
|
||||||
|
if (!TimerService.pomodoroRunning) {
|
||||||
|
TimerService.resetPomodoro();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: settingsColumn
|
||||||
|
width: root.width
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 4
|
||||||
|
Layout.rightMargin: 12
|
||||||
|
implicitHeight: 108
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
color: Appearance.colors.colLayer2
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
implicitWidth: 42
|
||||||
|
implicitHeight: 42
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
color: Appearance.colors.colSecondaryContainer
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "search_activity"
|
||||||
|
iconSize: Appearance.font.pixelSize.hugeass
|
||||||
|
color: Appearance.colors.colOnSecondaryContainer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: Translation.tr("Focus profile")
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: `${root.minutes("focus")} / ${root.minutes("breakTime")} / ${root.minutes("longBreak")} min • ${Config.options.time.pomodoro.cyclesBeforeLongBreak} cycles`
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||||
|
color: Appearance.colors.colSubtext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ProfileChip {
|
||||||
|
iconName: "search_activity"
|
||||||
|
label: `${root.minutes("focus")}m`
|
||||||
|
color: Appearance.colors.colSecondaryContainer
|
||||||
|
textColor: Appearance.colors.colOnSecondaryContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileChip {
|
||||||
|
iconName: "coffee"
|
||||||
|
label: `${root.minutes("breakTime")}m`
|
||||||
|
color: Appearance.colors.colTertiaryContainer
|
||||||
|
textColor: Appearance.colors.colOnTertiaryContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileChip {
|
||||||
|
iconName: "spa"
|
||||||
|
label: `${root.minutes("longBreak")}m`
|
||||||
|
color: Appearance.colors.colLayer1
|
||||||
|
textColor: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: 3
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Config.options.time.pomodoro.cyclesBeforeLongBreak
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitWidth: 7
|
||||||
|
implicitHeight: 7
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
opacity: 0.45
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentSection {
|
||||||
|
icon: "timer"
|
||||||
|
title: Translation.tr("Pomodoro")
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 8
|
||||||
|
Layout.rightMargin: 8
|
||||||
|
spacing: 6
|
||||||
|
uniformCellSizes: true
|
||||||
|
|
||||||
|
PresetButton {
|
||||||
|
label: "25/5"
|
||||||
|
onClicked: root.applyPreset(25, 5, 15, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
PresetButton {
|
||||||
|
label: "50/10"
|
||||||
|
onClicked: root.applyPreset(50, 10, 25, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
PresetButton {
|
||||||
|
label: "15/5"
|
||||||
|
onClicked: root.applyPreset(15, 5, 15, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PomodoroSpinRow {
|
||||||
|
iconName: "search_activity"
|
||||||
|
label: Translation.tr("Focus")
|
||||||
|
suffix: Translation.tr("min")
|
||||||
|
from: 1
|
||||||
|
to: 180
|
||||||
|
stepSize: 5
|
||||||
|
value: root.minutes("focus")
|
||||||
|
onValueModified: value => root.setMinutes("focus", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
PomodoroSpinRow {
|
||||||
|
iconName: "coffee"
|
||||||
|
label: Translation.tr("Break")
|
||||||
|
suffix: Translation.tr("min")
|
||||||
|
from: 1
|
||||||
|
to: 60
|
||||||
|
stepSize: 1
|
||||||
|
value: root.minutes("breakTime")
|
||||||
|
onValueModified: value => root.setMinutes("breakTime", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
PomodoroSpinRow {
|
||||||
|
iconName: "spa"
|
||||||
|
label: Translation.tr("Long break")
|
||||||
|
suffix: Translation.tr("min")
|
||||||
|
from: 1
|
||||||
|
to: 120
|
||||||
|
stepSize: 5
|
||||||
|
value: root.minutes("longBreak")
|
||||||
|
onValueModified: value => root.setMinutes("longBreak", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
PomodoroSpinRow {
|
||||||
|
iconName: "repeat"
|
||||||
|
label: Translation.tr("Cycles")
|
||||||
|
suffix: ""
|
||||||
|
from: 1
|
||||||
|
to: 12
|
||||||
|
stepSize: 1
|
||||||
|
value: Config.options.time.pomodoro.cyclesBeforeLongBreak
|
||||||
|
onValueModified: value => root.setCycles(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentSection {
|
||||||
|
icon: "notifications"
|
||||||
|
title: Translation.tr("Alerts")
|
||||||
|
|
||||||
|
ConfigRow {
|
||||||
|
uniform: true
|
||||||
|
|
||||||
|
ConfigSwitch {
|
||||||
|
buttonIcon: "notifications"
|
||||||
|
text: Translation.tr("Notifications")
|
||||||
|
checked: Config.options.time.pomodoro.notifications
|
||||||
|
onCheckedChanged: Config.options.time.pomodoro.notifications = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigSwitch {
|
||||||
|
buttonIcon: "notification_sound"
|
||||||
|
text: Translation.tr("Sound")
|
||||||
|
checked: Config.options.sounds.pomodoro
|
||||||
|
onCheckedChanged: Config.options.sounds.pomodoro = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 40
|
||||||
|
buttonRadius: Appearance.rounding.full
|
||||||
|
colBackground: Appearance.colors.colLayer2
|
||||||
|
colBackgroundHover: Appearance.colors.colLayer2Hover
|
||||||
|
colRipple: Appearance.colors.colLayer2Active
|
||||||
|
|
||||||
|
onClicked: Audio.playSystemSound("alarm-clock-elapsed")
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 12
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: "play_circle"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: Translation.tr("Test sound")
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component PresetButton: RippleButton {
|
||||||
|
id: preset
|
||||||
|
property string label
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 34
|
||||||
|
buttonRadius: Appearance.rounding.full
|
||||||
|
colBackground: Appearance.colors.colLayer2
|
||||||
|
colBackgroundHover: Appearance.colors.colLayer2Hover
|
||||||
|
colRipple: Appearance.colors.colLayer2Active
|
||||||
|
|
||||||
|
contentItem: StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: preset.label
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.family: Appearance.font.family.monospace
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component ProfileChip: Rectangle {
|
||||||
|
property string iconName
|
||||||
|
property string label
|
||||||
|
property color textColor
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 28
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: parent.parent.iconName
|
||||||
|
iconSize: 14
|
||||||
|
color: parent.parent.textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: parent.parent.label
|
||||||
|
font.family: Appearance.font.family.monospace
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
color: parent.parent.textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component PomodoroSpinRow: RowLayout {
|
||||||
|
id: row
|
||||||
|
|
||||||
|
property string iconName
|
||||||
|
property string label
|
||||||
|
property string suffix
|
||||||
|
property alias value: spinBox.value
|
||||||
|
property alias from: spinBox.from
|
||||||
|
property alias to: spinBox.to
|
||||||
|
property alias stepSize: spinBox.stepSize
|
||||||
|
signal valueModified(int value)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 8
|
||||||
|
Layout.rightMargin: 8
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: row.iconName
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colOnSecondaryContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: row.label
|
||||||
|
color: Appearance.colors.colOnSecondaryContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledSpinBox {
|
||||||
|
id: spinBox
|
||||||
|
Layout.preferredWidth: 96
|
||||||
|
stepSize: 1
|
||||||
|
onValueModified: row.valueModified(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.preferredWidth: 24
|
||||||
|
text: row.suffix
|
||||||
|
color: Appearance.colors.colSubtext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
implicitHeight: contentColumn.implicitHeight
|
||||||
|
implicitWidth: contentColumn.implicitWidth
|
||||||
|
readonly property color stateColor: TimerService.pomodoroBreak ? Appearance.colors.colTertiaryContainer : Appearance.colors.colSecondaryContainer
|
||||||
|
readonly property color stateTextColor: TimerService.pomodoroBreak ? Appearance.colors.colOnTertiaryContainer : Appearance.colors.colOnSecondaryContainer
|
||||||
|
readonly property string stateLabel: TimerService.pomodoroLongBreak ? Translation.tr("Long break") : TimerService.pomodoroBreak ? Translation.tr("Break") : Translation.tr("Focus")
|
||||||
|
readonly property int cyclePosition: TimerService.pomodoroCycle + 1
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: contentColumn
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
// The Pomodoro timer circle
|
||||||
|
CircularProgress {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
lineWidth: 10
|
||||||
|
value: {
|
||||||
|
return TimerService.pomodoroSecondsLeft / TimerService.pomodoroLapDuration;
|
||||||
|
}
|
||||||
|
implicitSize: 200
|
||||||
|
colPrimary: root.stateTextColor
|
||||||
|
colSecondary: root.stateColor
|
||||||
|
enableAnimation: true
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.preferredWidth: 150
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: {
|
||||||
|
let minutes = Math.floor(TimerService.pomodoroSecondsLeft / 60).toString().padStart(2, '0');
|
||||||
|
let seconds = Math.floor(TimerService.pomodoroSecondsLeft % 60).toString().padStart(2, '0');
|
||||||
|
return `${minutes}:${seconds}`;
|
||||||
|
}
|
||||||
|
font.family: Appearance.font.family.monospace
|
||||||
|
font.pixelSize: 40
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
color: Appearance.m3colors.m3onSurface
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: root.stateLabel
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colSubtext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
color: root.stateColor
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
implicitWidth: 58
|
||||||
|
implicitHeight: 36
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 3
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: "repeat"
|
||||||
|
iconSize: 14
|
||||||
|
color: root.stateTextColor
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: cycleText
|
||||||
|
font.family: Appearance.font.family.monospace
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
color: root.stateTextColor
|
||||||
|
text: `${root.cyclePosition}/${TimerService.cyclesBeforeLongBreak}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.preferredWidth: 210
|
||||||
|
Layout.preferredHeight: 30
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
color: Appearance.colors.colLayer2
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: TimerService.pomodoroBreak ? "coffee" : "search_activity"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
color: root.stateTextColor
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.stateLabel
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: TimerService.cyclesBeforeLongBreak
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
required property int index
|
||||||
|
implicitWidth: index === TimerService.pomodoroCycle ? 18 : 8
|
||||||
|
implicitHeight: 8
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
color: index <= TimerService.pomodoroCycle ? root.stateTextColor : Appearance.colors.colLayer2
|
||||||
|
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Start/Stop and Reset buttons
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
buttonRadius: Appearance.rounding.full
|
||||||
|
contentItem: StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: TimerService.pomodoroRunning ? Translation.tr("Pause") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr("Start") : Translation.tr("Resume")
|
||||||
|
color: TimerService.pomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
|
||||||
|
}
|
||||||
|
implicitHeight: 38
|
||||||
|
implicitWidth: 96
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
onClicked: TimerService.togglePomodoro()
|
||||||
|
colBackground: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
|
||||||
|
colBackgroundHover: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
buttonRadius: Appearance.rounding.full
|
||||||
|
implicitHeight: 38
|
||||||
|
implicitWidth: 96
|
||||||
|
|
||||||
|
onClicked: TimerService.resetPomodoro()
|
||||||
|
enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.pomodoroBreak
|
||||||
|
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
colBackground: Appearance.colors.colErrorContainer
|
||||||
|
colBackgroundHover: Appearance.colors.colErrorContainerHover
|
||||||
|
colRipple: Appearance.colors.colErrorContainerActive
|
||||||
|
|
||||||
|
contentItem: StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: Translation.tr("Reset")
|
||||||
|
color: Appearance.colors.colOnErrorContainer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property var tabButtonList: [
|
||||||
|
{"name": Translation.tr("Timer"), "icon": "search_activity"},
|
||||||
|
{"name": Translation.tr("Customize"), "icon": "tune"}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Pomodoro keybinds
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { // Switch tabs
|
||||||
|
if (event.key === Qt.Key_PageDown) {
|
||||||
|
tabBar.incrementCurrentIndex();
|
||||||
|
} else if (event.key === Qt.Key_PageUp) {
|
||||||
|
tabBar.decrementCurrentIndex();
|
||||||
|
}
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S
|
||||||
|
if (tabBar.currentIndex === 0) {
|
||||||
|
TimerService.togglePomodoro()
|
||||||
|
}
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.key === Qt.Key_R) { // Reset with R
|
||||||
|
if (tabBar.currentIndex === 0) {
|
||||||
|
TimerService.resetPomodoro()
|
||||||
|
}
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
SecondaryTabBar {
|
||||||
|
id: tabBar
|
||||||
|
currentIndex: swipeView.currentIndex
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.tabButtonList
|
||||||
|
delegate: SecondaryTabButton {
|
||||||
|
buttonText: modelData.name
|
||||||
|
buttonIcon: modelData.icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeView {
|
||||||
|
id: swipeView
|
||||||
|
Layout.topMargin: 10
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: 10
|
||||||
|
clip: true
|
||||||
|
currentIndex: tabBar.currentIndex
|
||||||
|
|
||||||
|
// Tabs
|
||||||
|
PomodoroTimer {}
|
||||||
|
PomodoroSettings {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: stopwatchTab
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
topMargin: 8
|
||||||
|
leftMargin: 16
|
||||||
|
rightMargin: 16
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout { // Elapsed
|
||||||
|
id: elapsedIndicator
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: undefined
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
left: controlButtons.left
|
||||||
|
leftMargin: 6
|
||||||
|
}
|
||||||
|
|
||||||
|
states: State {
|
||||||
|
name: "hasLaps"
|
||||||
|
when: TimerService.stopwatchLaps.length > 0
|
||||||
|
AnchorChanges {
|
||||||
|
target: elapsedIndicator
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.verticalCenter: undefined
|
||||||
|
anchors.left: controlButtons.left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transitions: Transition {
|
||||||
|
AnchorAnimation {
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Appearance.animation.elementMoveFast.type
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
StyledText {
|
||||||
|
// Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness
|
||||||
|
font.pixelSize: 40
|
||||||
|
color: Appearance.m3colors.m3onSurface
|
||||||
|
text: {
|
||||||
|
let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100
|
||||||
|
let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
|
||||||
|
let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
|
||||||
|
return `${minutes}:${seconds}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
font.pixelSize: 40
|
||||||
|
color: Appearance.colors.colSubtext
|
||||||
|
text: {
|
||||||
|
return `:<sub>${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}</sub>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laps
|
||||||
|
StyledListView {
|
||||||
|
id: lapsList
|
||||||
|
anchors {
|
||||||
|
top: elapsedIndicator.bottom
|
||||||
|
bottom: controlButtons.top
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
topMargin: 16
|
||||||
|
bottomMargin: 16
|
||||||
|
}
|
||||||
|
spacing: 4
|
||||||
|
clip: true
|
||||||
|
popin: true
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
values: TimerService.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i])
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: lapItem
|
||||||
|
required property int index
|
||||||
|
required property var modelData
|
||||||
|
property var horizontalPadding: 10
|
||||||
|
property var verticalPadding: 6
|
||||||
|
width: lapsList.width
|
||||||
|
implicitHeight: lapRow.implicitHeight + verticalPadding * 2
|
||||||
|
implicitWidth: lapRow.implicitWidth + horizontalPadding * 2
|
||||||
|
color: Appearance.colors.colLayer2
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: lapRow
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
leftMargin: lapItem.horizontalPadding
|
||||||
|
rightMargin: lapItem.horizontalPadding
|
||||||
|
topMargin: lapItem.verticalPadding
|
||||||
|
bottomMargin: lapItem.verticalPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
color: Appearance.colors.colSubtext
|
||||||
|
text: `${TimerService.stopwatchLaps.length - lapItem.index}.`
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
text: {
|
||||||
|
const lapTime = lapItem.modelData
|
||||||
|
const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')
|
||||||
|
const totalSeconds = Math.floor(lapTime) / 100
|
||||||
|
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
|
||||||
|
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
|
||||||
|
return `${minutes}:${seconds}.${_10ms}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||||
|
color: Appearance.colors.colPrimary
|
||||||
|
text: {
|
||||||
|
const originalIndex = TimerService.stopwatchLaps.length - lapItem.index - 1
|
||||||
|
const lastTime = originalIndex > 0 ? TimerService.stopwatchLaps[originalIndex - 1] : 0
|
||||||
|
const lapTime = lapItem.modelData - lastTime
|
||||||
|
const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')
|
||||||
|
const totalSeconds = Math.floor(lapTime) / 100
|
||||||
|
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
|
||||||
|
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
|
||||||
|
return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: controlButtons
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
bottom: parent.bottom
|
||||||
|
bottomMargin: 6
|
||||||
|
}
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
Layout.preferredHeight: 35
|
||||||
|
Layout.preferredWidth: 90
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
TimerService.toggleStopwatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
colBackground: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
|
||||||
|
colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover
|
||||||
|
colRipple: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive
|
||||||
|
|
||||||
|
contentItem: StyledText {
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
color: TimerService.stopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
|
||||||
|
text: TimerService.stopwatchRunning ? Translation.tr("Pause") : TimerService.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
implicitHeight: 35
|
||||||
|
implicitWidth: 90
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (TimerService.stopwatchRunning)
|
||||||
|
TimerService.stopwatchRecordLap()
|
||||||
|
else
|
||||||
|
TimerService.stopwatchReset()
|
||||||
|
}
|
||||||
|
enabled: TimerService.stopwatchTime > 0 || Persistent.states.timer.stopwatch.laps.length > 0
|
||||||
|
|
||||||
|
colBackground: TimerService.stopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer
|
||||||
|
colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover
|
||||||
|
colRipple: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive
|
||||||
|
|
||||||
|
contentItem: StyledText {
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: TimerService.stopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset")
|
||||||
|
color: TimerService.stopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
if (event.key === Qt.Key_Space || event.key === Qt.Key_S) {
|
||||||
|
TimerService.toggleStopwatch();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_R) {
|
||||||
|
TimerService.stopwatchReset();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_L) {
|
||||||
|
TimerService.stopwatchRecordLap();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 4
|
||||||
|
Layout.rightMargin: 14
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
text: "timer"
|
||||||
|
iconSize: Appearance.font.pixelSize.hugeass
|
||||||
|
color: Appearance.colors.colOnSecondaryContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: Translation.tr("Stopwatch")
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: TimerService.stopwatchRunning ? Translation.tr("Running") : Translation.tr("Ready")
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||||
|
color: Appearance.colors.colSubtext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stopwatch {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
PomodoroSettings 1.0 PomodoroSettings.qml
|
||||||
|
PomodoroTimer 1.0 PomodoroTimer.qml
|
||||||
|
PomodoroWidget 1.0 PomodoroWidget.qml
|
||||||
|
Stopwatch 1.0 Stopwatch.qml
|
||||||
|
StopwatchWidget 1.0 StopwatchWidget.qml
|
||||||
141
surfaces/quickshell/modules/ii/sidebarRight/todo/TaskList.qml
Normal file
141
surfaces/quickshell/modules/ii/sidebarRight/todo/TaskList.qml
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import qs.services
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
required property var taskList
|
||||||
|
property string emptyPlaceholderIcon
|
||||||
|
property string emptyPlaceholderText
|
||||||
|
property int todoListItemSpacing: 5
|
||||||
|
property int todoListItemPadding: 8
|
||||||
|
property int listBottomPadding: 80
|
||||||
|
|
||||||
|
StyledListView {
|
||||||
|
id: listView
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: root.todoListItemSpacing
|
||||||
|
animateAppearance: false
|
||||||
|
model: ScriptModel {
|
||||||
|
values: root.taskList
|
||||||
|
}
|
||||||
|
delegate: Item {
|
||||||
|
id: todoItem
|
||||||
|
required property var modelData
|
||||||
|
property bool pendingDoneToggle: false
|
||||||
|
property bool pendingDelete: false
|
||||||
|
property bool enableHeightAnimation: false
|
||||||
|
|
||||||
|
implicitHeight: todoItemRectangle.implicitHeight
|
||||||
|
width: ListView.view.width
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
enabled: enableHeightAnimation
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Appearance.animation.elementMoveFast.type
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: todoItemRectangle
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
implicitHeight: todoContentRowLayout.implicitHeight
|
||||||
|
color: Appearance.colors.colLayer2
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: todoContentRowLayout
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: todoContentText
|
||||||
|
Layout.fillWidth: true // Needed for wrapping
|
||||||
|
Layout.leftMargin: 10
|
||||||
|
Layout.rightMargin: 10
|
||||||
|
Layout.topMargin: todoListItemPadding
|
||||||
|
text: todoItem.modelData.content
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.leftMargin: 10
|
||||||
|
Layout.rightMargin: 10
|
||||||
|
Layout.bottomMargin: todoListItemPadding
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
TodoItemActionButton {
|
||||||
|
Layout.fillWidth: false
|
||||||
|
onClicked: {
|
||||||
|
if (!todoItem.modelData.done)
|
||||||
|
Todo.markDone(todoItem.modelData.originalIndex);
|
||||||
|
else
|
||||||
|
Todo.markUnfinished(todoItem.modelData.originalIndex);
|
||||||
|
}
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: todoItem.modelData.done ? "remove_done" : "check"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TodoItemActionButton {
|
||||||
|
Layout.fillWidth: false
|
||||||
|
onClicked: {
|
||||||
|
Todo.deleteItem(todoItem.modelData.originalIndex);
|
||||||
|
}
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: "delete_forever"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
// Placeholder when list is empty
|
||||||
|
visible: opacity > 0
|
||||||
|
opacity: taskList.length === 0 ? 1 : 0
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
MaterialSymbol {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
iconSize: 55
|
||||||
|
color: Appearance.m3colors.m3outline
|
||||||
|
text: emptyPlaceholderIcon
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.m3colors.m3outline
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: emptyPlaceholderText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
id: button
|
||||||
|
property string buttonText: ""
|
||||||
|
property string tooltipText: ""
|
||||||
|
|
||||||
|
implicitHeight: 30
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
SmoothedAnimation {
|
||||||
|
velocity: Appearance.animation.elementMove.velocity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonRadius: Appearance.rounding.small
|
||||||
|
|
||||||
|
contentItem: StyledText {
|
||||||
|
text: buttonText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledToolTip {
|
||||||
|
text: tooltipText
|
||||||
|
extraVisibleCondition: tooltipText.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
219
surfaces/quickshell/modules/ii/sidebarRight/todo/TodoWidget.qml
Normal file
219
surfaces/quickshell/modules/ii/sidebarRight/todo/TodoWidget.qml
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property var tabButtonList: [{"icon": "checklist", "name": Translation.tr("Unfinished")}, {"name": Translation.tr("Done"), "icon": "check_circle"}]
|
||||||
|
property bool showAddDialog: false
|
||||||
|
property int dialogMargins: 20
|
||||||
|
property int fabSize: 48
|
||||||
|
property int fabMargins: 14
|
||||||
|
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) {
|
||||||
|
if (event.key === Qt.Key_PageDown) {
|
||||||
|
tabBar.incrementCurrentIndex();
|
||||||
|
} else if (event.key === Qt.Key_PageUp) {
|
||||||
|
tabBar.decrementCurrentIndex();
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
// Open add dialog on "N" (any modifiers)
|
||||||
|
else if (event.key === Qt.Key_N) {
|
||||||
|
root.showAddDialog = true
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
// Close dialog on Esc if open
|
||||||
|
else if (event.key === Qt.Key_Escape && root.showAddDialog) {
|
||||||
|
root.showAddDialog = false
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
SecondaryTabBar {
|
||||||
|
id: tabBar
|
||||||
|
currentIndex: swipeView.currentIndex
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.tabButtonList
|
||||||
|
delegate: SecondaryTabButton {
|
||||||
|
buttonText: modelData.name
|
||||||
|
buttonIcon: modelData.icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeView {
|
||||||
|
id: swipeView
|
||||||
|
Layout.topMargin: 10
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: 10
|
||||||
|
clip: true
|
||||||
|
currentIndex: tabBar.currentIndex
|
||||||
|
|
||||||
|
// To Do tab
|
||||||
|
TaskList {
|
||||||
|
listBottomPadding: root.fabSize + root.fabMargins * 2
|
||||||
|
emptyPlaceholderIcon: "check_circle"
|
||||||
|
emptyPlaceholderText: Translation.tr("Nothing here!")
|
||||||
|
taskList: Todo.list
|
||||||
|
.map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); })
|
||||||
|
.filter(function(item) { return !item.done; })
|
||||||
|
}
|
||||||
|
TaskList {
|
||||||
|
listBottomPadding: root.fabSize + root.fabMargins * 2
|
||||||
|
emptyPlaceholderIcon: "checklist"
|
||||||
|
emptyPlaceholderText: Translation.tr("Finished tasks will go here")
|
||||||
|
taskList: Todo.list
|
||||||
|
.map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); })
|
||||||
|
.filter(function(item) { return item.done; })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// + FAB
|
||||||
|
StyledRectangularShadow {
|
||||||
|
target: fabButton
|
||||||
|
radius: fabButton.buttonRadius
|
||||||
|
blur: 0.6 * Appearance.sizes.elevationMargin
|
||||||
|
}
|
||||||
|
FloatingActionButton {
|
||||||
|
id: fabButton
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.rightMargin: root.fabMargins
|
||||||
|
anchors.bottomMargin: root.fabMargins
|
||||||
|
|
||||||
|
onClicked: root.showAddDialog = true
|
||||||
|
iconText: "add"
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
z: 9999
|
||||||
|
|
||||||
|
visible: opacity > 0
|
||||||
|
opacity: root.showAddDialog ? 1 : 0
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Appearance.animation.elementMoveFast.type
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible) {
|
||||||
|
todoInput.text = ""
|
||||||
|
fabButton.focus = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle { // Scrim
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
color: Appearance.colors.colScrim
|
||||||
|
MouseArea {
|
||||||
|
hoverEnabled: true
|
||||||
|
anchors.fill: parent
|
||||||
|
preventStealing: true
|
||||||
|
propagateComposedEvents: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle { // The dialog
|
||||||
|
id: dialog
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.margins: root.dialogMargins
|
||||||
|
implicitHeight: dialogColumnLayout.implicitHeight
|
||||||
|
|
||||||
|
color: Appearance.m3colors.m3surfaceContainerHigh
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
|
||||||
|
function addTask() {
|
||||||
|
if (todoInput.text.length > 0) {
|
||||||
|
Todo.addTask(todoInput.text)
|
||||||
|
todoInput.text = ""
|
||||||
|
root.showAddDialog = false
|
||||||
|
tabBar.setCurrentIndex(0) // Show unfinished tasks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: dialogColumnLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.topMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
color: Appearance.m3colors.m3onSurface
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
text: Translation.tr("Add task")
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: todoInput
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
padding: 10
|
||||||
|
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
||||||
|
selectionColor: Appearance.colors.colSecondaryContainer
|
||||||
|
placeholderText: Translation.tr("Task description")
|
||||||
|
placeholderTextColor: Appearance.m3colors.m3outline
|
||||||
|
focus: root.showAddDialog
|
||||||
|
onAccepted: dialog.addTask()
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Appearance.rounding.verysmall
|
||||||
|
border.width: 2
|
||||||
|
border.color: todoInput.activeFocus ? Appearance.colors.colPrimary : Appearance.m3colors.m3outline
|
||||||
|
color: "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
|
cursorDelegate: Rectangle {
|
||||||
|
width: 1
|
||||||
|
color: todoInput.activeFocus ? Appearance.colors.colPrimary : "transparent"
|
||||||
|
radius: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.bottomMargin: 16
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
DialogButton {
|
||||||
|
buttonText: Translation.tr("Cancel")
|
||||||
|
onClicked: root.showAddDialog = false
|
||||||
|
}
|
||||||
|
DialogButton {
|
||||||
|
buttonText: Translation.tr("Add")
|
||||||
|
enabled: todoInput.text.length > 0
|
||||||
|
onClicked: dialog.addTask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
surfaces/quickshell/modules/ii/sidebarRight/todo/qmldir
Normal file
3
surfaces/quickshell/modules/ii/sidebarRight/todo/qmldir
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
TaskList 1.0 TaskList.qml
|
||||||
|
TodoItemActionButton 1.0 TodoItemActionButton.qml
|
||||||
|
TodoWidget 1.0 TodoWidget.qml
|
||||||
|
|
@ -126,6 +126,15 @@ Singleton {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (msg.event === "note_created") {
|
||||||
|
const rel = msg.rel ?? "";
|
||||||
|
const title = rel.replace(/\.md$/, "").split("/").pop();
|
||||||
|
Quickshell.execDetached(["notify-send", "-a", "Garden", "Note planted", title]);
|
||||||
|
}
|
||||||
|
else if (msg.event === "synced") {
|
||||||
|
Quickshell.execDetached(["notify-send", "-a", "Garden", "Garden synced"]);
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.event === "state" && msg.state) {
|
if (msg.event === "state" && msg.state) {
|
||||||
const st = msg.state.status ?? {};
|
const st = msg.state.status ?? {};
|
||||||
root.notes = msg.state.notes ?? [];
|
root.notes = msg.state.notes ?? [];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue