Commit cd305279 authored by Dimitris Kardarakos's avatar Dimitris Kardarakos

Start date, due and alarm functionality added to tasks

Users can now change the start date of a task, set a due date and set an
alarm - based on the start date of the task
parent 5a433437
Pipeline #27166 passed with stage
in 11 minutes and 38 seconds
......@@ -51,11 +51,13 @@ if(ANDROID)
view-calendar-day
view-calendar-tasks
view-calendar-upcoming-events
view-calendar-timeline
view-choose
find-location
media-playlist-repeat
settings-configure
gps
edit-clear-all
)
endif()
......
......@@ -14,11 +14,11 @@ Controls2.ToolButton {
property date selectorDate
property string selectorTitle
text: selectorDate.toLocaleDateString(Qt.locale(),Locale.NarrowFormat)
text: (selectorDate != undefined && !isNaN(root.selectorDate)) ? selectorDate.toLocaleDateString(Qt.locale(),Locale.NarrowFormat) : "-"
implicitWidth: Kirigami.Units.gridUnit * 5
onClicked: {
datePickerSheet.selectedDate = selectorDate;
datePickerSheet.selectedDate = (selectorDate != undefined && !isNaN(root.selectorDate)) ? selectorDate: new Date()
datePickerSheet.open();
}
......
......@@ -61,7 +61,13 @@ ListView {
pageStack.push(eventEditor, { startDt: eventDt });
}
onAddTodo: pageStack.push(todoEditor, { startDt: selectedDate, startHour: currentIndex % 12, startPm: currentIndex > 12 } )
onAddTodo: {
var todoDt = selectedDate;
todoDt.setHours(currentIndex);
todoDt.setMinutes(0);
pageStack.push(todoEditor, { startDt: todoDt });
}
onCurrentIndexChanged: {
if (pageStack.depth > 1) {
......@@ -100,7 +106,7 @@ ListView {
IncidenceItemDelegate {
itemBackgroundColor: hourListItem.incidenceColor
label: "%1 %2".arg(model.startEndTime).arg(model.summary)
label: "%1 %2".arg(model.type == 0 ? model.displayStartEndTime : (model.displayDueTime || model.displayStartTime)).arg(model.summary)
Layout.fillWidth: true
onClicked: pageStack.push(incidencePage, { incidence: model })
......
......@@ -259,7 +259,6 @@ Kirigami.Page {
var validation = validate();
if(validation.success) {
console.log("Saving event, root.startDt:" + startDt);
var vevent = { "uid" : root.uid, "startDate": root.startDt, "summary": root.summary, "description": root.description, "startHour": root.startHour + (root.startPm ? 12 : 0), "startMinute": root.startMinute, "allDay": root.allDay, "location": root.location, "endDate": (root.allDay ? root.startDt : root.endDt), "endHour": root.endHour + (root.endPm ? 12 : 0), "endMinute": root.endMinute, "alarms": incidenceAlarmsModel.alarms(), "periodType": root.repeatType, "repeatEvery": root.repeatEvery, "stopAfter": root.repeatStopAfter};
_eventController.addEdit(root.calendar, vevent);
......
......@@ -50,10 +50,10 @@ Kirigami.ScrollablePage {
model: incidenceModel
section {
property: "displayDate"
property: incidenceType == 0 ? "displayStartDate" : "displayDueDate"
criteria: ViewSection.FullString
delegate: Kirigami.ListSectionHeader {
label: section || i18n("No start date")
label: section
}
}
......@@ -61,7 +61,7 @@ Kirigami.ScrollablePage {
id: itemDelegate
reserveSpaceForIcon: false
label: "%1\t%2".arg(model.displayTime).arg(model.summary)
label: "%1\t%2".arg(model.allday ? i18n("All day") : (incidenceType == 0 ? model.displayStartEndTime : model.displayDueTime) ).arg(model.summary)
onClicked: pageStack.push(incidencePage, { incidence: model })
}
......
......@@ -58,6 +58,23 @@ Kirigami.Card {
}
}
Row {
visible: dataModel && dataModel.due != undefined && !isNaN(dataModel.due)
width: cardDelegate.availableWidth
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
source: "view-calendar-timeline"
width: Kirigami.Units.iconSizes.small
height: width
}
Controls2.Label {
wrapMode: Text.WordWrap
text: dataModel && (dataModel.due != undefined) && !isNaN(dataModel.due) && dataModel.due.toLocaleString(Qt.locale(), dataModel.allday ? "ddd d MMM yyyy" : "ddd d MMM yyyy hh:mm" )
}
}
Controls2.Label {
width: cardDelegate.availableWidth
wrapMode: Text.WordWrap
......
......@@ -13,13 +13,17 @@ import org.kde.calindori 0.1 as Calindori
Kirigami.Page {
id: root
property date startDt
property alias startDt: startDateSelector.selectorDate
property string uid
property alias summary: summary.text
property alias description: description.text
property alias startHour: startTimeSelector.selectorHour
property alias startMinute: startTimeSelector.selectorMinutes
property alias startPm: startTimeSelector.selectorPm
property alias dueDt: dueDateSelector.selectorDate
property alias dueHour: dueDtTimeSelector.selectorHour
property alias dueMinute: dueDtTimeSelector.selectorMinutes
property alias duePm: dueDtTimeSelector.selectorPm
property alias allDay: allDaySelector.checked
property alias location: location.text
property alias completed: completed.checked
......@@ -72,25 +76,91 @@ Kirigami.Page {
}
RowLayout {
Kirigami.FormData.label: i18n("Start time:")
enabled: root.startDt != undefined && !isNaN(root.startDt)
Kirigami.FormData.label: i18n("Start:")
spacing: 0
DateSelectorButton {
id: startDateSelector
selectorTitle: i18n("Start Date")
}
TimeSelectorButton {
id: startTimeSelector
property bool validSelectedDt: startDateSelector.selectorDate != undefined && !isNaN(startDateSelector.selectorDate)
selectorDate: startDateSelector.selectorDate
selectorTitle: i18n("Start Time")
selectorDate: root.startDt
selectorHour: root.incidenceData ? root.incidenceData.dtstart.toLocaleTimeString(Qt.locale(), "hh") % 12 : 0
selectorMinutes: root.incidenceData ? root.incidenceData.dtstart.toLocaleTimeString(Qt.locale(), "mm") : 0
selectorPm: (root.incidenceData && root.incidenceData.dtstart.toLocaleTimeString(Qt.locale("en_US"), "AP") == "PM") ? true : false
enabled: !allDaySelector.checked
selectorHour: validSelectedDt ? selectorDate.toLocaleTimeString(Qt.locale(), "hh") % 12 : 0
selectorMinutes: validSelectedDt ? selectorDate.toLocaleTimeString(Qt.locale(), "mm") : 0
selectorPm: (validSelectedDt && (selectorDate.toLocaleTimeString(Qt.locale("en_US"), "AP") == "PM")) ? true : false
enabled: !allDaySelector.checked && validSelectedDt
}
Controls2.ToolButton {
id: clearStartDt
icon.name: "edit-clear-all"
onClicked: {
startDateSelector.selectorDate = new Date("invalid");
incidenceAlarmsModel.removeAll();
}
}
}
RowLayout {
Kirigami.FormData.label: i18n("Due:")
spacing: 0
DateSelectorButton {
id: dueDateSelector
selectorTitle: i18n("Due Date")
Component.onCompleted: {
// Do not bind, just initialize
if (root.incidenceData && root.incidenceData.validDueDt)
selectorDate = root.incidenceData.due
else if (root.incidenceData == undefined && root.startDt != undefined && !isNaN(root.startDt))
selectorDate = new Date(root.startDt.getFullYear(), root.startDt.getMonth(), root.startDt.getDate(), root.startHour + (startPm ? 12 : 0) , root.startMinute);
else
selectorDate = new Date("invalid");
}
}
TimeSelectorButton {
id: dueDtTimeSelector
property bool validSelectedDt: dueDateSelector.selectorDate != undefined && !isNaN(dueDateSelector.selectorDate)
selectorDate: dueDateSelector.selectorDate
selectorTitle: i18n("Due Time")
selectorHour: validSelectedDt ? selectorDate.toLocaleTimeString(Qt.locale(), "hh") % 12 : 0
selectorMinutes: validSelectedDt ? selectorDate.toLocaleTimeString(Qt.locale(), "mm") : 0
selectorPm: validSelectedDt && (selectorDate.toLocaleTimeString(Qt.locale("en_US"), "AP") == "PM") ? true : false
enabled: !allDaySelector.checked && validSelectedDt
}
Controls2.ToolButton {
id: clearDueDt
icon.name: "edit-clear-all"
onClicked: dueDateSelector.selectorDate = new Date("invalid")
}
}
Controls2.CheckBox {
id: allDaySelector
enabled: !isNaN(root.startDt)
enabled: !isNaN(root.startDt) || !isNaN(root.dueDt)
checked: incidenceData ? incidenceData.allday: false
text: i18n("All day")
}
......@@ -130,6 +200,51 @@ Kirigami.Page {
Layout.fillWidth: true
}
RowLayout {
enabled: root.startDt != undefined && !isNaN(root.startDt)
Controls2.Label {
id: remindersLabel
Layout.fillWidth: true
text: i18n("Reminders")
}
Controls2.ToolButton {
text: i18n("Add")
onClicked: reminderEditor.open()
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
Repeater {
id: alarmsList
model: incidenceAlarmsModel
delegate: Kirigami.SwipeListItem {
contentItem: Controls2.Label {
text: model.display
wrapMode: Text.WordWrap
}
Layout.fillWidth: true
actions: [
Kirigami.Action {
id: deleteAlarm
iconName: "delete"
onTriggered: incidenceAlarmsModel.removeAlarm(model.index)
}
]
}
}
Controls2.CheckBox {
id: completed
......@@ -157,11 +272,31 @@ Kirigami.Page {
enabled: summary.text
onTriggered: {
console.log("Saving task");
var vtodo = { "uid": root.uid, "summary":root.summary, "startDate": root.startDt , "startHour": root.startHour + (root.startPm ? 12 : 0), "startMinute": root.startMinute, "allDay": root.allDay, "description": root.description,"location": root.location, "completed": root.completed };
_todoController.addEdit(root.calendar, vtodo);
editcompleted();
var vtodo = { "uid": root.uid, "summary":root.summary, "startDate": root.startDt , "startHour": root.startHour + (root.startPm ? 12 : 0), "startMinute": root.startMinute, "allDay": root.allDay, "description": root.description, "location": root.location, "completed": root.completed, "dueDate": root.dueDt, "dueHour": root.dueHour + (root.duePm ? 12 : 0), "dueMinute": root.dueMinute, "alarms": incidenceAlarmsModel.alarms() };
var validation = _todoController.validate(vtodo);
if(validation.success) {
_todoController.addEdit(root.calendar, vtodo);
editcompleted();
}
else {
showPassiveNotification(validation.reason);
}
}
}
}
Calindori.IncidenceAlarmsModel {
id: incidenceAlarmsModel
alarmProperties: { "calendar" : root.calendar, "uid": root.uid }
}
ReminderEditor {
id: reminderEditor
onOffsetSelected: incidenceAlarmsModel.addAlarm(offset)
}
}
......@@ -105,7 +105,7 @@ ListView {
IncidenceItemDelegate {
itemBackgroundColor: dayListItem.incidenceColor
label: "%1 %2".arg(model.startEndTime).arg(model.summary)
label: "%1 %2".arg(model.type == 0 ? model.displayStartEndTime : (model.displayDueTime || model.displayStartTime)).arg(model.summary)
Layout.fillWidth: true
onClicked: pageStack.push(incidencePage, { incidence: model })
......
......@@ -36,6 +36,13 @@ void IncidenceAlarmsModel::removeAlarm(const int row)
endRemoveRows();
}
void IncidenceAlarmsModel::removeAll()
{
beginResetModel();
mAlarms.clear();
endResetModel();
}
void IncidenceAlarmsModel::addAlarm(const int secondsFromStart)
{
qDebug() << "\nAddAlarm:\tAdding alarm. Seconds before start: " << secondsFromStart;
......
......@@ -50,14 +50,22 @@ public:
void setAlarmProperties(const QVariantMap & alarmProps);
public Q_SLOTS:
/**
* @brief Removes an alarm from the model
*/
void removeAlarm(const int row);
/**
* @brief Removes all alarms
*/
void removeAll();
/**
* @brief Creates a model item and adds it to the model
*/
void addAlarm(const int secondsFromStart);
/**
* @return A QVariantList of the items of the model. The members of the list are QHash<QString, QVariant> items that contain the following members: startOffsetValue, startOffsetType and actionType
*/
......
......@@ -36,7 +36,6 @@ void IncidenceModel::setFilterMode(const int mode)
Q_EMIT filterModeChanged();
}
QDate IncidenceModel::filterDt() const
{
return m_filter_dt;
......@@ -95,11 +94,17 @@ QHash<int, QByteArray> IncidenceModel::roleNames() const
{ RepeatEvery, "repeatEvery" },
{ RepeatStopAfter, "repeatStopAfter" },
{ IsRepeating, "isRepeating" },
{ DisplayDate, "displayDate" },
{ DisplayTime, "displayTime" },
{ DisplayStartDate, "displayStartDate" },
{ DisplayDueDate, "displayDueDate" },
{ DisplayDueTime, "displayDueTime" },
{ DisplayStartEndTime, "displayStartEndTime" },
{ DisplayStartTime, "displayStartTime" },
{ Completed, "completed" },
{ IncidenceType, "type" },
{ DisplayStartEndTime, "startEndTime" }
{ Due, "due" },
{ ValidStartDt, "validStartDt" },
{ ValidEndDt, "validEndDt" },
{ ValidDueDt, "validDueDt" }
};
}
......@@ -145,16 +150,28 @@ QVariant IncidenceModel::data(const QModelIndex& index, int role) const
return repeatStopAfter(row);
case IsRepeating:
return m_incidences.at(row)->recurs();
case DisplayDate:
return m_incidences.at(row)->dtStart().date().toString(Qt::SystemLocaleLongDate);
case DisplayTime:
return m_incidences.at(row)->allDay() ? i18n("All day") : m_incidences.at(row)->dtStart().time().toString("hh:mm");
case DisplayStartDate:
return displayStartDate(row);
case DisplayDueDate:
return displayDueDate(row);
case DisplayStartEndTime:
return displayStartEndTime(row);
case DisplayDueTime:
return displayDueTime(row);
case DisplayStartTime:
return displayStartTime(row);
case Completed:
return (type == IncidenceBase::TypeTodo) ? m_incidences.at(row).dynamicCast<Todo>()->isCompleted() : false;
case IncidenceType:
return type;
case DisplayStartEndTime:
return displayStartEndTime(row);
case Due:
return (type == IncidenceBase::TypeTodo) ? m_incidences.at(row).dynamicCast<Todo>()->dtDue() : QDateTime();
case ValidStartDt:
return m_incidences.at(row)->dtStart().isValid();
case ValidEndDt:
return ((type == IncidenceBase::TypeEvent) && m_incidences.at(row).dynamicCast<Event>()->hasEndDate());
case ValidDueDt:
return ((type == IncidenceBase::TypeTodo) && m_incidences.at(row).dynamicCast<Todo>()->hasDueDate());
default:
return QVariant();
}
......@@ -298,7 +315,8 @@ Incidence::List IncidenceModel::hourTodos() const
for(const auto & t : dayTodoList)
{
auto k = t->allDay() ? 0 : t->dtStart().time().hour();
auto todo = t.dynamicCast<Todo>();
auto k = t->allDay() ? 0 : (todo->dtDue().isValid() ? todo->dtDue().time().hour() : todo->dtStart().time().hour());
if(k == m_filter_hour || t->allDay())
{
incidences.append(t);
......@@ -339,7 +357,7 @@ Incidence::List IncidenceModel::allIncidences() const
Incidence::List IncidenceModel::allTodos() const
{
auto todos = m_calendar->memorycalendar()->rawTodos(TodoSortStartDate, SortDirectionDescending);
auto todos = m_calendar->memorycalendar()->rawTodos(TodoSortDueDate, SortDirectionDescending);
return toIncidences(todos);
}
......@@ -384,10 +402,66 @@ QString IncidenceModel::displayStartEndTime(const int idx) const
return QString();
}
if(incidence->type() == IncidenceBase::TypeEvent)
if(incidence->type() == IncidenceBase::TypeEvent && incidence.dynamicCast<Event>()->dtEnd().isValid() )
{
return QString("%1 - %2").arg(incidence->dtStart().time().toString("hh:mm")).arg(incidence.dynamicCast<Event>()->dtEnd().time().toString("hh:mm"));
}
return incidence->dtStart().time().toString("hh:mm");
}
QString IncidenceModel::displayStartDate ( const int idx ) const
{
auto incidence = m_incidences.at(idx);
if(incidence->dtStart().isValid())
return incidence->dtStart().date().toString(Qt::SystemLocaleLongDate);
return QString();
}
QString IncidenceModel::displayDueDate ( const int idx ) const
{
auto incidence = m_incidences.at(idx);
if((incidence->type() == IncidenceBase::TypeTodo) && (incidence.dynamicCast<Todo>()->dtDue().isValid()))
return incidence.dynamicCast<Todo>()->dtDue().date().toString(Qt::SystemLocaleLongDate);
return i18n("Unspecified due date");
}
QString IncidenceModel::displayDueTime(const int idx) const
{
auto incidence = m_incidences.at(idx);
if(incidence->allDay())
{
return QString();
}
if(incidence->type() == IncidenceBase::TypeTodo)
{
auto todo = incidence.dynamicCast<Todo>();
return todo->dtDue().isValid() ? todo->dtDue().time().toString("hh:mm") : QString();
}
return QString();
}
QString IncidenceModel::displayStartTime ( const int idx ) const
{
auto incidence = m_incidences.at(idx);
if(incidence->allDay())
{
return QString();
}
if(incidence->type() == IncidenceBase::TypeTodo)
{
auto todo = incidence.dynamicCast<Todo>();
return todo->dtStart().isValid() ? todo->dtStart().time().toString("hh:mm") : QString();
}
return QString();
}
......@@ -59,11 +59,17 @@ public:
RepeatPeriodType,
RepeatEvery,
RepeatStopAfter,
DisplayDate,
DisplayTime,
DisplayStartDate,
Completed,
IncidenceType,
DisplayStartEndTime
DisplayStartEndTime,
DisplayDueDate,
DisplayDueTime,
DisplayStartTime,
Due,
ValidStartDt,
ValidEndDt,
ValidDueDt
};
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
......@@ -122,7 +128,10 @@ private:
Incidence::List toIncidences(const Todo::List & todoList) const;
Incidence::List toIncidences(const Event::List & eventList, const Todo::List & todoList) const;
QString displayStartEndTime(const int idx) const;
QString displayStartDate (const int idx) const;
QString displayDueDate (const int idx) const;
QString displayDueTime (const int idx) const;
QString displayStartTime (const int idx) const;
int m_filter_mode;
QDate m_filter_dt;
......
......@@ -8,6 +8,7 @@
#include "localcalendar.h"
#include <KCalendarCore/Todo>
#include <KCalendarCore/MemoryCalendar>
#include <KLocalizedString>
#include <QDebug>
TodoController::TodoController(QObject* parent) : QObject(parent) {}
......@@ -48,12 +49,46 @@ void TodoController::addEdit(LocalCalendar *calendar, const QVariantMap& todo)
}
vtodo->setDtStart(startDateTime);
QDate dueDate = todo["dueDate"].toDate();
bool validDueHour {false};
int dueHour = todo["dueHour"].toInt(&validDueHour);
bool validDueMinutes {false};
int dueMinute = todo["dueMinute"].toInt(&validDueMinutes);
QDateTime dueDateTime = QDateTime();
if(dueDate.isValid() && validDueHour && validDueMinutes && !allDayFlg)
dueDateTime = QDateTime(dueDate, QTime(dueHour, dueMinute, 0, 0), QTimeZone::systemTimeZone());
else if(dueDate.isValid() && allDayFlg)
dueDateTime = QDateTime(dueDate);
vtodo->setDtDue(dueDateTime);
vtodo->setDescription(todo["description"].toString());
vtodo->setSummary(summary);
vtodo->setAllDay(allDayFlg);
vtodo->setAllDay((startDate.isValid() || dueDate.isValid()) ? allDayFlg: false);
vtodo->setLocation(todo["location"].toString());
vtodo->setCompleted(todo["completed"].toBool());
vtodo->clearAlarms();
QVariantList newAlarms = todo["alarms"].value<QVariantList>();
QVariantList::const_iterator itr = newAlarms.constBegin();
while(itr != newAlarms.constEnd())
{
Alarm::Ptr newAlarm = vtodo->newAlarm();
QHash<QString, QVariant> newAlarmHashMap = (*itr).value<QHash<QString, QVariant>>();
int startOffsetValue = newAlarmHashMap["startOffsetValue"].value<int>();
int startOffsetType = newAlarmHashMap["startOffsetType"].value<int>();
int actionType = newAlarmHashMap["actionType"].value<int>();
newAlarm->setStartOffset(Duration(startOffsetValue, static_cast<Duration::Type>(startOffsetType)));
newAlarm->setType(static_cast<Alarm::Type>(actionType));
newAlarm->setEnabled(true);
newAlarm->setText((vtodo->summary()).isEmpty() ? vtodo->description() : vtodo->summary());
++itr;
}
memoryCalendar->addTodo(vtodo);
bool merged = calendar->save();
......@@ -76,3 +111,43 @@ void TodoController::remove(LocalCalendar *calendar, const QVariantMap& todo)
Q_EMIT calendar->todosChanged();
qDebug() << "Todo deleted: " << removed;
}
QVariantMap TodoController::validate ( const QVariantMap& todo ) const
{
QVariantMap result {};
QDate startDate = todo["startDate"].toDate();
bool validStartHour {false};
int startHour = todo["startHour"].toInt(&validStartHour);
bool validStartMinutes {false};
int startMinute = todo["startMinute"].toInt(&validStartMinutes);
QDate dueDate = todo["dueDate"].toDate();
bool validDueHour {false};
int dueHour = todo["dueHour"].toInt(&validDueHour);
bool validDueMinutes {false};
int dueMinute = todo["dueMinute"].toInt(&validDueMinutes);
bool allDayFlg= todo["allDay"].toBool();
if(startDate.isValid() && validStartHour && validStartMinutes && dueDate.isValid() && validDueHour && validDueMinutes)
{
if(allDayFlg && (dueDate != startDate))
{
result["success"] = false;
result["reason"] = i18n("In case of all day tasks, start date and due date should be equal");
return result;
}
if(!allDayFlg && (QDateTime(startDate, QTime(startHour, startMinute, 0, 0), QTimeZone::systemTimeZone()) > QDateTime(dueDate, QTime(dueHour, dueMinute, 0, 0), QTimeZone::systemTimeZone())))
{
result["success"] = false;
result["reason"] = i18n("Due date time should be equal to or greater than the start date time");
return result;
}
}
result["success"] = true;
result["reason"] = QString();
return result;
}
......@@ -21,7 +21,7 @@ public:
Q_INVOKABLE void remove(LocalCalendar *calendar, const QVariantMap& todo);
Q_INVOKABLE void addEdit(LocalCalendar *calendar, const QVariantMap& todo);
Q_INVOKABLE QVariantMap validate(const QVariantMap& todo) const;
};
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment