Merge lp:~frankencode/ubuntu-calendar-app/MonthView into lp:ubuntu-calendar-app

Proposed by Frank Mertens
Status: Merged
Merged at revision: 6
Proposed branch: lp:~frankencode/ubuntu-calendar-app/MonthView
Merge into: lp:ubuntu-calendar-app
Diff against target: 620 lines (+409/-118)
7 files modified
Calendar.desktop (+9/-0)
DateLib.js (+47/-5)
EventView.qml (+72/-0)
MonthView.qml (+178/-55)
calendar.qml (+89/-58)
colorUtils.js (+10/-0)
testrun.sh (+4/-0)
To merge this branch: bzr merge lp:~frankencode/ubuntu-calendar-app/MonthView
Reviewer Review Type Date Requested Status
Mario Boikov (community) Approve
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Review via email: mp+152655@code.launchpad.net

Commit message

First basic prototype of the calendar key journeys.

Description of the change

First basic prototype of the calendar key journeys.

To post a comment you must log in.
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Mario Boikov (mariob) wrote :

In 'daysInMonth' function: Shouldn't the leap year thing only be applied to 'February'?

Now every month gets an extra day for a leap year. Would be great thou :)

24. By Frank Mertens

Fix for leap year: add leap day only to Feb.

Revision history for this message
Frank Mertens (frankencode) wrote :

Ups, smth. got lost there when I was reformatting the code from C++.
(You have eagles eyes!)

Revision history for this message
Mario Boikov (mariob) wrote :

> Ups, smth. got lost there when I was reformatting the code from C++.
> (You have eagles eyes!)

LOL! Actually, I was doing a similar function myself the other day, that why I spotted it :)

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Mario Boikov (mariob) wrote :

Looks good to me

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'Calendar.desktop'
2--- Calendar.desktop 1970-01-01 00:00:00 +0000
3+++ Calendar.desktop 2013-03-11 18:20:26 +0000
4@@ -0,0 +1,9 @@
5+[Desktop Entry]
6+Encoding=UTF-8
7+Type=Application
8+Terminal=false
9+Exec=/usr/bin/qmlscene
10+Name=Calendar
11+GenericName=Calendar
12+Comment=Ubuntu Calendar
13+Icon=ubuntu-qmlrunner
14
15=== renamed file 'dateTimeUtils.js' => 'DateLib.js'
16--- dateTimeUtils.js 2013-02-14 18:03:27 +0000
17+++ DateLib.js 2013-03-11 18:20:26 +0000
18@@ -1,17 +1,51 @@
19 .pragma library
20
21-var msPerDay = 86400e3
22-var msPerWeek = msPerDay * 7
23+Date.msPerDay = 86400e3
24+Date.msPerWeek = Date.msPerDay * 7
25+
26+Date.leapYear = function(year) {
27+ return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
28+}
29+
30+Date.daysInMonth = function(year, month) {
31+ return [
32+ 31/*Jan*/, 28/*Feb*/, 31/*Mar*/, 30/*Apr*/, 31/*May*/, 30/*Jun*/,
33+ 31/*Jul*/, 31/*Aug*/, 30/*Sep*/, 31/*Oct*/, 30/*Nov*/, 31/*Dec*/
34+ ][month] + (month == 1) * Date.leapYear(year)
35+}
36+
37+Date.weeksInMonth = function(year, month, weekday) {
38+ var y = year, m = month
39+ var date0 = new Date(y, m, 1)
40+ var date1 = new Date(y + (m == 11), m < 11 ? m + 1 : 0, 1)
41+ var day = date0.getDay()
42+ var m = (date1.getTime() - date0.getTime()) / Date.msPerDay
43+ var n = 0
44+ while (m > 0) {
45+ if (day == weekday) n = n + 1
46+ day = day < 6 ? day + 1 : 0
47+ m = m - 1
48+ }
49+ return n
50+}
51
52 Date.prototype.midnight = function() {
53 var date = new Date(this)
54- date.setTime(date.getTime() - date.getTime() % msPerDay)
55+ var t = date.getTime()
56+ if (t % Date.msPerDay != 0)
57+ date.setTime(t - t % Date.msPerDay)
58 return date
59 }
60
61 Date.prototype.addDays = function(days) {
62 var date = new Date(this)
63- date.setTime(date.getTime() + msPerDay * days)
64+ date.setTime(date.getTime() + Date.msPerDay * days)
65+ return date
66+}
67+
68+Date.prototype.addMonths = function(months) {
69+ var date = new Date(this)
70+ date.setUTCMonth(date.getUTCMonth() + months)
71 return date
72 }
73
74@@ -26,14 +60,22 @@
75 return date.addDays(-n)
76 }
77
78+Date.prototype.monthStart = function() {
79+ return this.midnight().addDays(1 - this.getDate())
80+}
81+
82 Date.prototype.weekNumber = function() {
83 var date = this.weekStart(1).addDays(3) // Thursday midnight
84 var newYear = new Date(date.getFullYear(), 0 /*Jan*/, 1 /*the 1st*/)
85 var n = 0
86 var tx = date.getTime(), tn = newYear.getTime()
87 while (tn < tx) {
88- tx = tx - msPerWeek
89+ tx = tx - Date.msPerWeek
90 n = n + 1
91 }
92 return n
93 }
94+
95+Date.prototype.weeksInMonth = function(weekday) {
96+ return Date.weeksInMonth(this.getFullYear(), this.getMonth(), weekday)
97+}
98
99=== added file 'EventView.qml'
100--- EventView.qml 1970-01-01 00:00:00 +0000
101+++ EventView.qml 2013-03-11 18:20:26 +0000
102@@ -0,0 +1,72 @@
103+import QtQuick 2.0
104+import Ubuntu.Components 0.1
105+import "DateLib.js" as DateLib
106+
107+PathView {
108+ id: eventView
109+
110+ property var currentDayStart: (new Date()).midnight()
111+
112+ signal incrementCurrentDay
113+ signal decrementCurrentDay
114+
115+ readonly property real visibleHeight: parent.height - y
116+
117+ QtObject {
118+ id: intern
119+ property int currentIndexSaved: 0
120+ property int currentIndex: 0
121+ property var currentDayStart: (new Date()).midnight()
122+ }
123+
124+ onCurrentIndexChanged: {
125+ var delta = currentIndex - intern.currentIndexSaved
126+ if (intern.currentIndexSaved == count - 1 && currentIndex == 0) delta = 1
127+ if (intern.currentIndexSaved == 0 && currentIndex == count - 1) delta = -1
128+ intern.currentIndexSaved = currentIndex
129+ if (delta > 0) incrementCurrentDay()
130+ else decrementCurrentDay()
131+ }
132+
133+ onCurrentDayStartChanged: {
134+ if (!moving) intern.currentDayStart = currentDayStart
135+ }
136+
137+ onMovementEnded: {
138+ intern.currentDayStart = currentDayStart
139+ intern.currentIndex = currentIndex
140+ }
141+
142+ preferredHighlightBegin: 0.5
143+ preferredHighlightEnd: 0.5
144+ highlightRangeMode: PathView.StrictlyEnforceRange
145+
146+ path: Path {
147+ startX: -eventView.width; startY: eventView.height / 2
148+ PathLine { relativeX: eventView.width; relativeY: 0 }
149+ PathLine { relativeX: eventView.width; relativeY: 0 }
150+ PathLine { relativeX: eventView.width; relativeY: 0 }
151+ }
152+
153+ snapMode: PathView.SnapOneItem
154+
155+ model: 3
156+
157+ delegate: Rectangle {
158+ property var dayStart: {
159+ if (index == intern.currentIndex) return intern.currentDayStart
160+ var previousIndex = intern.currentIndex > 0 ? intern.currentIndex - 1 : 2
161+ if (index == previousIndex) return intern.currentDayStart.addDays(-1)
162+ return intern.currentDayStart.addDays(1)
163+ }
164+ width: eventView.width
165+ height: eventView.height
166+ color: index == 0 ? "#FFFFFF" : index == 1 ? "#EEEEEE" : "#DDDDDD"
167+ Label {
168+ anchors.horizontalCenter: parent.horizontalCenter
169+ y: units.gu(4)
170+ text: i18n.tr("No events for") + "\n" + Qt.formatDate(dayStart)
171+ fontSize: "large"
172+ }
173+ }
174+}
175
176=== modified file 'MonthView.qml'
177--- MonthView.qml 2013-02-15 08:10:23 +0000
178+++ MonthView.qml 2013-03-11 18:20:26 +0000
179@@ -1,73 +1,196 @@
180 import QtQuick 2.0
181 import Ubuntu.Components 0.1
182-import "dateTimeUtils.js" as DateTime
183+import "DateLib.js" as DateLib
184+import "colorUtils.js" as Color
185
186 ListView {
187- id: monthView // id for internal reference
188-
189- // public properties
190- property bool portraitMode: width < height
191- property real weeksInView: 8
192- property int weekStartDay: 1 // Monday, FIXME: depends on locale / user settings
193-
194- // private properties
195+ id: monthView
196+
197+ readonly property var monthStart: currentItem != null ? currentItem.monthStart : (new Date()).monthStart()
198+ readonly property var monthEnd: currentItem != null ? currentItem.monthEnd : (new Date()).monthStart().addMonths(1)
199+ readonly property var currentDayStart: intern.currentDayStart
200+
201+ property bool compressed: false
202+ property real compressedHeight: intern.squareUnit + intern.verticalMargin * 2
203+
204+ signal incrementCurrentDay
205+ signal decrementCurrentDay
206+
207+ signal gotoNextMonth(int month)
208+ signal focusOnDay(var dayStart)
209+
210+ onCurrentItemChanged: {
211+ if (currentItem == null) {
212+ intern.currentDayStart = intern.currentDayStart
213+ return
214+ }
215+ if (currentItem.monthStart <= intern.currentDayStart && intern.currentDayStart < currentItem.monthEnd)
216+ return
217+ if (currentItem.monthStart <= intern.today && intern.today < currentItem.monthEnd)
218+ intern.currentDayStart = intern.today
219+ else
220+ intern.currentDayStart = currentItem.monthStart
221+ }
222+
223+ onIncrementCurrentDay: {
224+ var t = intern.currentDayStart.addDays(1)
225+ if (t < monthEnd) {
226+ intern.currentDayStart = t
227+ }
228+ else if (currentIndex < count - 1) {
229+ intern.currentDayStart = t
230+ currentIndex = currentIndex + 1
231+ }
232+ }
233+
234+ onDecrementCurrentDay: {
235+ var t = intern.currentDayStart.addDays(-1)
236+ if (t >= monthStart) {
237+ intern.currentDayStart = t
238+ }
239+ else if (currentIndex > 0) {
240+ intern.currentDayStart = t
241+ currentIndex = currentIndex - 1
242+ }
243+ }
244+
245+ onGotoNextMonth: {
246+ if (monthStart.getMonth() != month) {
247+ var i = intern.monthIndex0, m = intern.today.getMonth()
248+ while (m != month) {
249+ m = (m + 1) % 12
250+ i = i + 1
251+ }
252+ currentIndex = i
253+ }
254+ }
255+
256+ onFocusOnDay: {
257+ if (dayStart < monthStart) {
258+ if (currentIndex > 0) {
259+ intern.currentDayStart = dayStart
260+ currentIndex = currentIndex - 1
261+ }
262+ }
263+ else if (dayStart >= monthEnd) {
264+ if (currentIndex < count - 1) {
265+ intern.currentDayStart = dayStart
266+ currentIndex = currentIndex + 1
267+ }
268+ }
269+ else intern.currentDayStart = dayStart
270+ }
271+
272+ focus: true
273+ Keys.onLeftPressed: decrementCurrentDay()
274+ Keys.onRightPressed: incrementCurrentDay()
275+
276 QtObject {
277- id: internal
278-
279- property real weekHeight: monthView.height / monthView.weeksInView | 0
280- property int indexOrigin: monthView.count / 2
281- property var timeOrigin: (new Date()).weekStart(monthView.weekStartDay)
282- property var today: (new Date()).midnight()
283+ id: intern
284+
285+ property int squareUnit: monthView.width / 8
286+ property int verticalMargin: units.gu(1)
287+ property int weekstartDay: Qt.locale().firstDayOfWeek
288+ property int monthCount: 49 // months for +-2 years
289+
290+ property var today: (new Date()).midnight() // TODO: update at midnight
291+ property var currentDayStart: today
292+ property int monthIndex0: Math.floor(monthCount / 2)
293+ property var monthStart0: today.monthStart()
294 }
295
296+ width: parent.width
297+ height: intern.squareUnit * 6 + intern.verticalMargin * 2
298+
299+ interactive: !compressed
300 clip: true
301-
302- model: 1041 // weeks for about +-10y
303+ orientation: ListView.Horizontal
304+ snapMode: ListView.SnapOneItem
305+ cacheBuffer: width + 1
306+
307+ highlightRangeMode: ListView.StrictlyEnforceRange
308+ preferredHighlightBegin: 0
309+ preferredHighlightEnd: width
310+
311+ model: intern.monthCount
312+ currentIndex: intern.monthIndex0
313
314 delegate: Item {
315- id: weekItem
316-
317- property var weekOrigin: internal.timeOrigin.addDays((index - internal.indexOrigin) * 7)
318-
319- width: parent.width
320- height: internal.weekHeight
321-
322- Row {
323- anchors.fill: parent
324- spacing: 1
325+ id: monthItem
326+
327+ property var monthStart: intern.monthStart0.addMonths(index - intern.monthIndex0)
328+ property var monthEnd: monthStart.addMonths(1)
329+ property var gridStart: monthStart.weekStart(intern.weekstartDay)
330+ property int currentWeekRow: Math.floor((currentDayStart.getTime() - gridStart.getTime()) / Date.msPerWeek)
331+
332+ width: monthView.width
333+ height: monthView.height
334+
335+ Grid {
336+ id: monthGrid
337+
338+ rows: 6
339+ columns: 7
340+
341+ x: intern.squareUnit / 2
342+ y: intern.verticalMargin
343+ width: intern.squareUnit * columns
344+ height: intern.squareUnit * rows
345
346 Repeater {
347- model: 7
348- delegate: Rectangle {
349+ model: monthGrid.rows * monthGrid.columns
350+ delegate: Item {
351 id: dayItem
352-
353- property var dayOrigin: weekOrigin.addDays(index)
354- property bool isToday: internal.today.getTime() == dayOrigin.getTime()
355-
356- width: weekItem.width / 7
357- height: weekItem.height - 1
358- color: isToday ? "#c94212" : dayOrigin.getMonth() % 2 ? "#c4c4c4" : "#e0e0e0"
359-
360- Label {
361+ property var dayStart: gridStart.addDays(index)
362+ property bool isCurrentMonth: monthStart <= dayStart && dayStart < monthEnd
363+ property bool isToday: dayStart.getTime() == intern.today.getTime()
364+ property bool isCurrent: dayStart.getTime() == intern.currentDayStart.getTime()
365+ property int weekday: (index % 7 + intern.weekstartDay) % 7
366+ property bool isSunday: weekday == 0
367+ property int row: Math.floor(index / 7)
368+ property bool isCurrentWeek: row == currentWeekRow
369+ property real topMargin: (row == 0 || (monthView.compressed && isCurrentWeek)) ? -intern.verticalMargin : 0
370+ property real bottomMargin: (row == 5 || (monthView.compressed && isCurrentWeek)) ? -intern.verticalMargin : 0
371+ visible: monthView.compressed ? isCurrentWeek : true
372+ width: intern.squareUnit
373+ height: intern.squareUnit
374+ Rectangle {
375+ visible: isSunday
376+ anchors.fill: parent
377+ anchors.topMargin: dayItem.topMargin
378+ anchors.bottomMargin: dayItem.bottomMargin
379+ color: Color.warmGrey
380+ opacity: 0.1
381+ }
382+ Text {
383 anchors.centerIn: parent
384- text: dayOrigin.getDate()
385- color: isToday ? "white" : dayOrigin.getDay() == 0 ? "#c94212" : "#404040"
386- }
387+ text: dayStart.getDate()
388+ font: themeDummy.font
389+ color: isToday ? Color.ubuntuOrange : themeDummy.color
390+ scale: isCurrent ? 1.8 : 1.
391+ opacity: isCurrentMonth ? 1. : 0.3
392+ Behavior on scale {
393+ NumberAnimation { duration: 50 }
394+ }
395+ }
396+ MouseArea {
397+ anchors.fill: parent
398+ anchors.topMargin: dayItem.topMargin
399+ anchors.bottomMargin: dayItem.bottomMargin
400+ onReleased: monthView.focusOnDay(dayStart)
401+ }
402+ // Component.onCompleted: console.log(dayStart, intern.currentDayStart)
403 }
404 }
405 }
406- }
407-
408- Timer { // make sure today is updated at midnight
409- interval: 1000
410- repeat: true
411- running: true
412-
413- onTriggered: {
414- var newDate = (new Date()).midnight()
415- if (internal.today < newDate) internal.today = newDate
416- }
417- }
418-
419- Component.onCompleted: positionViewAtIndex(internal.indexOrigin, ListView.Center)
420+
421+ // Component.onCompleted: console.log("Created delegate for month", index, monthStart, gridStart, currentWeekRow, currentWeekRowReal)
422+ }
423+
424+ Label {
425+ visible: false
426+ id: themeDummy
427+ fontSize: "large"
428+ // Component.onCompleted: console.log(color, Qt.lighter(color, 1.74))
429+ }
430 }
431
432=== modified file 'calendar.qml'
433--- calendar.qml 2013-02-14 18:03:27 +0000
434+++ calendar.qml 2013-03-11 18:20:26 +0000
435@@ -1,72 +1,103 @@
436 import QtQuick 2.0
437 import Ubuntu.Components 0.1
438
439-/*!
440- \brief MainView with Tabs element.
441- First Tab has a single Label and
442- second Tab has a single ToolbarAction.
443-*/
444-
445 MainView {
446- // objectName for functional testing purposes (autopilot-qt5)
447- objectName: "calendar"
448-
449 id: mainView
450 width: units.gu(45)
451 height: units.gu(80)
452- // FIXME: 80/45 = aspect ration of galaxy nexus
453+ // FIXME: 80/45 = aspect ration of Galaxy Nexus
454
455- Tabs {
456+ Tabs { // preliminary HACK, needs rewrite when NewTabBar is finalized!
457 id: tabs
458 anchors.fill: parent
459
460- // First tab begins here
461- Tab {
462- objectName: "Tab1"
463-
464- title: i18n.tr("Calendar")
465-
466- // Tab content begins here
467- page: Page {
468- MonthView {
469- width: mainView.width
470- height: mainView.height
471- // anchors.fill: parent // FIXME: delivers broken geometry on startup
472- }
473- }
474- }
475-
476- // Second tab begins here
477- Tab {
478- objectName: "Tab2"
479-
480- title: i18n.tr("Optional Screen")
481- page: Page {
482- anchors.margins: units.gu(2)
483-
484- tools: ToolbarActions {
485- Action {
486- objectName: "action"
487-
488- iconSource: Qt.resolvedUrl("avatar.png")
489- text: i18n.tr("Tap me!")
490-
491- onTriggered: {
492- label.text = i18n.tr("Toolbar tapped")
493- }
494- }
495- }
496-
497- Column {
498- anchors.centerIn: parent
499- Label {
500- id: label
501- objectName: "label"
502-
503- text: i18n.tr("Swipe from bottom to up to reveal the toolbar.")
504- }
505- }
506- }
507+ Tab { id: pageArea; title: i18n.tr("January"); page: Item { anchors.fill: parent } }
508+ Tab { title: i18n.tr("February") }
509+ Tab { title: i18n.tr("March") }
510+ Tab { title: i18n.tr("April") }
511+ Tab { title: i18n.tr("May") }
512+ Tab { title: i18n.tr("June") }
513+ Tab { title: i18n.tr("July") }
514+ Tab { title: i18n.tr("August") }
515+ Tab { title: i18n.tr("September") }
516+ Tab { title: i18n.tr("October") }
517+ Tab { title: i18n.tr("November") }
518+ Tab { title: i18n.tr("December") }
519+
520+ onSelectedTabIndexChanged: monthView.gotoNextMonth(selectedTabIndex)
521+ }
522+
523+ MonthView {
524+ id: monthView
525+ onMonthStartChanged: tabs.selectedTabIndex = monthStart.getMonth()
526+ y: pageArea.y
527+ }
528+
529+ EventView {
530+ id: eventView
531+ property real minY: pageArea.y + monthView.compressedHeight
532+ property real maxY: pageArea.y + monthView.height
533+ y: maxY
534+ width: mainView.width
535+ height: parent.height - monthView.compressedHeight
536+ currentDayStart: monthView.currentDayStart
537+ Component.onCompleted: {
538+ incrementCurrentDay.connect(monthView.incrementCurrentDay)
539+ decrementCurrentDay.connect(monthView.decrementCurrentDay)
540+ }
541+ MouseArea {
542+ id: drawer
543+ property bool compression: true
544+ anchors.fill: parent
545+ drag {
546+ axis: Drag.YAxis
547+ target: eventView
548+ minimumY: monthView.y + monthView.compressedHeight
549+ maximumY: monthView.y + monthView.height
550+ onActiveChanged: {
551+ if (compression) {
552+ if (drag.active) {
553+ monthView.compressed = true
554+ }
555+ else {
556+ yBehavior.enabled = true
557+ eventView.y = Qt.binding(function() { return eventView.minY })
558+ compression = false
559+ }
560+ }
561+ else {
562+ if (drag.active) {}
563+ else{
564+ eventView.y = Qt.binding(function() { return eventView.maxY })
565+ monthView.compressed = false
566+ compression = true
567+ }
568+ }
569+ }
570+ }
571+ }
572+ Behavior on y {
573+ id: yBehavior
574+ enabled: false
575+ NumberAnimation { duration: 100 }
576+ }
577+ }
578+
579+ tools: ToolbarActions {
580+ Action {
581+ iconSource: Qt.resolvedUrl("avatar.png")
582+ text: i18n.tr("To-do")
583+ onTriggered:; // FIXME
584+ }
585+ Action {
586+ iconSource: Qt.resolvedUrl("avatar.png")
587+ text: i18n.tr("New Event")
588+ onTriggered:; // FIXME
589+ }
590+ Action {
591+ iconSource: Qt.resolvedUrl("avatar.png")
592+ text: i18n.tr("Timeline")
593+ onTriggered:; // FIXME
594 }
595 }
596 }
597
598=== added file 'colorUtils.js'
599--- colorUtils.js 1970-01-01 00:00:00 +0000
600+++ colorUtils.js 2013-03-11 18:20:26 +0000
601@@ -0,0 +1,10 @@
602+.pragma library
603+
604+// colors for custom widgets
605+// see: http://design.ubuntu.com/brand/colour-palette
606+
607+var ubuntuOrange = "#DD4814"
608+var canonicalAubergine = "#772953"
609+var lightAubergine = "#77216F"
610+var warmGrey = "#AEA79F"
611+var coolGrey = "#333333"
612
613=== added file 'testrun.sh'
614--- testrun.sh 1970-01-01 00:00:00 +0000
615+++ testrun.sh 2013-03-11 18:20:26 +0000
616@@ -0,0 +1,4 @@
617+#! /bin/bash -ex
618+
619+rsync -aHv ./ phablet@nexus:/tmp/Calendar/
620+ssh -t phablet@nexus 'cd /tmp/Calendar && GRID_UNIT_PX=18 qmlscene --desktop_file_hint=$PWD/Calendar.desktop $PWD/calendar.qml'

Subscribers

People subscribed via source and target branches

to status/vote changes: