Merge lp:~rpadovani/ubuntu-calculator-app/favouriteImplementation20150128 into lp:ubuntu-calculator-app

Proposed by Riccardo Padovani
Status: Merged
Approved by: Bartosz Kosiorek
Approved revision: 80
Merged at revision: 85
Proposed branch: lp:~rpadovani/ubuntu-calculator-app/favouriteImplementation20150128
Merge into: lp:ubuntu-calculator-app
Diff against target: 1341 lines (+940/-297)
5 files modified
app/engine/CalculationHistory.qml (+36/-9)
app/ubuntu-calculator-app.qml (+405/-287)
app/ui/FavouritePage.qml (+91/-0)
app/ui/PortraitKeyboard.qml (+1/-1)
app/upstreamcomponents/PageWithBottomEdge.qml (+407/-0)
To merge this branch: bzr merge lp:~rpadovani/ubuntu-calculator-app/favouriteImplementation20150128
Reviewer Review Type Date Requested Status
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Bartosz Kosiorek Approve
Review via email: mp+247900@code.launchpad.net

Commit message

Implemented favourite feature as per design.

Description of the change

Implemented favourite feature as per design.

I appreciate the diff is huge, so I suggest to look to the diff of commits 72, 75, and 77

73 is a merge, so I don't think I did damages there
74 is the implementation of pagestack, most of differences are due different tabulation
77 is the creation of PageWithBottomEdge. I imported it from upstream, so there shouldn't be errors here

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
Bartosz Kosiorek (gang65) wrote :

Thanks Riccardo.

I'm impressed with the work you made.
You have added scrollable TextField! It is huge improvement.

Some first look remarks:
1. During adding I missed button to confirm favourite description.
2. The star could change colour after pressing. Maybe yellow or even blue, it is up to you.
3. We really need some autopilot tests for that. Please create ticket for that after merge.

Did you change PageWithBottomEdge.qml component or it is default?

In my opinion we should integrate this branch to get feedback from users and UX designers.

review: Needs Fixing
Revision history for this message
Riccardo Padovani (rpadovani) wrote :

> Some first look remarks:
> 1. During adding I missed button to confirm favourite description.

Added

> 2. The star could change colour after pressing. Maybe yellow or even blue, it
> is up to you.

I tried with Ubuntu Orange - what do you think? :-)

> 3. We really need some autopilot tests for that. Please create ticket for that
> after merge.

Yes, I'm going to open bugs

> Did you change PageWithBottomEdge.qml component or it is default?

It is the default one

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
Bartosz Kosiorek (gang65) wrote :

Looks ok to me.

Thanks Riccardo

review: Approve
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
79. By Riccardo Padovani

Merged from trunk

80. By Riccardo Padovani

Fixing buttonRatio

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'app/engine/CalculationHistory.qml'
--- app/engine/CalculationHistory.qml 2015-01-15 13:45:19 +0000
+++ app/engine/CalculationHistory.qml 2015-01-30 10:41:41 +0000
@@ -25,6 +25,14 @@
2525
26 Component.onCompleted: {26 Component.onCompleted: {
27 getCalculations(function(calc) {27 getCalculations(function(calc) {
28 // Also if isFavourite is set as BOOL, LocalStorage saves it as
29 // int, so we need to convert it before adding the calc to
30 // the history
31 if (calc.isFavourite === 1) {
32 calc.isFavourite = true;
33 } else {
34 calc.isFavourite = false;
35 }
28 history.append(calc);36 history.append(calc);
29 });37 });
30 }38 }
@@ -34,7 +42,7 @@
34 formula: ''42 formula: ''
35 result: ''43 result: ''
36 date: 044 date: 0
37 isFavourite: 045 isFavourite: false
38 favouriteText: ''46 favouriteText: ''
39 }47 }
40 }48 }
@@ -104,7 +112,7 @@
104 );112 );
105 }113 }
106114
107 function addCalculationToScreen(formula, result) {115 function addCalculationToScreen(formula, result, isFavourite, favouriteText) {
108 // The function add the last formula to the model, and leave to116 // The function add the last formula to the model, and leave to
109 // addCalculationToDatabase the job to add it to the database117 // addCalculationToDatabase the job to add it to the database
110 // that is called only after the element has been added to the118 // that is called only after the element has been added to the
@@ -113,27 +121,25 @@
113 history.append({"formula": formula,121 history.append({"formula": formula,
114 "result": result,122 "result": result,
115 "date": date,123 "date": date,
116 "isFavourite": 0,124 "isFavourite": isFavourite,
117 "favouriteText": ''});125 "favouriteText": favouriteText});
118
119 var index = history.count - 1;126 var index = history.count - 1;
120 // TODO: move this function to a plave that retards the execution to127 // TODO: move this function to a plave that retards the execution to
121 // improve performances128 // improve performances
122 timer.execute.push(function() {129 timer.execute.push(function() {
123 calculationHistory.addCalculationToDatabase(formula, result, date, index);130 calculationHistory.addCalculationToDatabase(formula, result, date, index, isFavourite, favouriteText);
124 });131 });
125 timer.start();132 timer.start();
126 }133 }
127134
128 function addCalculationToDatabase(formula, result, date, index) {135 function addCalculationToDatabase(formula, result, date, index, isFavourite, favouriteText) {
129 openDatabase();136 openDatabase();
130
131 calculationHistoryDatabase.transaction(137 calculationHistoryDatabase.transaction(
132 function (tx) {138 function (tx) {
133 var results = tx.executeSql('INSERT INTO Calculations (139 var results = tx.executeSql('INSERT INTO Calculations (
134 formula, result, date, isFavourite, favouriteText) VALUES(140 formula, result, date, isFavourite, favouriteText) VALUES(
135 ?, ?, ?, ?, ?)',141 ?, ?, ?, ?, ?)',
136 [formula, result, date, false, '']142 [formula, result, date, isFavourite, favouriteText]
137 );143 );
138 // we need to update the listmodel unless we would have dbId = 0 on the144 // we need to update the listmodel unless we would have dbId = 0 on the
139 // last inserted item145 // last inserted item
@@ -160,4 +166,25 @@
160 });166 });
161 timer.start();167 timer.start();
162 }168 }
169
170 function removeFavourites(removedFavourites) {
171 openDatabase();
172 var sql = "UPDATE Calculations SET isFavourite = 'false' WHERE dbId IN (";
173 var removed = removedFavourites[0];
174 history.setProperty(removedFavourites[0], "isFavourite", false);
175 removedFavourites.splice(0, 1);
176
177 for (var index in removedFavourites) {
178 history.setProperty(removedFavourites[index], "isFavourite", false);
179 removed += "," + removedFavourites[index];
180 }
181
182 sql += removed + ")";
183
184 calculationHistoryDatabase.transaction(
185 function (tx) {
186 var result = tx.executeSql(sql);
187 }
188 );
189 }
163}190}
164191
=== modified file 'app/ubuntu-calculator-app.qml'
--- app/ubuntu-calculator-app.qml 2015-01-30 09:42:00 +0000
+++ app/ubuntu-calculator-app.qml 2015-01-30 10:41:41 +0000
@@ -62,33 +62,12 @@
6262
63 property var decimalPoint: Qt.locale().decimalPoint63 property var decimalPoint: Qt.locale().decimalPoint
6464
65 // Var used to save favourite calcs
66 property bool isFavourite: false
67
65 // By default we delete selected calculation from history68 // By default we delete selected calculation from history
66 property bool deleteSelectedCalculation: true;69 property bool deleteSelectedCalculation: true;
6770
68 state: visualModel.isInSelectionMode ? "selection" : "default"
69 states: [
70 State {
71 name: "default"
72 StateChangeScript {
73 script: header.hide()
74 }
75 PropertyChanges {
76 target: scrollableView
77 clip: false
78 }
79 },
80 State {
81 name: "selection"
82 StateChangeScript {
83 script: header.show()
84 }
85 PropertyChanges {
86 target: scrollableView
87 clip: true
88 }
89 }
90 ]
91
92 /**71 /**
93 * The function calls the Formula.deleteLastFormulaElement function and72 * The function calls the Formula.deleteLastFormulaElement function and
94 * place the result in right vars73 * place the result in right vars
@@ -208,281 +187,420 @@
208 }187 }
209188
210189
211 calculationHistory.addCalculationToScreen(longFormula, result);190 if (!isFavourite) {
191 favouriteTextField.text = "";
192 }
193
194 calculationHistory.addCalculationToScreen(longFormula, result, isFavourite, favouriteTextField.text);
212 longFormula = result;195 longFormula = result;
213 shortFormula = result;196 shortFormula = result;
197 favouriteTextField.text = "";
198 isFavourite = false;
214 displayedInputText = result;199 displayedInputText = result;
215 }200 }
216201
217 CalculationHistory {202 PageStack {
218 id: calculationHistory203 id: mainStack
219 }204
220205 Component.onCompleted: push(calculatorPage)
221 Keys.onPressed: {206
222 keyboardLoader.item.pressedKey = event.key;207 PageWithBottomEdge {
223 keyboardLoader.item.pressedKeyText = event.text;208 id: calculatorPage
224 }209
225210 bottomEdgeTitle: i18n.tr("Favorite")
226 Keys.onReleased: {211
227 keyboardLoader.item.pressedKey = -1;212 bottomEdgePageComponent: FavouritePage {
228 keyboardLoader.item.pressedKeyText = "";213 anchors.fill: parent
229 }214
230215 title: i18n.tr("Favorite")
231 Header {
232 id: header
233 visible: true
234 useDeprecatedToolbar: false
235 property color dividerColor: "#babbbc"
236 property color panelColor: "white"
237 config: PageHeadConfiguration {
238 backAction: Action {
239 objectName: "cancelSelectionAction"
240 iconName: "close"
241 text: i18n.tr("Cancel")
242 onTriggered: visualModel.cancelSelection()
243 }216 }
244 actions: [217
245 Action {218 state: visualModel.isInSelectionMode ? "selection" : "default"
246 id: selectAllAction219 states: [
247 objectName: "selectAllAction"220 State {
248 iconName: "select"221 name: "default"
249 text: i18n.tr("Select All")222 StateChangeScript {
250 onTriggered: visualModel.selectAll()223 script: header.hide()
251 },224 }
252 Action {225 PropertyChanges {
253 id: copySelectedAction226 target: scrollableView
254 objectName: "copySelectedAction"227 clip: false
255 iconName: "edit-copy"228 }
256 text: i18n.tr("Copy")229 },
257 onTriggered: copySelectedCalculations()230 State {
258 },231 name: "selection"
259 Action {232 StateChangeScript {
260 id: multiDeleteAction233 script: header.show()
261 objectName: "multiDeleteAction"234 }
262 iconName: "delete"235 PropertyChanges {
263 text: i18n.tr("Delete")236 target: scrollableView
264 onTriggered: deleteSelectedCalculations()237 clip: true
238 }
265 }239 }
266 ]240 ]
267 }241
268 }242 CalculationHistory {
269243 id: calculationHistory
270 Component {244 }
271 id: emptyDelegate245
272 Item { }246 Keys.onPressed: {
273 }247 keyboardLoader.item.pressedKey = event.key;
274248 keyboardLoader.item.pressedKeyText = event.text;
275 Component {249 }
276 id: screenDelegateComponent250
277 Screen {251 Keys.onReleased: {
278 id: screenDelegate252 keyboardLoader.item.pressedKey = -1;
279 width: parent ? parent.width : 0253 keyboardLoader.item.pressedKeyText = "";
280254 }
281 property var model: itemModel255
282 visible: model.dbId !== -1256 Header {
283257 id: header
284 selectionMode: visualModel.isInSelectionMode258 visible: true
285 selected: visualModel.isSelected(visualDelegate)259 useDeprecatedToolbar: false
286260 property color dividerColor: "#babbbc"
287 property var removalAnimation261 property color panelColor: "white"
288 function remove() {262 config: PageHeadConfiguration {
289 removalAnimation.start();263 backAction: Action {
290 }264 objectName: "cancelSelectionAction"
291265 iconName: "close"
292 // parent is the loader component266 text: i18n.tr("Cancel")
293 property var visualDelegate: parent ? parent : null267 onTriggered: visualModel.cancelSelection()
294268 }
295 onSwippingChanged: {269 actions: [
296 visualModel.updateSwipeState(screenDelegate);270 Action {
297 }271 id: selectAllAction
298272 objectName: "selectAllAction"
299 onSwipeStateChanged: {273 iconName: "select"
300 visualModel.updateSwipeState(screenDelegate);274 text: i18n.tr("Select All")
301 }275 onTriggered: visualModel.selectAll()
302276 },
303 onItemClicked: {277 Action {
304 if (visualModel.isInSelectionMode) {278 id: copySelectedAction
305 if (!visualModel.selectItem(visualDelegate)) {279 objectName: "copySelectedAction"
306 visualModel.deselectItem(visualDelegate);280 iconName: "edit-copy"
307 }281 text: i18n.tr("Copy")
308 }282 onTriggered: copySelectedCalculations()
309 }283 },
310284 Action {
311 onItemPressAndHold: {285 id: multiDeleteAction
312 visualModel.startSelection();286 objectName: "multiDeleteAction"
313 visualModel.selectItem(visualDelegate);287 iconName: "delete"
314 }288 text: i18n.tr("Delete")
315289 onTriggered: deleteSelectedCalculations()
316 rightSideActions: [ screenDelegateCopyAction.item ]290 }
317 leftSideAction: screenDelegateDeleteAction.item291 ]
318292 }
319 Loader {293 }
320 id: screenDelegateCopyAction294
321 sourceComponent: Action {295 Component {
322 iconName: "edit-copy"296 id: emptyDelegate
323 text: i18n.tr("Copy")297 Item { }
324 onTriggered: {298 }
299
300 Component {
301 id: screenDelegateComponent
302 Screen {
303 id: screenDelegate
304 width: parent ? parent.width : 0
305
306 property var model: itemModel
307 visible: model.dbId !== -1
308
309 selectionMode: visualModel.isInSelectionMode
310 selected: visualModel.isSelected(visualDelegate)
311
312 property var removalAnimation
313 function remove() {
314 removalAnimation.start();
315 }
316
317 // parent is the loader component
318 property var visualDelegate: parent ? parent : null
319
320 onSwippingChanged: {
321 visualModel.updateSwipeState(screenDelegate);
322 }
323
324 onSwipeStateChanged: {
325 visualModel.updateSwipeState(screenDelegate);
326 }
327
328 onItemClicked: {
329 if (visualModel.isInSelectionMode) {
330 if (!visualModel.selectItem(visualDelegate)) {
331 visualModel.deselectItem(visualDelegate);
332 }
333 }
334 }
335
336 onItemPressAndHold: {
337 visualModel.startSelection();
338 visualModel.selectItem(visualDelegate);
339 }
340
341 rightSideActions: [ screenDelegateCopyAction.item ]
342 leftSideAction: screenDelegateDeleteAction.item
343
344 Loader {
345 id: screenDelegateCopyAction
346 sourceComponent: Action {
347 iconName: "edit-copy"
348 text: i18n.tr("Copy")
349 onTriggered: {
350 var mimeData = Clipboard.newData();
351 mimeData.text = model.formula + "=" + model.result;
352 Clipboard.push(mimeData);
353 }
354 }
355 }
356
357 Loader {
358 id: screenDelegateDeleteAction
359 sourceComponent: Action {
360 iconName: "delete"
361 text: i18n.tr("Delete")
362 onTriggered: {
363 screenDelegate.remove();
364 }
365 }
366 }
367
368 removalAnimation: SequentialAnimation {
369 alwaysRunToEnd: true
370
371 ScriptAction {
372 script: {
373 if (visualModel.currentSwipedItem === screenDelegate) {
374 visualModel.currentSwipedItem = null;
375 }
376 }
377 }
378
379 UbuntuNumberAnimation {
380 target: screenDelegate
381 property: "height"
382 to: 0
383 }
384
385 ScriptAction {
386 script: {
387 calculationHistory.deleteCalc(model.dbId, model.index);
388 }
389 }
390 }
391 }
392 }
393
394 function deleteSelectedCalculations() {
395 deleteSelectedCalculation = true;
396 visualModel.endSelection();
397 }
398
399 function copySelectedCalculations() {
400 deleteSelectedCalculation = false;
401 visualModel.endSelection();
402 }
403
404 MultipleSelectionVisualModel {
405 id: visualModel
406 model: calculationHistory.getContents()
407
408 onSelectionDone: {
409 if(deleteSelectedCalculation === true) {
410 for(var i = 0; i < items.count; i++) {
411 calculationHistory.deleteCalc(items.get(i).model.dbId, items.get(i).model.index);
412 }
413 } else {
325 var mimeData = Clipboard.newData();414 var mimeData = Clipboard.newData();
326 mimeData.text = model.formula + "=" + model.result;415 mimeData.text = "";
416 for(var j = 0; j < items.count; j++) {
417 if (items.get(j).model.dbId !== -1) {
418 mimeData.text = mimeData.text + items.get(j).model.formula + "=" + items.get(j).model.result + "\n";
419 }
420 }
327 Clipboard.push(mimeData);421 Clipboard.push(mimeData);
328 }422 }
329 }423 }
330 }424
331425 delegate: Component {
332 Loader {426 Loader {
333 id: screenDelegateDeleteAction427 property var itemModel: model
334 sourceComponent: Action {428 width: parent.width
335 iconName: "delete"429 height: model.dbId !== -1 ? item.height : 0;
336 text: i18n.tr("Delete")430 sourceComponent: screenDelegateComponent
337 onTriggered: {431 opacity: ((y + height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0
338 screenDelegate.remove();432 onOpacityChanged: {
339 }433 if (this.hasOwnProperty('item') && this.item !== null) {
340 }434 if (opacity > 0) {
341 }435 sourceComponent = screenDelegateComponent;
342436 } else {
343 removalAnimation: SequentialAnimation {437 this.item.visible = false;
344 alwaysRunToEnd: true438 sourceComponent = emptyDelegate;
345439 }
346 ScriptAction {440 }
347 script: {441 }
348 if (visualModel.currentSwipedItem === screenDelegate) {442 }
349 visualModel.currentSwipedItem = null;443 }
350 }444 }
351 }445
352 }446 ScrollableView {
353447 anchors {
354 UbuntuNumberAnimation {448 top: header.bottom
355 target: screenDelegate449 bottom: parent.bottom
356 property: "height"450 left: parent.left
357 to: 0451 right: parent.right
358 }452 }
359453 id: scrollableView
360 ScriptAction {454 objectName: "scrollableView"
361 script: {455
362 calculationHistory.deleteCalc(model.dbId, model.index);456 Component.onCompleted: {
363 }457 // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
364 }458 // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
365 }459 var scaleFactor = units.gridUnit / 8;
366 }460 maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
367 }461 flickDeceleration = flickDeceleration * scaleFactor;
368462 }
369 function deleteSelectedCalculations() {463
370 deleteSelectedCalculation = true;464 Repeater {
371 visualModel.endSelection();465 id: formulaView
372 }466 model: visualModel
373467 }
374 function copySelectedCalculations() {468
375 deleteSelectedCalculation = false;469 Rectangle {
376 visualModel.endSelection();470 width: parent.width
377 }471 height: units.gu(6)
378472
379 MultipleSelectionVisualModel {473 Icon {
380 id: visualModel474 id: favouriteIcon
381 model: calculationHistory.getContents()475 height: parent.height - units.gu(2)
382476 width: height
383 onSelectionDone: {477
384 if(deleteSelectedCalculation === true) {478 anchors {
385 for(var i = 0; i < items.count; i++) {479 left: parent.left
386 calculationHistory.deleteCalc(items.get(i).model.dbId, items.get(i).model.index);480 leftMargin: units.gu(1)
387 }481 top: parent.top
388 } else {482 topMargin: units.gu(1)
389 var mimeData = Clipboard.newData();483 }
390 mimeData.text = "";484
391 for(var j = 0; j < items.count; j++) {485 name: isFavourite ? "starred" : "non-starred"
392 if (items.get(j).model.dbId !== -1) {486 color: isFavourite ? "#dd4814" : "#808080"
393 mimeData.text = mimeData.text + items.get(j).model.formula + "=" + items.get(j).model.result + "\n";487
394 }488 MouseArea {
395 }489 anchors.fill: parent
396 Clipboard.push(mimeData);490 onClicked: {
397 }491 if (isFavourite) {
398 }492 textInputField.visible = true;
399493 textInputField.forceActiveFocus();
400 delegate: Component {494 } else {
401 Loader {495 textInputField.visible = false;
402 property var itemModel: model496 favouriteTextField.forceActiveFocus();
403 width: parent.width497 }
404 height: model.dbId !== -1 ? item.height : 0;498 isFavourite = !isFavourite;
405 sourceComponent: screenDelegateComponent499 }
406 opacity: ((y + height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0500 }
407 onOpacityChanged: {501 }
408 if (this.hasOwnProperty('item') && this.item !== null) {502
409 if (opacity > 0) {503 TextField {
410 sourceComponent = screenDelegateComponent;504 id: favouriteTextField
411 } else {505
412 this.item.visible = false;506 anchors {
413 sourceComponent = emptyDelegate;507 right: confirmFavourite.left
414 }508 rightMargin: units.gu(1)
415 }509 }
416 }510 width: parent.width - favouriteIcon.width - confirmFavourite.width - units.gu(3)
417 }511 height: parent.height
418 }512 visible: !textInputField.visible
419 }513
420514 font.italic: true
421 ScrollableView {515 font.pixelSize: height * 0.5
422 anchors {516 verticalAlignment: TextInput.AlignVCenter
423 top: header.bottom517
424 bottom: parent.bottom518 // TRANSLATORS: this is a time formatting string, see
425 left: parent.left519 // http://qt-project.org/doc/qt-5/qml-qtqml-date.html#details for
426 right: parent.right520 // valid expressions
427 }521 placeholderText: Qt.formatDateTime(new Date(), i18n.tr("dd MMM yyyy"))
428 id: scrollableView522
429 objectName: "scrollableView"523 // remove ubuntu shape
430524 style: TextFieldStyle {
431 Component.onCompleted: {525 background: Item {
432 // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition526 }
433 // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration527 }
434 var scaleFactor = units.gridUnit / 8;528
435 maximumFlickVelocity = maximumFlickVelocity * scaleFactor;529 InverseMouseArea {
436 flickDeceleration = flickDeceleration * scaleFactor;530 anchors.fill: parent
437 }531
438532 onClicked: {
439 Repeater {533 textInputField.visible = true;
440 id: formulaView534 textInputField.forceActiveFocus();
441 model: visualModel535 }
442 }536 }
443537 }
444 TextField {538
445 id: textInputField539 Icon {
446 objectName: "textInputField"540 id: confirmFavourite
447 width: contentWidth + units.gu(3)541 visible: favouriteTextField.visible
448 // TODO: Make sure this bug gets fixed in SDK:542
449 // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1320885543 name: "keyboard-enter"
450 //width: parent.width544
451 height: units.gu(6)545 anchors {
452546 right: parent.right
453 // remove ubuntu shape547 rightMargin: units.gu(1)
454 style: TextFieldStyle {548 top: parent.top
455 background: Item {549 topMargin: units.gu(1)
456 }550 }
457 }551
458552 height: parent.height - units.gu(2)
459 text: Formula.returnFormulaToDisplay(displayedInputText)553 width: height
460 font.pixelSize: height * 0.7554 }
461 //horizontalAlignment: TextInput.AlignRight555
462 anchors {556 TextField {
463 right: parent.right557 id: textInputField
464 rightMargin: units.gu(1)558 objectName: "textInputField"
465 }559 // TODO: Make sure this bug gets fixed in SDK:
466560 // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1320885
467 readOnly: true561 // It has been fixed in vivid - wait until it becomes the stable
468 selectByMouse: true562 // version before removing this
469 cursorVisible: true563 width: parent.width - favouriteIcon.width - units.gu(2)
470 onCursorPositionChanged:564 //width: Math.min(contentWidth + units.gu(3), parent.width - favouriteIcon.width - units.gu(2))
471 if (cursorPosition !== length ) {565 height: parent.height
472 // Count cursor position from the end of line566
473 var preservedCursorPosition = length - cursorPosition;567 // remove ubuntu shape
474 displayedInputText = longFormula;568 style: TextFieldStyle {
475 cursorPosition = length - preservedCursorPosition;569 background: Item {
476 } else {570 }
477 displayedInputText = shortFormula;571 }
478 }572
479 }573 text: Formula.returnFormulaToDisplay(displayedInputText)
480574 font.pixelSize: height * 0.7
481 Loader {575 horizontalAlignment: TextInput.AlignRight
482 id: keyboardLoader576 anchors {
483 width: parent.width577 right: parent.right
484 source: scrollableView.width > scrollableView.height ? "ui/LandscapeKeyboard.qml" : "ui/PortraitKeyboard.qml"578 rightMargin: units.gu(1)
485 opacity: ((y+height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0579 }
580
581 readOnly: true
582 selectByMouse: true
583 cursorVisible: true
584 onCursorPositionChanged:
585 if (cursorPosition !== length ) {
586 // Count cursor position from the end of line
587 var preservedCursorPosition = length - cursorPosition;
588 displayedInputText = longFormula;
589 cursorPosition = length - preservedCursorPosition;
590 } else {
591 displayedInputText = shortFormula;
592 }
593 }
594 }
595
596 Loader {
597 id: keyboardLoader
598 width: parent.width
599 visible: textInputField.visible
600 source: scrollableView.width > scrollableView.height ? "ui/LandscapeKeyboard.qml" : "ui/PortraitKeyboard.qml"
601 opacity: ((y+height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0
602 }
603 }
486 }604 }
487 }605 }
488}606}
489607
=== added file 'app/ui/FavouritePage.qml'
--- app/ui/FavouritePage.qml 1970-01-01 00:00:00 +0000
+++ app/ui/FavouritePage.qml 2015-01-30 10:41:41 +0000
@@ -0,0 +1,91 @@
1/*
2 * Copyright (C) 2015 Canonical Ltd
3 *
4 * This file is part of Ubuntu Calculator App
5 *
6 * Ubuntu Calculator App is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * Ubuntu Calculator App is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18import QtQuick 2.3
19import QtQuick.Layouts 1.1
20import Ubuntu.Components 1.1
21import Ubuntu.Components.ListItems 1.0 as ListItem
22
23import "../engine"
24
25Page {
26 anchors.fill: parent
27
28 property var removedFavourites: []
29
30 head.backAction: Action {
31 iconName: "back"
32 onTriggered: {
33 if (removedFavourites.length > 0) {
34 calculationHistory.removeFavourites(removedFavourites);
35 }
36 mainStack.pop();
37 }
38 }
39
40 ListView {
41 id: favouriteListview
42 anchors.fill: parent
43 model: calculationHistory.getContents();
44
45 delegate: ListItem.Empty {
46 visible: model.isFavourite
47 height: visible ? units.gu(6) : 0
48
49 MouseArea {
50 anchors.fill: parent
51
52 onClicked: {
53 if (favouriteIcon.name == "starred") {
54 favouriteIcon.name = "non-starred";
55 removedFavourites.push(model.dbId);
56 }
57 else {
58 favouriteIcon.name = "starred";
59 removedFavourites.splice(removedFavourites.indexOf(model.dbId), 1);
60 }
61 }
62 }
63
64 RowLayout {
65 height: units.gu(5)
66 spacing: units.gu(2)
67 width: parent.width - units.gu(4)
68 anchors.horizontalCenter: parent.horizontalCenter
69 Icon {
70 id: favouriteIcon
71 height: parent.height - units.gu(2)
72 width: height
73
74 Layout.alignment: Qt.AlignVCenter
75
76 name: "starred"
77 }
78
79 Text {
80 text: model.favouriteText
81 Layout.fillWidth: true
82 }
83
84 Text {
85 text: model.result
86 font.bold: true
87 }
88 }
89 }
90 }
91}
092
=== modified file 'app/ui/PortraitKeyboard.qml'
--- app/ui/PortraitKeyboard.qml 2015-01-29 22:17:33 +0000
+++ app/ui/PortraitKeyboard.qml 2015-01-30 10:41:41 +0000
@@ -31,7 +31,7 @@
31 }31 }
3232
33 KeyboardPage {33 KeyboardPage {
34 buttonRatio: 0.734 buttonRatio: 0.6
35 buttonMaxHeight: scrollableView.height / 10.035 buttonMaxHeight: scrollableView.height / 10.0
3636
37 keyboardModel: new Array(37 keyboardModel: new Array(
3838
=== added file 'app/upstreamcomponents/PageWithBottomEdge.qml'
--- app/upstreamcomponents/PageWithBottomEdge.qml 1970-01-01 00:00:00 +0000
+++ app/upstreamcomponents/PageWithBottomEdge.qml 2015-01-30 10:41:41 +0000
@@ -0,0 +1,407 @@
1/*
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 Example:
19
20 MainView {
21 objectName: "mainView"
22
23 applicationName: "com.ubuntu.developer.boiko.bottomedge"
24
25 width: units.gu(100)
26 height: units.gu(75)
27
28 Component {
29 id: pageComponent
30
31 PageWithBottomEdge {
32 id: mainPage
33 title: i18n.tr("Main Page")
34
35 Rectangle {
36 anchors.fill: parent
37 color: "white"
38 }
39
40 bottomEdgePageComponent: Page {
41 title: "Contents"
42 anchors.fill: parent
43 //anchors.topMargin: contentsPage.flickable.contentY
44
45 ListView {
46 anchors.fill: parent
47 model: 50
48 delegate: ListItems.Standard {
49 text: "One Content Item: " + index
50 }
51 }
52 }
53 bottomEdgeTitle: i18n.tr("Bottom edge action")
54 }
55 }
56
57 PageStack {
58 id: stack
59 Component.onCompleted: stack.push(pageComponent)
60 }
61 }
62
63*/
64
65import QtQuick 2.2
66import Ubuntu.Components 1.1
67
68Page {
69 id: page
70
71 property alias bottomEdgePageComponent: edgeLoader.sourceComponent
72 property alias bottomEdgePageSource: edgeLoader.source
73 property alias bottomEdgeTitle: tipLabel.text
74 property bool bottomEdgeEnabled: true
75 property int bottomEdgeExpandThreshold: page.height * 0.2
76 property int bottomEdgeExposedArea: bottomEdge.state !== "expanded" ? (page.height - bottomEdge.y - bottomEdge.tipHeight) : _areaWhenExpanded
77 property bool reloadBottomEdgePage: true
78
79 readonly property alias bottomEdgePage: edgeLoader.item
80 readonly property bool isReady: ((bottomEdge.y === 0) && bottomEdgePageLoaded && edgeLoader.item.active)
81 readonly property bool isCollapsed: (bottomEdge.y === page.height)
82 readonly property bool bottomEdgePageLoaded: (edgeLoader.status == Loader.Ready)
83
84 property bool _showEdgePageWhenReady: false
85 property int _areaWhenExpanded: 0
86
87 signal bottomEdgeReleased()
88 signal bottomEdgeDismissed()
89
90
91 function showBottomEdgePage(source, properties)
92 {
93 edgeLoader.setSource(source, properties)
94 _showEdgePageWhenReady = true
95 }
96
97 function setBottomEdgePage(source, properties)
98 {
99 edgeLoader.setSource(source, properties)
100 }
101
102 function _pushPage()
103 {
104 if (edgeLoader.status === Loader.Ready) {
105 edgeLoader.item.active = true
106 page.pageStack.push(edgeLoader.item)
107 if (edgeLoader.item.flickable) {
108 edgeLoader.item.flickable.contentY = -page.header.height
109 edgeLoader.item.flickable.returnToBounds()
110 }
111 if (edgeLoader.item.ready)
112 edgeLoader.item.ready()
113 }
114 }
115
116
117 Component.onCompleted: {
118 // avoid a binding on the expanded height value
119 var expandedHeight = height;
120 _areaWhenExpanded = expandedHeight;
121 }
122
123 onActiveChanged: {
124 if (active) {
125 bottomEdge.state = "collapsed"
126 }
127 }
128
129 onBottomEdgePageLoadedChanged: {
130 if (_showEdgePageWhenReady && bottomEdgePageLoaded) {
131 bottomEdge.state = "expanded"
132 _showEdgePageWhenReady = false
133 }
134 }
135
136 Rectangle {
137 id: bgVisual
138
139 color: "black"
140 anchors.fill: page
141 opacity: 0.7 * ((page.height - bottomEdge.y) / page.height)
142 z: 1
143 }
144
145 UbuntuShape {
146 id: tip
147 objectName: "bottomEdgeTip"
148
149 property bool hidden: (activeFocus === false) || ((bottomEdge.y - units.gu(1)) < tip.y)
150
151 enabled: mouseArea.enabled
152 visible: page.bottomEdgeEnabled
153 anchors {
154 bottom: parent.bottom
155 horizontalCenter: bottomEdge.horizontalCenter
156 bottomMargin: hidden ? - height + units.gu(1) : -units.gu(1)
157 Behavior on bottomMargin {
158 SequentialAnimation {
159 // wait some msecs in case of the focus change again, to avoid flickering
160 PauseAnimation {
161 duration: 300
162 }
163 UbuntuNumberAnimation {
164 duration: UbuntuAnimation.SnapDuration
165 }
166 }
167 }
168 }
169
170 z: 1
171 width: tipLabel.paintedWidth + units.gu(6)
172 height: bottomEdge.tipHeight + units.gu(1)
173 color: Theme.palette.normal.overlay
174 Label {
175 id: tipLabel
176
177 anchors {
178 top: parent.top
179 left: parent.left
180 right: parent.right
181 }
182 height: bottomEdge.tipHeight
183 verticalAlignment: Text.AlignVCenter
184 horizontalAlignment: Text.AlignHCenter
185 opacity: tip.hidden ? 0.0 : 1.0
186 Behavior on opacity {
187 UbuntuNumberAnimation {
188 duration: UbuntuAnimation.SnapDuration
189 }
190 }
191 }
192 }
193
194 Rectangle {
195 id: shadow
196
197 anchors {
198 left: parent.left
199 right: parent.right
200 bottom: parent.bottom
201 }
202 height: units.gu(1)
203 z: 1
204 opacity: 0.0
205 gradient: Gradient {
206 GradientStop { position: 0.0; color: "transparent" }
207 GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.2) }
208 }
209 }
210
211 MouseArea {
212 id: mouseArea
213
214 property real previousY: -1
215 property string dragDirection: "None"
216
217 preventStealing: true
218 drag {
219 axis: Drag.YAxis
220 target: bottomEdge
221 minimumY: bottomEdge.pageStartY
222 maximumY: page.height
223 }
224 enabled: edgeLoader.status == Loader.Ready
225 visible: page.bottomEdgeEnabled
226
227 anchors {
228 left: parent.left
229 right: parent.right
230 bottom: parent.bottom
231
232 }
233 height: bottomEdge.tipHeight
234 z: 1
235
236 onReleased: {
237 page.bottomEdgeReleased()
238 if ((dragDirection === "BottomToTop") &&
239 bottomEdge.y < (page.height - bottomEdgeExpandThreshold - bottomEdge.tipHeight)) {
240 bottomEdge.state = "expanded"
241 } else {
242 bottomEdge.state = "collapsed"
243 }
244 previousY = -1
245 dragDirection = "None"
246 }
247
248 onPressed: {
249 previousY = mouse.y
250 tip.forceActiveFocus()
251 }
252
253 onMouseYChanged: {
254 var yOffset = previousY - mouseY
255 // skip if was a small move
256 if (Math.abs(yOffset) <= units.gu(2)) {
257 return
258 }
259 previousY = mouseY
260 dragDirection = yOffset > 0 ? "BottomToTop" : "TopToBottom"
261 }
262 }
263
264 Rectangle {
265 id: bottomEdge
266 objectName: "bottomEdge"
267
268 readonly property int tipHeight: units.gu(3)
269 readonly property int pageStartY: 0
270
271 z: 1
272 color: Theme.palette.normal.background
273 clip: true
274 anchors {
275 left: parent.left
276 right: parent.right
277 }
278 height: page.height
279 y: height
280 visible: !page.isCollapsed
281 state: "collapsed"
282 states: [
283 State {
284 name: "collapsed"
285 PropertyChanges {
286 target: bottomEdge
287 y: bottomEdge.height
288 }
289 },
290 State {
291 name: "expanded"
292 PropertyChanges {
293 target: bottomEdge
294 y: bottomEdge.pageStartY
295 }
296 },
297 State {
298 name: "floating"
299 when: mouseArea.drag.active
300 PropertyChanges {
301 target: shadow
302 opacity: 1.0
303 }
304 }
305 ]
306
307 transitions: [
308 Transition {
309 to: "expanded"
310 SequentialAnimation {
311 alwaysRunToEnd: true
312
313 SmoothedAnimation {
314 target: bottomEdge
315 property: "y"
316 duration: UbuntuAnimation.FastDuration
317 easing.type: Easing.Linear
318 }
319 SmoothedAnimation {
320 target: edgeLoader
321 property: "anchors.topMargin"
322 to: - units.gu(4)
323 duration: UbuntuAnimation.FastDuration
324 easing.type: Easing.Linear
325 }
326 SmoothedAnimation {
327 target: edgeLoader
328 property: "anchors.topMargin"
329 to: 0
330 duration: UbuntuAnimation.FastDuration
331 easing: UbuntuAnimation.StandardEasing
332 }
333 ScriptAction {
334 script: page._pushPage()
335 }
336 }
337 },
338 Transition {
339 from: "expanded"
340 to: "collapsed"
341 SequentialAnimation {
342 alwaysRunToEnd: true
343
344 ScriptAction {
345 script: {
346 Qt.inputMethod.hide()
347 edgeLoader.item.parent = edgeLoader
348 edgeLoader.item.anchors.fill = edgeLoader
349 edgeLoader.item.active = false
350 }
351 }
352 SmoothedAnimation {
353 target: bottomEdge
354 property: "y"
355 duration: UbuntuAnimation.SlowDuration
356 }
357 ScriptAction {
358 script: {
359 // destroy current bottom page
360 if (page.reloadBottomEdgePage) {
361 edgeLoader.active = false
362 // tip will receive focus on page active true
363 } else {
364 tip.forceActiveFocus()
365 }
366
367 // notify
368 page.bottomEdgeDismissed()
369
370 edgeLoader.active = true
371 }
372 }
373 }
374 },
375 Transition {
376 from: "floating"
377 to: "collapsed"
378 SmoothedAnimation {
379 target: bottomEdge
380 property: "y"
381 duration: UbuntuAnimation.FastDuration
382 }
383 }
384 ]
385
386 Loader {
387 id: edgeLoader
388
389 asynchronous: true
390 anchors.fill: parent
391 //WORKAROUND: The SDK move the page contents down to allocate space for the header we need to avoid that during the page dragging
392 Binding {
393 target: edgeLoader.status === Loader.Ready ? edgeLoader : null
394 property: "anchors.topMargin"
395 value: edgeLoader.item && edgeLoader.item.flickable ? edgeLoader.item.flickable.contentY : 0
396 when: !page.isReady
397 }
398
399 onLoaded: {
400 tip.forceActiveFocus()
401 if (page.isReady && edgeLoader.item.active !== true) {
402 page._pushPage()
403 }
404 }
405 }
406 }
407}

Subscribers

People subscribed via source and target branches