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
1=== modified file 'app/engine/CalculationHistory.qml'
2--- app/engine/CalculationHistory.qml 2015-01-15 13:45:19 +0000
3+++ app/engine/CalculationHistory.qml 2015-01-30 10:41:41 +0000
4@@ -25,6 +25,14 @@
5
6 Component.onCompleted: {
7 getCalculations(function(calc) {
8+ // Also if isFavourite is set as BOOL, LocalStorage saves it as
9+ // int, so we need to convert it before adding the calc to
10+ // the history
11+ if (calc.isFavourite === 1) {
12+ calc.isFavourite = true;
13+ } else {
14+ calc.isFavourite = false;
15+ }
16 history.append(calc);
17 });
18 }
19@@ -34,7 +42,7 @@
20 formula: ''
21 result: ''
22 date: 0
23- isFavourite: 0
24+ isFavourite: false
25 favouriteText: ''
26 }
27 }
28@@ -104,7 +112,7 @@
29 );
30 }
31
32- function addCalculationToScreen(formula, result) {
33+ function addCalculationToScreen(formula, result, isFavourite, favouriteText) {
34 // The function add the last formula to the model, and leave to
35 // addCalculationToDatabase the job to add it to the database
36 // that is called only after the element has been added to the
37@@ -113,27 +121,25 @@
38 history.append({"formula": formula,
39 "result": result,
40 "date": date,
41- "isFavourite": 0,
42- "favouriteText": ''});
43-
44+ "isFavourite": isFavourite,
45+ "favouriteText": favouriteText});
46 var index = history.count - 1;
47 // TODO: move this function to a plave that retards the execution to
48 // improve performances
49 timer.execute.push(function() {
50- calculationHistory.addCalculationToDatabase(formula, result, date, index);
51+ calculationHistory.addCalculationToDatabase(formula, result, date, index, isFavourite, favouriteText);
52 });
53 timer.start();
54 }
55
56- function addCalculationToDatabase(formula, result, date, index) {
57+ function addCalculationToDatabase(formula, result, date, index, isFavourite, favouriteText) {
58 openDatabase();
59-
60 calculationHistoryDatabase.transaction(
61 function (tx) {
62 var results = tx.executeSql('INSERT INTO Calculations (
63 formula, result, date, isFavourite, favouriteText) VALUES(
64 ?, ?, ?, ?, ?)',
65- [formula, result, date, false, '']
66+ [formula, result, date, isFavourite, favouriteText]
67 );
68 // we need to update the listmodel unless we would have dbId = 0 on the
69 // last inserted item
70@@ -160,4 +166,25 @@
71 });
72 timer.start();
73 }
74+
75+ function removeFavourites(removedFavourites) {
76+ openDatabase();
77+ var sql = "UPDATE Calculations SET isFavourite = 'false' WHERE dbId IN (";
78+ var removed = removedFavourites[0];
79+ history.setProperty(removedFavourites[0], "isFavourite", false);
80+ removedFavourites.splice(0, 1);
81+
82+ for (var index in removedFavourites) {
83+ history.setProperty(removedFavourites[index], "isFavourite", false);
84+ removed += "," + removedFavourites[index];
85+ }
86+
87+ sql += removed + ")";
88+
89+ calculationHistoryDatabase.transaction(
90+ function (tx) {
91+ var result = tx.executeSql(sql);
92+ }
93+ );
94+ }
95 }
96
97=== modified file 'app/ubuntu-calculator-app.qml'
98--- app/ubuntu-calculator-app.qml 2015-01-30 09:42:00 +0000
99+++ app/ubuntu-calculator-app.qml 2015-01-30 10:41:41 +0000
100@@ -62,33 +62,12 @@
101
102 property var decimalPoint: Qt.locale().decimalPoint
103
104+ // Var used to save favourite calcs
105+ property bool isFavourite: false
106+
107 // By default we delete selected calculation from history
108 property bool deleteSelectedCalculation: true;
109
110- state: visualModel.isInSelectionMode ? "selection" : "default"
111- states: [
112- State {
113- name: "default"
114- StateChangeScript {
115- script: header.hide()
116- }
117- PropertyChanges {
118- target: scrollableView
119- clip: false
120- }
121- },
122- State {
123- name: "selection"
124- StateChangeScript {
125- script: header.show()
126- }
127- PropertyChanges {
128- target: scrollableView
129- clip: true
130- }
131- }
132- ]
133-
134 /**
135 * The function calls the Formula.deleteLastFormulaElement function and
136 * place the result in right vars
137@@ -208,281 +187,420 @@
138 }
139
140
141- calculationHistory.addCalculationToScreen(longFormula, result);
142+ if (!isFavourite) {
143+ favouriteTextField.text = "";
144+ }
145+
146+ calculationHistory.addCalculationToScreen(longFormula, result, isFavourite, favouriteTextField.text);
147 longFormula = result;
148 shortFormula = result;
149+ favouriteTextField.text = "";
150+ isFavourite = false;
151 displayedInputText = result;
152 }
153
154- CalculationHistory {
155- id: calculationHistory
156- }
157-
158- Keys.onPressed: {
159- keyboardLoader.item.pressedKey = event.key;
160- keyboardLoader.item.pressedKeyText = event.text;
161- }
162-
163- Keys.onReleased: {
164- keyboardLoader.item.pressedKey = -1;
165- keyboardLoader.item.pressedKeyText = "";
166- }
167-
168- Header {
169- id: header
170- visible: true
171- useDeprecatedToolbar: false
172- property color dividerColor: "#babbbc"
173- property color panelColor: "white"
174- config: PageHeadConfiguration {
175- backAction: Action {
176- objectName: "cancelSelectionAction"
177- iconName: "close"
178- text: i18n.tr("Cancel")
179- onTriggered: visualModel.cancelSelection()
180+ PageStack {
181+ id: mainStack
182+
183+ Component.onCompleted: push(calculatorPage)
184+
185+ PageWithBottomEdge {
186+ id: calculatorPage
187+
188+ bottomEdgeTitle: i18n.tr("Favorite")
189+
190+ bottomEdgePageComponent: FavouritePage {
191+ anchors.fill: parent
192+
193+ title: i18n.tr("Favorite")
194 }
195- actions: [
196- Action {
197- id: selectAllAction
198- objectName: "selectAllAction"
199- iconName: "select"
200- text: i18n.tr("Select All")
201- onTriggered: visualModel.selectAll()
202- },
203- Action {
204- id: copySelectedAction
205- objectName: "copySelectedAction"
206- iconName: "edit-copy"
207- text: i18n.tr("Copy")
208- onTriggered: copySelectedCalculations()
209- },
210- Action {
211- id: multiDeleteAction
212- objectName: "multiDeleteAction"
213- iconName: "delete"
214- text: i18n.tr("Delete")
215- onTriggered: deleteSelectedCalculations()
216+
217+ state: visualModel.isInSelectionMode ? "selection" : "default"
218+ states: [
219+ State {
220+ name: "default"
221+ StateChangeScript {
222+ script: header.hide()
223+ }
224+ PropertyChanges {
225+ target: scrollableView
226+ clip: false
227+ }
228+ },
229+ State {
230+ name: "selection"
231+ StateChangeScript {
232+ script: header.show()
233+ }
234+ PropertyChanges {
235+ target: scrollableView
236+ clip: true
237+ }
238 }
239 ]
240- }
241- }
242-
243- Component {
244- id: emptyDelegate
245- Item { }
246- }
247-
248- Component {
249- id: screenDelegateComponent
250- Screen {
251- id: screenDelegate
252- width: parent ? parent.width : 0
253-
254- property var model: itemModel
255- visible: model.dbId !== -1
256-
257- selectionMode: visualModel.isInSelectionMode
258- selected: visualModel.isSelected(visualDelegate)
259-
260- property var removalAnimation
261- function remove() {
262- removalAnimation.start();
263- }
264-
265- // parent is the loader component
266- property var visualDelegate: parent ? parent : null
267-
268- onSwippingChanged: {
269- visualModel.updateSwipeState(screenDelegate);
270- }
271-
272- onSwipeStateChanged: {
273- visualModel.updateSwipeState(screenDelegate);
274- }
275-
276- onItemClicked: {
277- if (visualModel.isInSelectionMode) {
278- if (!visualModel.selectItem(visualDelegate)) {
279- visualModel.deselectItem(visualDelegate);
280- }
281- }
282- }
283-
284- onItemPressAndHold: {
285- visualModel.startSelection();
286- visualModel.selectItem(visualDelegate);
287- }
288-
289- rightSideActions: [ screenDelegateCopyAction.item ]
290- leftSideAction: screenDelegateDeleteAction.item
291-
292- Loader {
293- id: screenDelegateCopyAction
294- sourceComponent: Action {
295- iconName: "edit-copy"
296- text: i18n.tr("Copy")
297- onTriggered: {
298+
299+ CalculationHistory {
300+ id: calculationHistory
301+ }
302+
303+ Keys.onPressed: {
304+ keyboardLoader.item.pressedKey = event.key;
305+ keyboardLoader.item.pressedKeyText = event.text;
306+ }
307+
308+ Keys.onReleased: {
309+ keyboardLoader.item.pressedKey = -1;
310+ keyboardLoader.item.pressedKeyText = "";
311+ }
312+
313+ Header {
314+ id: header
315+ visible: true
316+ useDeprecatedToolbar: false
317+ property color dividerColor: "#babbbc"
318+ property color panelColor: "white"
319+ config: PageHeadConfiguration {
320+ backAction: Action {
321+ objectName: "cancelSelectionAction"
322+ iconName: "close"
323+ text: i18n.tr("Cancel")
324+ onTriggered: visualModel.cancelSelection()
325+ }
326+ actions: [
327+ Action {
328+ id: selectAllAction
329+ objectName: "selectAllAction"
330+ iconName: "select"
331+ text: i18n.tr("Select All")
332+ onTriggered: visualModel.selectAll()
333+ },
334+ Action {
335+ id: copySelectedAction
336+ objectName: "copySelectedAction"
337+ iconName: "edit-copy"
338+ text: i18n.tr("Copy")
339+ onTriggered: copySelectedCalculations()
340+ },
341+ Action {
342+ id: multiDeleteAction
343+ objectName: "multiDeleteAction"
344+ iconName: "delete"
345+ text: i18n.tr("Delete")
346+ onTriggered: deleteSelectedCalculations()
347+ }
348+ ]
349+ }
350+ }
351+
352+ Component {
353+ id: emptyDelegate
354+ Item { }
355+ }
356+
357+ Component {
358+ id: screenDelegateComponent
359+ Screen {
360+ id: screenDelegate
361+ width: parent ? parent.width : 0
362+
363+ property var model: itemModel
364+ visible: model.dbId !== -1
365+
366+ selectionMode: visualModel.isInSelectionMode
367+ selected: visualModel.isSelected(visualDelegate)
368+
369+ property var removalAnimation
370+ function remove() {
371+ removalAnimation.start();
372+ }
373+
374+ // parent is the loader component
375+ property var visualDelegate: parent ? parent : null
376+
377+ onSwippingChanged: {
378+ visualModel.updateSwipeState(screenDelegate);
379+ }
380+
381+ onSwipeStateChanged: {
382+ visualModel.updateSwipeState(screenDelegate);
383+ }
384+
385+ onItemClicked: {
386+ if (visualModel.isInSelectionMode) {
387+ if (!visualModel.selectItem(visualDelegate)) {
388+ visualModel.deselectItem(visualDelegate);
389+ }
390+ }
391+ }
392+
393+ onItemPressAndHold: {
394+ visualModel.startSelection();
395+ visualModel.selectItem(visualDelegate);
396+ }
397+
398+ rightSideActions: [ screenDelegateCopyAction.item ]
399+ leftSideAction: screenDelegateDeleteAction.item
400+
401+ Loader {
402+ id: screenDelegateCopyAction
403+ sourceComponent: Action {
404+ iconName: "edit-copy"
405+ text: i18n.tr("Copy")
406+ onTriggered: {
407+ var mimeData = Clipboard.newData();
408+ mimeData.text = model.formula + "=" + model.result;
409+ Clipboard.push(mimeData);
410+ }
411+ }
412+ }
413+
414+ Loader {
415+ id: screenDelegateDeleteAction
416+ sourceComponent: Action {
417+ iconName: "delete"
418+ text: i18n.tr("Delete")
419+ onTriggered: {
420+ screenDelegate.remove();
421+ }
422+ }
423+ }
424+
425+ removalAnimation: SequentialAnimation {
426+ alwaysRunToEnd: true
427+
428+ ScriptAction {
429+ script: {
430+ if (visualModel.currentSwipedItem === screenDelegate) {
431+ visualModel.currentSwipedItem = null;
432+ }
433+ }
434+ }
435+
436+ UbuntuNumberAnimation {
437+ target: screenDelegate
438+ property: "height"
439+ to: 0
440+ }
441+
442+ ScriptAction {
443+ script: {
444+ calculationHistory.deleteCalc(model.dbId, model.index);
445+ }
446+ }
447+ }
448+ }
449+ }
450+
451+ function deleteSelectedCalculations() {
452+ deleteSelectedCalculation = true;
453+ visualModel.endSelection();
454+ }
455+
456+ function copySelectedCalculations() {
457+ deleteSelectedCalculation = false;
458+ visualModel.endSelection();
459+ }
460+
461+ MultipleSelectionVisualModel {
462+ id: visualModel
463+ model: calculationHistory.getContents()
464+
465+ onSelectionDone: {
466+ if(deleteSelectedCalculation === true) {
467+ for(var i = 0; i < items.count; i++) {
468+ calculationHistory.deleteCalc(items.get(i).model.dbId, items.get(i).model.index);
469+ }
470+ } else {
471 var mimeData = Clipboard.newData();
472- mimeData.text = model.formula + "=" + model.result;
473+ mimeData.text = "";
474+ for(var j = 0; j < items.count; j++) {
475+ if (items.get(j).model.dbId !== -1) {
476+ mimeData.text = mimeData.text + items.get(j).model.formula + "=" + items.get(j).model.result + "\n";
477+ }
478+ }
479 Clipboard.push(mimeData);
480 }
481 }
482- }
483-
484- Loader {
485- id: screenDelegateDeleteAction
486- sourceComponent: Action {
487- iconName: "delete"
488- text: i18n.tr("Delete")
489- onTriggered: {
490- screenDelegate.remove();
491- }
492- }
493- }
494-
495- removalAnimation: SequentialAnimation {
496- alwaysRunToEnd: true
497-
498- ScriptAction {
499- script: {
500- if (visualModel.currentSwipedItem === screenDelegate) {
501- visualModel.currentSwipedItem = null;
502- }
503- }
504- }
505-
506- UbuntuNumberAnimation {
507- target: screenDelegate
508- property: "height"
509- to: 0
510- }
511-
512- ScriptAction {
513- script: {
514- calculationHistory.deleteCalc(model.dbId, model.index);
515- }
516- }
517- }
518- }
519- }
520-
521- function deleteSelectedCalculations() {
522- deleteSelectedCalculation = true;
523- visualModel.endSelection();
524- }
525-
526- function copySelectedCalculations() {
527- deleteSelectedCalculation = false;
528- visualModel.endSelection();
529- }
530-
531- MultipleSelectionVisualModel {
532- id: visualModel
533- model: calculationHistory.getContents()
534-
535- onSelectionDone: {
536- if(deleteSelectedCalculation === true) {
537- for(var i = 0; i < items.count; i++) {
538- calculationHistory.deleteCalc(items.get(i).model.dbId, items.get(i).model.index);
539- }
540- } else {
541- var mimeData = Clipboard.newData();
542- mimeData.text = "";
543- for(var j = 0; j < items.count; j++) {
544- if (items.get(j).model.dbId !== -1) {
545- mimeData.text = mimeData.text + items.get(j).model.formula + "=" + items.get(j).model.result + "\n";
546- }
547- }
548- Clipboard.push(mimeData);
549- }
550- }
551-
552- delegate: Component {
553- Loader {
554- property var itemModel: model
555- width: parent.width
556- height: model.dbId !== -1 ? item.height : 0;
557- sourceComponent: screenDelegateComponent
558- opacity: ((y + height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0
559- onOpacityChanged: {
560- if (this.hasOwnProperty('item') && this.item !== null) {
561- if (opacity > 0) {
562- sourceComponent = screenDelegateComponent;
563- } else {
564- this.item.visible = false;
565- sourceComponent = emptyDelegate;
566- }
567- }
568- }
569- }
570- }
571- }
572-
573- ScrollableView {
574- anchors {
575- top: header.bottom
576- bottom: parent.bottom
577- left: parent.left
578- right: parent.right
579- }
580- id: scrollableView
581- objectName: "scrollableView"
582-
583- Component.onCompleted: {
584- // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
585- // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
586- var scaleFactor = units.gridUnit / 8;
587- maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
588- flickDeceleration = flickDeceleration * scaleFactor;
589- }
590-
591- Repeater {
592- id: formulaView
593- model: visualModel
594- }
595-
596- TextField {
597- id: textInputField
598- objectName: "textInputField"
599- width: contentWidth + units.gu(3)
600- // TODO: Make sure this bug gets fixed in SDK:
601- // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1320885
602- //width: parent.width
603- height: units.gu(6)
604-
605- // remove ubuntu shape
606- style: TextFieldStyle {
607- background: Item {
608- }
609- }
610-
611- text: Formula.returnFormulaToDisplay(displayedInputText)
612- font.pixelSize: height * 0.7
613- //horizontalAlignment: TextInput.AlignRight
614- anchors {
615- right: parent.right
616- rightMargin: units.gu(1)
617- }
618-
619- readOnly: true
620- selectByMouse: true
621- cursorVisible: true
622- onCursorPositionChanged:
623- if (cursorPosition !== length ) {
624- // Count cursor position from the end of line
625- var preservedCursorPosition = length - cursorPosition;
626- displayedInputText = longFormula;
627- cursorPosition = length - preservedCursorPosition;
628- } else {
629- displayedInputText = shortFormula;
630- }
631- }
632-
633- Loader {
634- id: keyboardLoader
635- width: parent.width
636- source: scrollableView.width > scrollableView.height ? "ui/LandscapeKeyboard.qml" : "ui/PortraitKeyboard.qml"
637- opacity: ((y+height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0
638+
639+ delegate: Component {
640+ Loader {
641+ property var itemModel: model
642+ width: parent.width
643+ height: model.dbId !== -1 ? item.height : 0;
644+ sourceComponent: screenDelegateComponent
645+ opacity: ((y + height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0
646+ onOpacityChanged: {
647+ if (this.hasOwnProperty('item') && this.item !== null) {
648+ if (opacity > 0) {
649+ sourceComponent = screenDelegateComponent;
650+ } else {
651+ this.item.visible = false;
652+ sourceComponent = emptyDelegate;
653+ }
654+ }
655+ }
656+ }
657+ }
658+ }
659+
660+ ScrollableView {
661+ anchors {
662+ top: header.bottom
663+ bottom: parent.bottom
664+ left: parent.left
665+ right: parent.right
666+ }
667+ id: scrollableView
668+ objectName: "scrollableView"
669+
670+ Component.onCompleted: {
671+ // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
672+ // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
673+ var scaleFactor = units.gridUnit / 8;
674+ maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
675+ flickDeceleration = flickDeceleration * scaleFactor;
676+ }
677+
678+ Repeater {
679+ id: formulaView
680+ model: visualModel
681+ }
682+
683+ Rectangle {
684+ width: parent.width
685+ height: units.gu(6)
686+
687+ Icon {
688+ id: favouriteIcon
689+ height: parent.height - units.gu(2)
690+ width: height
691+
692+ anchors {
693+ left: parent.left
694+ leftMargin: units.gu(1)
695+ top: parent.top
696+ topMargin: units.gu(1)
697+ }
698+
699+ name: isFavourite ? "starred" : "non-starred"
700+ color: isFavourite ? "#dd4814" : "#808080"
701+
702+ MouseArea {
703+ anchors.fill: parent
704+ onClicked: {
705+ if (isFavourite) {
706+ textInputField.visible = true;
707+ textInputField.forceActiveFocus();
708+ } else {
709+ textInputField.visible = false;
710+ favouriteTextField.forceActiveFocus();
711+ }
712+ isFavourite = !isFavourite;
713+ }
714+ }
715+ }
716+
717+ TextField {
718+ id: favouriteTextField
719+
720+ anchors {
721+ right: confirmFavourite.left
722+ rightMargin: units.gu(1)
723+ }
724+ width: parent.width - favouriteIcon.width - confirmFavourite.width - units.gu(3)
725+ height: parent.height
726+ visible: !textInputField.visible
727+
728+ font.italic: true
729+ font.pixelSize: height * 0.5
730+ verticalAlignment: TextInput.AlignVCenter
731+
732+ // TRANSLATORS: this is a time formatting string, see
733+ // http://qt-project.org/doc/qt-5/qml-qtqml-date.html#details for
734+ // valid expressions
735+ placeholderText: Qt.formatDateTime(new Date(), i18n.tr("dd MMM yyyy"))
736+
737+ // remove ubuntu shape
738+ style: TextFieldStyle {
739+ background: Item {
740+ }
741+ }
742+
743+ InverseMouseArea {
744+ anchors.fill: parent
745+
746+ onClicked: {
747+ textInputField.visible = true;
748+ textInputField.forceActiveFocus();
749+ }
750+ }
751+ }
752+
753+ Icon {
754+ id: confirmFavourite
755+ visible: favouriteTextField.visible
756+
757+ name: "keyboard-enter"
758+
759+ anchors {
760+ right: parent.right
761+ rightMargin: units.gu(1)
762+ top: parent.top
763+ topMargin: units.gu(1)
764+ }
765+
766+ height: parent.height - units.gu(2)
767+ width: height
768+ }
769+
770+ TextField {
771+ id: textInputField
772+ objectName: "textInputField"
773+ // TODO: Make sure this bug gets fixed in SDK:
774+ // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1320885
775+ // It has been fixed in vivid - wait until it becomes the stable
776+ // version before removing this
777+ width: parent.width - favouriteIcon.width - units.gu(2)
778+ //width: Math.min(contentWidth + units.gu(3), parent.width - favouriteIcon.width - units.gu(2))
779+ height: parent.height
780+
781+ // remove ubuntu shape
782+ style: TextFieldStyle {
783+ background: Item {
784+ }
785+ }
786+
787+ text: Formula.returnFormulaToDisplay(displayedInputText)
788+ font.pixelSize: height * 0.7
789+ horizontalAlignment: TextInput.AlignRight
790+ anchors {
791+ right: parent.right
792+ rightMargin: units.gu(1)
793+ }
794+
795+ readOnly: true
796+ selectByMouse: true
797+ cursorVisible: true
798+ onCursorPositionChanged:
799+ if (cursorPosition !== length ) {
800+ // Count cursor position from the end of line
801+ var preservedCursorPosition = length - cursorPosition;
802+ displayedInputText = longFormula;
803+ cursorPosition = length - preservedCursorPosition;
804+ } else {
805+ displayedInputText = shortFormula;
806+ }
807+ }
808+ }
809+
810+ Loader {
811+ id: keyboardLoader
812+ width: parent.width
813+ visible: textInputField.visible
814+ source: scrollableView.width > scrollableView.height ? "ui/LandscapeKeyboard.qml" : "ui/PortraitKeyboard.qml"
815+ opacity: ((y+height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0
816+ }
817+ }
818 }
819 }
820 }
821
822=== added file 'app/ui/FavouritePage.qml'
823--- app/ui/FavouritePage.qml 1970-01-01 00:00:00 +0000
824+++ app/ui/FavouritePage.qml 2015-01-30 10:41:41 +0000
825@@ -0,0 +1,91 @@
826+/*
827+ * Copyright (C) 2015 Canonical Ltd
828+ *
829+ * This file is part of Ubuntu Calculator App
830+ *
831+ * Ubuntu Calculator App is free software: you can redistribute it and/or modify
832+ * it under the terms of the GNU General Public License version 3 as
833+ * published by the Free Software Foundation.
834+ *
835+ * Ubuntu Calculator App is distributed in the hope that it will be useful,
836+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
837+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
838+ * GNU General Public License for more details.
839+ *
840+ * You should have received a copy of the GNU General Public License
841+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
842+ */
843+import QtQuick 2.3
844+import QtQuick.Layouts 1.1
845+import Ubuntu.Components 1.1
846+import Ubuntu.Components.ListItems 1.0 as ListItem
847+
848+import "../engine"
849+
850+Page {
851+ anchors.fill: parent
852+
853+ property var removedFavourites: []
854+
855+ head.backAction: Action {
856+ iconName: "back"
857+ onTriggered: {
858+ if (removedFavourites.length > 0) {
859+ calculationHistory.removeFavourites(removedFavourites);
860+ }
861+ mainStack.pop();
862+ }
863+ }
864+
865+ ListView {
866+ id: favouriteListview
867+ anchors.fill: parent
868+ model: calculationHistory.getContents();
869+
870+ delegate: ListItem.Empty {
871+ visible: model.isFavourite
872+ height: visible ? units.gu(6) : 0
873+
874+ MouseArea {
875+ anchors.fill: parent
876+
877+ onClicked: {
878+ if (favouriteIcon.name == "starred") {
879+ favouriteIcon.name = "non-starred";
880+ removedFavourites.push(model.dbId);
881+ }
882+ else {
883+ favouriteIcon.name = "starred";
884+ removedFavourites.splice(removedFavourites.indexOf(model.dbId), 1);
885+ }
886+ }
887+ }
888+
889+ RowLayout {
890+ height: units.gu(5)
891+ spacing: units.gu(2)
892+ width: parent.width - units.gu(4)
893+ anchors.horizontalCenter: parent.horizontalCenter
894+ Icon {
895+ id: favouriteIcon
896+ height: parent.height - units.gu(2)
897+ width: height
898+
899+ Layout.alignment: Qt.AlignVCenter
900+
901+ name: "starred"
902+ }
903+
904+ Text {
905+ text: model.favouriteText
906+ Layout.fillWidth: true
907+ }
908+
909+ Text {
910+ text: model.result
911+ font.bold: true
912+ }
913+ }
914+ }
915+ }
916+}
917
918=== modified file 'app/ui/PortraitKeyboard.qml'
919--- app/ui/PortraitKeyboard.qml 2015-01-29 22:17:33 +0000
920+++ app/ui/PortraitKeyboard.qml 2015-01-30 10:41:41 +0000
921@@ -31,7 +31,7 @@
922 }
923
924 KeyboardPage {
925- buttonRatio: 0.7
926+ buttonRatio: 0.6
927 buttonMaxHeight: scrollableView.height / 10.0
928
929 keyboardModel: new Array(
930
931=== added file 'app/upstreamcomponents/PageWithBottomEdge.qml'
932--- app/upstreamcomponents/PageWithBottomEdge.qml 1970-01-01 00:00:00 +0000
933+++ app/upstreamcomponents/PageWithBottomEdge.qml 2015-01-30 10:41:41 +0000
934@@ -0,0 +1,407 @@
935+/*
936+ * Copyright (C) 2014 Canonical, Ltd.
937+ *
938+ * This program is free software; you can redistribute it and/or modify
939+ * it under the terms of the GNU General Public License as published by
940+ * the Free Software Foundation; version 3.
941+ *
942+ * This program is distributed in the hope that it will be useful,
943+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
944+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
945+ * GNU General Public License for more details.
946+ *
947+ * You should have received a copy of the GNU General Public License
948+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
949+ */
950+
951+/*
952+ Example:
953+
954+ MainView {
955+ objectName: "mainView"
956+
957+ applicationName: "com.ubuntu.developer.boiko.bottomedge"
958+
959+ width: units.gu(100)
960+ height: units.gu(75)
961+
962+ Component {
963+ id: pageComponent
964+
965+ PageWithBottomEdge {
966+ id: mainPage
967+ title: i18n.tr("Main Page")
968+
969+ Rectangle {
970+ anchors.fill: parent
971+ color: "white"
972+ }
973+
974+ bottomEdgePageComponent: Page {
975+ title: "Contents"
976+ anchors.fill: parent
977+ //anchors.topMargin: contentsPage.flickable.contentY
978+
979+ ListView {
980+ anchors.fill: parent
981+ model: 50
982+ delegate: ListItems.Standard {
983+ text: "One Content Item: " + index
984+ }
985+ }
986+ }
987+ bottomEdgeTitle: i18n.tr("Bottom edge action")
988+ }
989+ }
990+
991+ PageStack {
992+ id: stack
993+ Component.onCompleted: stack.push(pageComponent)
994+ }
995+ }
996+
997+*/
998+
999+import QtQuick 2.2
1000+import Ubuntu.Components 1.1
1001+
1002+Page {
1003+ id: page
1004+
1005+ property alias bottomEdgePageComponent: edgeLoader.sourceComponent
1006+ property alias bottomEdgePageSource: edgeLoader.source
1007+ property alias bottomEdgeTitle: tipLabel.text
1008+ property bool bottomEdgeEnabled: true
1009+ property int bottomEdgeExpandThreshold: page.height * 0.2
1010+ property int bottomEdgeExposedArea: bottomEdge.state !== "expanded" ? (page.height - bottomEdge.y - bottomEdge.tipHeight) : _areaWhenExpanded
1011+ property bool reloadBottomEdgePage: true
1012+
1013+ readonly property alias bottomEdgePage: edgeLoader.item
1014+ readonly property bool isReady: ((bottomEdge.y === 0) && bottomEdgePageLoaded && edgeLoader.item.active)
1015+ readonly property bool isCollapsed: (bottomEdge.y === page.height)
1016+ readonly property bool bottomEdgePageLoaded: (edgeLoader.status == Loader.Ready)
1017+
1018+ property bool _showEdgePageWhenReady: false
1019+ property int _areaWhenExpanded: 0
1020+
1021+ signal bottomEdgeReleased()
1022+ signal bottomEdgeDismissed()
1023+
1024+
1025+ function showBottomEdgePage(source, properties)
1026+ {
1027+ edgeLoader.setSource(source, properties)
1028+ _showEdgePageWhenReady = true
1029+ }
1030+
1031+ function setBottomEdgePage(source, properties)
1032+ {
1033+ edgeLoader.setSource(source, properties)
1034+ }
1035+
1036+ function _pushPage()
1037+ {
1038+ if (edgeLoader.status === Loader.Ready) {
1039+ edgeLoader.item.active = true
1040+ page.pageStack.push(edgeLoader.item)
1041+ if (edgeLoader.item.flickable) {
1042+ edgeLoader.item.flickable.contentY = -page.header.height
1043+ edgeLoader.item.flickable.returnToBounds()
1044+ }
1045+ if (edgeLoader.item.ready)
1046+ edgeLoader.item.ready()
1047+ }
1048+ }
1049+
1050+
1051+ Component.onCompleted: {
1052+ // avoid a binding on the expanded height value
1053+ var expandedHeight = height;
1054+ _areaWhenExpanded = expandedHeight;
1055+ }
1056+
1057+ onActiveChanged: {
1058+ if (active) {
1059+ bottomEdge.state = "collapsed"
1060+ }
1061+ }
1062+
1063+ onBottomEdgePageLoadedChanged: {
1064+ if (_showEdgePageWhenReady && bottomEdgePageLoaded) {
1065+ bottomEdge.state = "expanded"
1066+ _showEdgePageWhenReady = false
1067+ }
1068+ }
1069+
1070+ Rectangle {
1071+ id: bgVisual
1072+
1073+ color: "black"
1074+ anchors.fill: page
1075+ opacity: 0.7 * ((page.height - bottomEdge.y) / page.height)
1076+ z: 1
1077+ }
1078+
1079+ UbuntuShape {
1080+ id: tip
1081+ objectName: "bottomEdgeTip"
1082+
1083+ property bool hidden: (activeFocus === false) || ((bottomEdge.y - units.gu(1)) < tip.y)
1084+
1085+ enabled: mouseArea.enabled
1086+ visible: page.bottomEdgeEnabled
1087+ anchors {
1088+ bottom: parent.bottom
1089+ horizontalCenter: bottomEdge.horizontalCenter
1090+ bottomMargin: hidden ? - height + units.gu(1) : -units.gu(1)
1091+ Behavior on bottomMargin {
1092+ SequentialAnimation {
1093+ // wait some msecs in case of the focus change again, to avoid flickering
1094+ PauseAnimation {
1095+ duration: 300
1096+ }
1097+ UbuntuNumberAnimation {
1098+ duration: UbuntuAnimation.SnapDuration
1099+ }
1100+ }
1101+ }
1102+ }
1103+
1104+ z: 1
1105+ width: tipLabel.paintedWidth + units.gu(6)
1106+ height: bottomEdge.tipHeight + units.gu(1)
1107+ color: Theme.palette.normal.overlay
1108+ Label {
1109+ id: tipLabel
1110+
1111+ anchors {
1112+ top: parent.top
1113+ left: parent.left
1114+ right: parent.right
1115+ }
1116+ height: bottomEdge.tipHeight
1117+ verticalAlignment: Text.AlignVCenter
1118+ horizontalAlignment: Text.AlignHCenter
1119+ opacity: tip.hidden ? 0.0 : 1.0
1120+ Behavior on opacity {
1121+ UbuntuNumberAnimation {
1122+ duration: UbuntuAnimation.SnapDuration
1123+ }
1124+ }
1125+ }
1126+ }
1127+
1128+ Rectangle {
1129+ id: shadow
1130+
1131+ anchors {
1132+ left: parent.left
1133+ right: parent.right
1134+ bottom: parent.bottom
1135+ }
1136+ height: units.gu(1)
1137+ z: 1
1138+ opacity: 0.0
1139+ gradient: Gradient {
1140+ GradientStop { position: 0.0; color: "transparent" }
1141+ GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.2) }
1142+ }
1143+ }
1144+
1145+ MouseArea {
1146+ id: mouseArea
1147+
1148+ property real previousY: -1
1149+ property string dragDirection: "None"
1150+
1151+ preventStealing: true
1152+ drag {
1153+ axis: Drag.YAxis
1154+ target: bottomEdge
1155+ minimumY: bottomEdge.pageStartY
1156+ maximumY: page.height
1157+ }
1158+ enabled: edgeLoader.status == Loader.Ready
1159+ visible: page.bottomEdgeEnabled
1160+
1161+ anchors {
1162+ left: parent.left
1163+ right: parent.right
1164+ bottom: parent.bottom
1165+
1166+ }
1167+ height: bottomEdge.tipHeight
1168+ z: 1
1169+
1170+ onReleased: {
1171+ page.bottomEdgeReleased()
1172+ if ((dragDirection === "BottomToTop") &&
1173+ bottomEdge.y < (page.height - bottomEdgeExpandThreshold - bottomEdge.tipHeight)) {
1174+ bottomEdge.state = "expanded"
1175+ } else {
1176+ bottomEdge.state = "collapsed"
1177+ }
1178+ previousY = -1
1179+ dragDirection = "None"
1180+ }
1181+
1182+ onPressed: {
1183+ previousY = mouse.y
1184+ tip.forceActiveFocus()
1185+ }
1186+
1187+ onMouseYChanged: {
1188+ var yOffset = previousY - mouseY
1189+ // skip if was a small move
1190+ if (Math.abs(yOffset) <= units.gu(2)) {
1191+ return
1192+ }
1193+ previousY = mouseY
1194+ dragDirection = yOffset > 0 ? "BottomToTop" : "TopToBottom"
1195+ }
1196+ }
1197+
1198+ Rectangle {
1199+ id: bottomEdge
1200+ objectName: "bottomEdge"
1201+
1202+ readonly property int tipHeight: units.gu(3)
1203+ readonly property int pageStartY: 0
1204+
1205+ z: 1
1206+ color: Theme.palette.normal.background
1207+ clip: true
1208+ anchors {
1209+ left: parent.left
1210+ right: parent.right
1211+ }
1212+ height: page.height
1213+ y: height
1214+ visible: !page.isCollapsed
1215+ state: "collapsed"
1216+ states: [
1217+ State {
1218+ name: "collapsed"
1219+ PropertyChanges {
1220+ target: bottomEdge
1221+ y: bottomEdge.height
1222+ }
1223+ },
1224+ State {
1225+ name: "expanded"
1226+ PropertyChanges {
1227+ target: bottomEdge
1228+ y: bottomEdge.pageStartY
1229+ }
1230+ },
1231+ State {
1232+ name: "floating"
1233+ when: mouseArea.drag.active
1234+ PropertyChanges {
1235+ target: shadow
1236+ opacity: 1.0
1237+ }
1238+ }
1239+ ]
1240+
1241+ transitions: [
1242+ Transition {
1243+ to: "expanded"
1244+ SequentialAnimation {
1245+ alwaysRunToEnd: true
1246+
1247+ SmoothedAnimation {
1248+ target: bottomEdge
1249+ property: "y"
1250+ duration: UbuntuAnimation.FastDuration
1251+ easing.type: Easing.Linear
1252+ }
1253+ SmoothedAnimation {
1254+ target: edgeLoader
1255+ property: "anchors.topMargin"
1256+ to: - units.gu(4)
1257+ duration: UbuntuAnimation.FastDuration
1258+ easing.type: Easing.Linear
1259+ }
1260+ SmoothedAnimation {
1261+ target: edgeLoader
1262+ property: "anchors.topMargin"
1263+ to: 0
1264+ duration: UbuntuAnimation.FastDuration
1265+ easing: UbuntuAnimation.StandardEasing
1266+ }
1267+ ScriptAction {
1268+ script: page._pushPage()
1269+ }
1270+ }
1271+ },
1272+ Transition {
1273+ from: "expanded"
1274+ to: "collapsed"
1275+ SequentialAnimation {
1276+ alwaysRunToEnd: true
1277+
1278+ ScriptAction {
1279+ script: {
1280+ Qt.inputMethod.hide()
1281+ edgeLoader.item.parent = edgeLoader
1282+ edgeLoader.item.anchors.fill = edgeLoader
1283+ edgeLoader.item.active = false
1284+ }
1285+ }
1286+ SmoothedAnimation {
1287+ target: bottomEdge
1288+ property: "y"
1289+ duration: UbuntuAnimation.SlowDuration
1290+ }
1291+ ScriptAction {
1292+ script: {
1293+ // destroy current bottom page
1294+ if (page.reloadBottomEdgePage) {
1295+ edgeLoader.active = false
1296+ // tip will receive focus on page active true
1297+ } else {
1298+ tip.forceActiveFocus()
1299+ }
1300+
1301+ // notify
1302+ page.bottomEdgeDismissed()
1303+
1304+ edgeLoader.active = true
1305+ }
1306+ }
1307+ }
1308+ },
1309+ Transition {
1310+ from: "floating"
1311+ to: "collapsed"
1312+ SmoothedAnimation {
1313+ target: bottomEdge
1314+ property: "y"
1315+ duration: UbuntuAnimation.FastDuration
1316+ }
1317+ }
1318+ ]
1319+
1320+ Loader {
1321+ id: edgeLoader
1322+
1323+ asynchronous: true
1324+ anchors.fill: parent
1325+ //WORKAROUND: The SDK move the page contents down to allocate space for the header we need to avoid that during the page dragging
1326+ Binding {
1327+ target: edgeLoader.status === Loader.Ready ? edgeLoader : null
1328+ property: "anchors.topMargin"
1329+ value: edgeLoader.item && edgeLoader.item.flickable ? edgeLoader.item.flickable.contentY : 0
1330+ when: !page.isReady
1331+ }
1332+
1333+ onLoaded: {
1334+ tip.forceActiveFocus()
1335+ if (page.isReady && edgeLoader.item.active !== true) {
1336+ page._pushPage()
1337+ }
1338+ }
1339+ }
1340+ }
1341+}

Subscribers

People subscribed via source and target branches