You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

261 lines
9.1 KiB

/*
SPDX-FileCopyrightText: 2022 Tanbir Jishan <tantalising007@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.workspace.calendar 2.0
Item {
id: root
required property var backend
required property int viewType
property QtObject eventPluginsManager
property alias delegate: infiniteRepeater.delegate
readonly property alias currentItem: infiniteList.currentItem
enum ViewType {
DayView,
YearView,
DecadeView
}
enum AnimationDirection {
Upward,
Downward
}
SwipeView {
id: infiniteList
anchors.fill: parent
orientation: Qt.Vertical
currentIndex: 1 //middle of the view, currentIndex always returns back to middle so that user can flick both upward and downward
property bool handlingIndexChange: false // This var is used to prevent date change ⇆ index change loop
property var highlightMoveDuration: PlasmaCore.Units.longDuration
property int lastMonth: -1
property int lastYear: -1
Connections {
id: dateToViewSynchroniser
target: root.backend
// Animation is done by moving to the edge with zero animation
// duration and then coming with a non-zero animation duration
onMonthChanged: infiniteList.animateDateChange()
onYearChanged: infiniteList.animateDateChange()
}
Repeater {
id: infiniteRepeater
model: 3
}
onCurrentIndexChanged: adjustDate()
Component.onCompleted: {
//init vars. last* vars are for tracking whether date changed toward future or past
contentItem.highlightMoveDuration = highlightMoveDuration;
lastMonth = root.backend.month - 1;
lastYear = root.backend.year;
// set up alternative model for delegates at edges
// so that they can be set up to always shows the last state of the main model
// date here means what the respective views should show(e.g. MonthView date -> month)
// this prevents them from showing the current date when they are being animated out of view
// since different years don't have different names for months, we don't need to set up alternative models for YearView
var alternativeModel = undefined;
if (root.viewType === InfiniteList.ViewType.DayView) {
alternativeModel = backend.daysModel;
} else if (root.viewType === InfiniteList.ViewType.DecadeView) {
alternativeModel = yearModel;
}
if (alternativeModel !== undefined) {
infiniteRepeater.itemAt(0).gridModel = alternativeModel;
infiniteRepeater.itemAt(2).gridModel = alternativeModel;
}
}
/*----------------------------------------------------- helper functions ---------------------------------------------------------------*/
function resetIndexTo(index: int, duration = 0) {
contentItem.highlightMoveDuration = duration;
if (currentIndex !== index) {
currentIndex = index;
}
}
function changeDateOfView() {
const swipedUp = currentIndex == 2;
switch(root.viewType) {
case InfiniteList.ViewType.DayView:
swipedUp ? root.backend.nextMonth() : root.backend.previousMonth();
break;
case InfiniteList.ViewType.YearView:
swipedUp ? root.backend.nextYear() : root.backend.previousYear();
break;
case InfiniteList.ViewType.DecadeView:
swipedUp ? root.backend.nextDecade() : root.backend.previousDecade();
break;
}
}
function adjustDate() {
const inMiddle = currentIndex == 1;
if (handlingIndexChange || inMiddle) return;
handlingIndexChange = true;
changeDateOfView();
resetIndexTo(1); //back to middle
handlingIndexChange = false;
}
function animate(direction) {
if (handlingIndexChange) return;
const targetIndex = (direction === InfiniteList.AnimationDirection.Upward) ? 0 : 2;
handlingIndexChange = true;
resetIndexTo(targetIndex); //move to edge from middle
resetIndexTo(1, highlightMoveDuration); // come back to middle with non-zero animation duration
handlingIndexChange = false;
}
function animateDateChange(toFuture = undefined) {
const month = root.backend.month - 1;
const year = root.backend.year;
let goToFuture = false;
if(toFuture === undefined) {
switch(root.viewType) {
case InfiniteList.ViewType.DayView:
if (month === lastMonth) return;
goToFuture = (month > lastMonth || year > lastYear) && !(year < lastYear);
break;
default:
if (year === lastYear) return;
goToFuture = year > lastYear;
break;
}
} else {
goToFuture = toFuture;
}
if (goToFuture) {
animate(InfiniteList.AnimationDirection.Upward);
} else {
animate(InfiniteList.AnimationDirection.Downward);
}
lastMonth = month;
lastYear = year;
}
// used to update the alternative decadeview models when year changes
function updateDecadeOverview() {
const date = backend.displayedDate;
const day = date.getDate();
const month = date.getMonth() + 1;
const year = date.getFullYear();
const decade = year - year % 10;
for (let i = 0, j = yearModel.count; i < j; ++i) {
const label = decade - 1 + i;
yearModel.setProperty(i, "yearNumber", label);
yearModel.setProperty(i, "label", label);
}
}
/*----------------------------------------------------- alternative models ---------------------------------------------------------------*/
Calendar {
id: backend
days: root.backend.days
weeks: root.backend.weeks
firstDayOfWeek: root.backend.firstDayOfWeek
today: root.backend.today
Component.onCompleted: {
daysModel.setPluginsManager(root.eventPluginsManager);
}
}
ListModel {
id: yearModel
Component.onCompleted: {
for (let i = 0; i < 12; ++i) {
append({
label: 2050, // this value will be overwritten, but it set the type of the property to int
yearNumber: 2050,
isCurrent: (i > 0 && i < 11) // first and last year are outside the decade
})
}
infiniteList.updateDecadeOverview();
}
}
}
/*----------------------------------------------------- public functions ---------------------------------------------------------------*/
function nextView() {
switch(root.viewType) {
case InfiniteList.ViewType.DayView:
backend.goToMonth(root.backend.month);
backend.goToYear(root.backend.year);
root.backend.nextMonth();
break;
case InfiniteList.ViewType.YearView:
root.backend.nextYear();
break;
case InfiniteList.ViewType.DecadeView:
backend.goToYear(root.backend.year);
infiniteList.updateDecadeOverview();
root.backend.nextDecade();
break;
}
}
function previousView() {
switch(root.viewType) {
case InfiniteList.ViewType.DayView:
backend.goToMonth(root.backend.month);
backend.goToYear(root.backend.year);
root.backend.previousMonth();
break;
case InfiniteList.ViewType.YearView:
root.backend.previousYear();
break;
case InfiniteList.ViewType.DecadeView:
backend.goToYear(root.backend.year);
infiniteList.updateDecadeOverview();
root.backend.previousDecade();
break;
}
}
function resetToToday() {
backend.goToMonth(root.backend.month);
backend.goToYear(root.backend.year);
root.backend.resetToToday();
}
function focusFirstCellOfView() {
infiniteList.currentItem.repeater.itemAt(0).forceActiveFocus(Qt.TabFocusReason);
infiniteList.resetIndexTo(1)
infiniteList.currentItem.repeater.itemAt(0).forceActiveFocus(Qt.TabFocusReason);
}
}