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
=== added file 'Calendar.desktop'
--- Calendar.desktop 1970-01-01 00:00:00 +0000
+++ Calendar.desktop 2013-03-11 18:20:26 +0000
@@ -0,0 +1,9 @@
1[Desktop Entry]
2Encoding=UTF-8
3Type=Application
4Terminal=false
5Exec=/usr/bin/qmlscene
6Name=Calendar
7GenericName=Calendar
8Comment=Ubuntu Calendar
9Icon=ubuntu-qmlrunner
010
=== renamed file 'dateTimeUtils.js' => 'DateLib.js'
--- dateTimeUtils.js 2013-02-14 18:03:27 +0000
+++ DateLib.js 2013-03-11 18:20:26 +0000
@@ -1,17 +1,51 @@
1.pragma library1.pragma library
22
3var msPerDay = 86400e33Date.msPerDay = 86400e3
4var msPerWeek = msPerDay * 74Date.msPerWeek = Date.msPerDay * 7
5
6Date.leapYear = function(year) {
7 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
8}
9
10Date.daysInMonth = function(year, month) {
11 return [
12 31/*Jan*/, 28/*Feb*/, 31/*Mar*/, 30/*Apr*/, 31/*May*/, 30/*Jun*/,
13 31/*Jul*/, 31/*Aug*/, 30/*Sep*/, 31/*Oct*/, 30/*Nov*/, 31/*Dec*/
14 ][month] + (month == 1) * Date.leapYear(year)
15}
16
17Date.weeksInMonth = function(year, month, weekday) {
18 var y = year, m = month
19 var date0 = new Date(y, m, 1)
20 var date1 = new Date(y + (m == 11), m < 11 ? m + 1 : 0, 1)
21 var day = date0.getDay()
22 var m = (date1.getTime() - date0.getTime()) / Date.msPerDay
23 var n = 0
24 while (m > 0) {
25 if (day == weekday) n = n + 1
26 day = day < 6 ? day + 1 : 0
27 m = m - 1
28 }
29 return n
30}
531
6Date.prototype.midnight = function() {32Date.prototype.midnight = function() {
7 var date = new Date(this)33 var date = new Date(this)
8 date.setTime(date.getTime() - date.getTime() % msPerDay)34 var t = date.getTime()
35 if (t % Date.msPerDay != 0)
36 date.setTime(t - t % Date.msPerDay)
9 return date37 return date
10}38}
1139
12Date.prototype.addDays = function(days) {40Date.prototype.addDays = function(days) {
13 var date = new Date(this)41 var date = new Date(this)
14 date.setTime(date.getTime() + msPerDay * days)42 date.setTime(date.getTime() + Date.msPerDay * days)
43 return date
44}
45
46Date.prototype.addMonths = function(months) {
47 var date = new Date(this)
48 date.setUTCMonth(date.getUTCMonth() + months)
15 return date49 return date
16}50}
1751
@@ -26,14 +60,22 @@
26 return date.addDays(-n)60 return date.addDays(-n)
27}61}
2862
63Date.prototype.monthStart = function() {
64 return this.midnight().addDays(1 - this.getDate())
65}
66
29Date.prototype.weekNumber = function() {67Date.prototype.weekNumber = function() {
30 var date = this.weekStart(1).addDays(3) // Thursday midnight68 var date = this.weekStart(1).addDays(3) // Thursday midnight
31 var newYear = new Date(date.getFullYear(), 0 /*Jan*/, 1 /*the 1st*/)69 var newYear = new Date(date.getFullYear(), 0 /*Jan*/, 1 /*the 1st*/)
32 var n = 070 var n = 0
33 var tx = date.getTime(), tn = newYear.getTime()71 var tx = date.getTime(), tn = newYear.getTime()
34 while (tn < tx) {72 while (tn < tx) {
35 tx = tx - msPerWeek73 tx = tx - Date.msPerWeek
36 n = n + 174 n = n + 1
37 }75 }
38 return n76 return n
39}77}
78
79Date.prototype.weeksInMonth = function(weekday) {
80 return Date.weeksInMonth(this.getFullYear(), this.getMonth(), weekday)
81}
4082
=== added file 'EventView.qml'
--- EventView.qml 1970-01-01 00:00:00 +0000
+++ EventView.qml 2013-03-11 18:20:26 +0000
@@ -0,0 +1,72 @@
1import QtQuick 2.0
2import Ubuntu.Components 0.1
3import "DateLib.js" as DateLib
4
5PathView {
6 id: eventView
7
8 property var currentDayStart: (new Date()).midnight()
9
10 signal incrementCurrentDay
11 signal decrementCurrentDay
12
13 readonly property real visibleHeight: parent.height - y
14
15 QtObject {
16 id: intern
17 property int currentIndexSaved: 0
18 property int currentIndex: 0
19 property var currentDayStart: (new Date()).midnight()
20 }
21
22 onCurrentIndexChanged: {
23 var delta = currentIndex - intern.currentIndexSaved
24 if (intern.currentIndexSaved == count - 1 && currentIndex == 0) delta = 1
25 if (intern.currentIndexSaved == 0 && currentIndex == count - 1) delta = -1
26 intern.currentIndexSaved = currentIndex
27 if (delta > 0) incrementCurrentDay()
28 else decrementCurrentDay()
29 }
30
31 onCurrentDayStartChanged: {
32 if (!moving) intern.currentDayStart = currentDayStart
33 }
34
35 onMovementEnded: {
36 intern.currentDayStart = currentDayStart
37 intern.currentIndex = currentIndex
38 }
39
40 preferredHighlightBegin: 0.5
41 preferredHighlightEnd: 0.5
42 highlightRangeMode: PathView.StrictlyEnforceRange
43
44 path: Path {
45 startX: -eventView.width; startY: eventView.height / 2
46 PathLine { relativeX: eventView.width; relativeY: 0 }
47 PathLine { relativeX: eventView.width; relativeY: 0 }
48 PathLine { relativeX: eventView.width; relativeY: 0 }
49 }
50
51 snapMode: PathView.SnapOneItem
52
53 model: 3
54
55 delegate: Rectangle {
56 property var dayStart: {
57 if (index == intern.currentIndex) return intern.currentDayStart
58 var previousIndex = intern.currentIndex > 0 ? intern.currentIndex - 1 : 2
59 if (index == previousIndex) return intern.currentDayStart.addDays(-1)
60 return intern.currentDayStart.addDays(1)
61 }
62 width: eventView.width
63 height: eventView.height
64 color: index == 0 ? "#FFFFFF" : index == 1 ? "#EEEEEE" : "#DDDDDD"
65 Label {
66 anchors.horizontalCenter: parent.horizontalCenter
67 y: units.gu(4)
68 text: i18n.tr("No events for") + "\n" + Qt.formatDate(dayStart)
69 fontSize: "large"
70 }
71 }
72}
073
=== modified file 'MonthView.qml'
--- MonthView.qml 2013-02-15 08:10:23 +0000
+++ MonthView.qml 2013-03-11 18:20:26 +0000
@@ -1,73 +1,196 @@
1import QtQuick 2.01import QtQuick 2.0
2import Ubuntu.Components 0.12import Ubuntu.Components 0.1
3import "dateTimeUtils.js" as DateTime3import "DateLib.js" as DateLib
4import "colorUtils.js" as Color
45
5ListView {6ListView {
6 id: monthView // id for internal reference7 id: monthView
78
8 // public properties9 readonly property var monthStart: currentItem != null ? currentItem.monthStart : (new Date()).monthStart()
9 property bool portraitMode: width < height10 readonly property var monthEnd: currentItem != null ? currentItem.monthEnd : (new Date()).monthStart().addMonths(1)
10 property real weeksInView: 811 readonly property var currentDayStart: intern.currentDayStart
11 property int weekStartDay: 1 // Monday, FIXME: depends on locale / user settings12
1213 property bool compressed: false
13 // private properties14 property real compressedHeight: intern.squareUnit + intern.verticalMargin * 2
15
16 signal incrementCurrentDay
17 signal decrementCurrentDay
18
19 signal gotoNextMonth(int month)
20 signal focusOnDay(var dayStart)
21
22 onCurrentItemChanged: {
23 if (currentItem == null) {
24 intern.currentDayStart = intern.currentDayStart
25 return
26 }
27 if (currentItem.monthStart <= intern.currentDayStart && intern.currentDayStart < currentItem.monthEnd)
28 return
29 if (currentItem.monthStart <= intern.today && intern.today < currentItem.monthEnd)
30 intern.currentDayStart = intern.today
31 else
32 intern.currentDayStart = currentItem.monthStart
33 }
34
35 onIncrementCurrentDay: {
36 var t = intern.currentDayStart.addDays(1)
37 if (t < monthEnd) {
38 intern.currentDayStart = t
39 }
40 else if (currentIndex < count - 1) {
41 intern.currentDayStart = t
42 currentIndex = currentIndex + 1
43 }
44 }
45
46 onDecrementCurrentDay: {
47 var t = intern.currentDayStart.addDays(-1)
48 if (t >= monthStart) {
49 intern.currentDayStart = t
50 }
51 else if (currentIndex > 0) {
52 intern.currentDayStart = t
53 currentIndex = currentIndex - 1
54 }
55 }
56
57 onGotoNextMonth: {
58 if (monthStart.getMonth() != month) {
59 var i = intern.monthIndex0, m = intern.today.getMonth()
60 while (m != month) {
61 m = (m + 1) % 12
62 i = i + 1
63 }
64 currentIndex = i
65 }
66 }
67
68 onFocusOnDay: {
69 if (dayStart < monthStart) {
70 if (currentIndex > 0) {
71 intern.currentDayStart = dayStart
72 currentIndex = currentIndex - 1
73 }
74 }
75 else if (dayStart >= monthEnd) {
76 if (currentIndex < count - 1) {
77 intern.currentDayStart = dayStart
78 currentIndex = currentIndex + 1
79 }
80 }
81 else intern.currentDayStart = dayStart
82 }
83
84 focus: true
85 Keys.onLeftPressed: decrementCurrentDay()
86 Keys.onRightPressed: incrementCurrentDay()
87
14 QtObject {88 QtObject {
15 id: internal89 id: intern
1690
17 property real weekHeight: monthView.height / monthView.weeksInView | 091 property int squareUnit: monthView.width / 8
18 property int indexOrigin: monthView.count / 292 property int verticalMargin: units.gu(1)
19 property var timeOrigin: (new Date()).weekStart(monthView.weekStartDay)93 property int weekstartDay: Qt.locale().firstDayOfWeek
20 property var today: (new Date()).midnight()94 property int monthCount: 49 // months for +-2 years
95
96 property var today: (new Date()).midnight() // TODO: update at midnight
97 property var currentDayStart: today
98 property int monthIndex0: Math.floor(monthCount / 2)
99 property var monthStart0: today.monthStart()
21 }100 }
22101
102 width: parent.width
103 height: intern.squareUnit * 6 + intern.verticalMargin * 2
104
105 interactive: !compressed
23 clip: true106 clip: true
24107 orientation: ListView.Horizontal
25 model: 1041 // weeks for about +-10y108 snapMode: ListView.SnapOneItem
109 cacheBuffer: width + 1
110
111 highlightRangeMode: ListView.StrictlyEnforceRange
112 preferredHighlightBegin: 0
113 preferredHighlightEnd: width
114
115 model: intern.monthCount
116 currentIndex: intern.monthIndex0
26117
27 delegate: Item {118 delegate: Item {
28 id: weekItem119 id: monthItem
29120
30 property var weekOrigin: internal.timeOrigin.addDays((index - internal.indexOrigin) * 7)121 property var monthStart: intern.monthStart0.addMonths(index - intern.monthIndex0)
31122 property var monthEnd: monthStart.addMonths(1)
32 width: parent.width123 property var gridStart: monthStart.weekStart(intern.weekstartDay)
33 height: internal.weekHeight124 property int currentWeekRow: Math.floor((currentDayStart.getTime() - gridStart.getTime()) / Date.msPerWeek)
34125
35 Row {126 width: monthView.width
36 anchors.fill: parent127 height: monthView.height
37 spacing: 1128
129 Grid {
130 id: monthGrid
131
132 rows: 6
133 columns: 7
134
135 x: intern.squareUnit / 2
136 y: intern.verticalMargin
137 width: intern.squareUnit * columns
138 height: intern.squareUnit * rows
38139
39 Repeater {140 Repeater {
40 model: 7141 model: monthGrid.rows * monthGrid.columns
41 delegate: Rectangle {142 delegate: Item {
42 id: dayItem143 id: dayItem
43144 property var dayStart: gridStart.addDays(index)
44 property var dayOrigin: weekOrigin.addDays(index)145 property bool isCurrentMonth: monthStart <= dayStart && dayStart < monthEnd
45 property bool isToday: internal.today.getTime() == dayOrigin.getTime()146 property bool isToday: dayStart.getTime() == intern.today.getTime()
46147 property bool isCurrent: dayStart.getTime() == intern.currentDayStart.getTime()
47 width: weekItem.width / 7148 property int weekday: (index % 7 + intern.weekstartDay) % 7
48 height: weekItem.height - 1149 property bool isSunday: weekday == 0
49 color: isToday ? "#c94212" : dayOrigin.getMonth() % 2 ? "#c4c4c4" : "#e0e0e0"150 property int row: Math.floor(index / 7)
50151 property bool isCurrentWeek: row == currentWeekRow
51 Label {152 property real topMargin: (row == 0 || (monthView.compressed && isCurrentWeek)) ? -intern.verticalMargin : 0
153 property real bottomMargin: (row == 5 || (monthView.compressed && isCurrentWeek)) ? -intern.verticalMargin : 0
154 visible: monthView.compressed ? isCurrentWeek : true
155 width: intern.squareUnit
156 height: intern.squareUnit
157 Rectangle {
158 visible: isSunday
159 anchors.fill: parent
160 anchors.topMargin: dayItem.topMargin
161 anchors.bottomMargin: dayItem.bottomMargin
162 color: Color.warmGrey
163 opacity: 0.1
164 }
165 Text {
52 anchors.centerIn: parent166 anchors.centerIn: parent
53 text: dayOrigin.getDate()167 text: dayStart.getDate()
54 color: isToday ? "white" : dayOrigin.getDay() == 0 ? "#c94212" : "#404040"168 font: themeDummy.font
55 }169 color: isToday ? Color.ubuntuOrange : themeDummy.color
170 scale: isCurrent ? 1.8 : 1.
171 opacity: isCurrentMonth ? 1. : 0.3
172 Behavior on scale {
173 NumberAnimation { duration: 50 }
174 }
175 }
176 MouseArea {
177 anchors.fill: parent
178 anchors.topMargin: dayItem.topMargin
179 anchors.bottomMargin: dayItem.bottomMargin
180 onReleased: monthView.focusOnDay(dayStart)
181 }
182 // Component.onCompleted: console.log(dayStart, intern.currentDayStart)
56 }183 }
57 }184 }
58 }185 }
59 }186
60187 // Component.onCompleted: console.log("Created delegate for month", index, monthStart, gridStart, currentWeekRow, currentWeekRowReal)
61 Timer { // make sure today is updated at midnight188 }
62 interval: 1000189
63 repeat: true190 Label {
64 running: true191 visible: false
65192 id: themeDummy
66 onTriggered: {193 fontSize: "large"
67 var newDate = (new Date()).midnight()194 // Component.onCompleted: console.log(color, Qt.lighter(color, 1.74))
68 if (internal.today < newDate) internal.today = newDate195 }
69 }
70 }
71
72 Component.onCompleted: positionViewAtIndex(internal.indexOrigin, ListView.Center)
73}196}
74197
=== modified file 'calendar.qml'
--- calendar.qml 2013-02-14 18:03:27 +0000
+++ calendar.qml 2013-03-11 18:20:26 +0000
@@ -1,72 +1,103 @@
1import QtQuick 2.01import QtQuick 2.0
2import Ubuntu.Components 0.12import Ubuntu.Components 0.1
33
4/*!
5 \brief MainView with Tabs element.
6 First Tab has a single Label and
7 second Tab has a single ToolbarAction.
8*/
9
10MainView {4MainView {
11 // objectName for functional testing purposes (autopilot-qt5)
12 objectName: "calendar"
13
14 id: mainView5 id: mainView
15 width: units.gu(45)6 width: units.gu(45)
16 height: units.gu(80)7 height: units.gu(80)
17 // FIXME: 80/45 = aspect ration of galaxy nexus8 // FIXME: 80/45 = aspect ration of Galaxy Nexus
189
19 Tabs {10 Tabs { // preliminary HACK, needs rewrite when NewTabBar is finalized!
20 id: tabs11 id: tabs
21 anchors.fill: parent12 anchors.fill: parent
2213
23 // First tab begins here14 Tab { id: pageArea; title: i18n.tr("January"); page: Item { anchors.fill: parent } }
24 Tab {15 Tab { title: i18n.tr("February") }
25 objectName: "Tab1"16 Tab { title: i18n.tr("March") }
2617 Tab { title: i18n.tr("April") }
27 title: i18n.tr("Calendar")18 Tab { title: i18n.tr("May") }
2819 Tab { title: i18n.tr("June") }
29 // Tab content begins here20 Tab { title: i18n.tr("July") }
30 page: Page {21 Tab { title: i18n.tr("August") }
31 MonthView {22 Tab { title: i18n.tr("September") }
32 width: mainView.width23 Tab { title: i18n.tr("October") }
33 height: mainView.height24 Tab { title: i18n.tr("November") }
34 // anchors.fill: parent // FIXME: delivers broken geometry on startup25 Tab { title: i18n.tr("December") }
35 }26
36 }27 onSelectedTabIndexChanged: monthView.gotoNextMonth(selectedTabIndex)
37 }28 }
3829
39 // Second tab begins here30 MonthView {
40 Tab {31 id: monthView
41 objectName: "Tab2"32 onMonthStartChanged: tabs.selectedTabIndex = monthStart.getMonth()
4233 y: pageArea.y
43 title: i18n.tr("Optional Screen")34 }
44 page: Page {35
45 anchors.margins: units.gu(2)36 EventView {
4637 id: eventView
47 tools: ToolbarActions {38 property real minY: pageArea.y + monthView.compressedHeight
48 Action {39 property real maxY: pageArea.y + monthView.height
49 objectName: "action"40 y: maxY
5041 width: mainView.width
51 iconSource: Qt.resolvedUrl("avatar.png")42 height: parent.height - monthView.compressedHeight
52 text: i18n.tr("Tap me!")43 currentDayStart: monthView.currentDayStart
5344 Component.onCompleted: {
54 onTriggered: {45 incrementCurrentDay.connect(monthView.incrementCurrentDay)
55 label.text = i18n.tr("Toolbar tapped")46 decrementCurrentDay.connect(monthView.decrementCurrentDay)
56 }47 }
57 }48 MouseArea {
58 }49 id: drawer
5950 property bool compression: true
60 Column {51 anchors.fill: parent
61 anchors.centerIn: parent52 drag {
62 Label {53 axis: Drag.YAxis
63 id: label54 target: eventView
64 objectName: "label"55 minimumY: monthView.y + monthView.compressedHeight
6556 maximumY: monthView.y + monthView.height
66 text: i18n.tr("Swipe from bottom to up to reveal the toolbar.")57 onActiveChanged: {
67 }58 if (compression) {
68 }59 if (drag.active) {
69 }60 monthView.compressed = true
61 }
62 else {
63 yBehavior.enabled = true
64 eventView.y = Qt.binding(function() { return eventView.minY })
65 compression = false
66 }
67 }
68 else {
69 if (drag.active) {}
70 else{
71 eventView.y = Qt.binding(function() { return eventView.maxY })
72 monthView.compressed = false
73 compression = true
74 }
75 }
76 }
77 }
78 }
79 Behavior on y {
80 id: yBehavior
81 enabled: false
82 NumberAnimation { duration: 100 }
83 }
84 }
85
86 tools: ToolbarActions {
87 Action {
88 iconSource: Qt.resolvedUrl("avatar.png")
89 text: i18n.tr("To-do")
90 onTriggered:; // FIXME
91 }
92 Action {
93 iconSource: Qt.resolvedUrl("avatar.png")
94 text: i18n.tr("New Event")
95 onTriggered:; // FIXME
96 }
97 Action {
98 iconSource: Qt.resolvedUrl("avatar.png")
99 text: i18n.tr("Timeline")
100 onTriggered:; // FIXME
70 }101 }
71 }102 }
72}103}
73104
=== added file 'colorUtils.js'
--- colorUtils.js 1970-01-01 00:00:00 +0000
+++ colorUtils.js 2013-03-11 18:20:26 +0000
@@ -0,0 +1,10 @@
1.pragma library
2
3// colors for custom widgets
4// see: http://design.ubuntu.com/brand/colour-palette
5
6var ubuntuOrange = "#DD4814"
7var canonicalAubergine = "#772953"
8var lightAubergine = "#77216F"
9var warmGrey = "#AEA79F"
10var coolGrey = "#333333"
011
=== added file 'testrun.sh'
--- testrun.sh 1970-01-01 00:00:00 +0000
+++ testrun.sh 2013-03-11 18:20:26 +0000
@@ -0,0 +1,4 @@
1#! /bin/bash -ex
2
3rsync -aHv ./ phablet@nexus:/tmp/Calendar/
4ssh -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: