32cefc5f7a24dfda6db96a571f66f3d7aace5a1a
[marketstoday] / src / qml / StockDetailsComponent.qml
1 /*
2 @version: 0.5
3 @author: Sudheer K. <scifi1947 at gmail.com>
4 @license: GNU General Public License
5 */
6
7 import Qt 4.7
8 import "Library" as Library
9 import "Library/js/CSVUtility.js" as CSVUtility
10
11 Item {
12
13     width: 800
14     height: 380
15
16     id: stockDetailsScreen
17     property int componentWidth: width
18     property int itemHeight: 75
19     property int fontSizeMed: 24
20     property int fontSizeSmall: 20
21     property string symbol: "YHOO"
22     property string stockName: ""
23     property string lastTradedPrice: ""
24     property string lastTradedDateTime: ""
25     property string change: ""
26     property string changePercentage: ""
27     property string daysRange: ""
28     property string yearRange: ""
29     property string marketVolume: ""
30     property string prevClose: ""
31     property string marketCap: ""
32     property string baseChartURL: "http://chart.finance.yahoo.com/z?q=&l=&z=m&p=s&a=v&p=s&lang=en-US&region=US"
33     property string chartURL: ""
34     property string rssURL: ""
35     property string orientation: "Portrait"
36
37     property int currentScreenIndex: 1
38
39     signal logRequest(string strMessage)
40     signal loadChart(string duration)
41
42     Rectangle {
43         anchors.fill: parent
44         color:"#343434"
45         id: detailsRect
46
47         Library.CustomGestureArea {
48             anchors.fill: parent
49             onSwipeLeft: detailsRect.switchScreen()
50             onSwipeRight: detailsRect.switchScreen()
51         }
52
53         Component.onCompleted: {                                   
54             if (symbol !== "") {
55                 loadDetails();
56                 loadNews();
57                 chartURL = baseChartURL+"&t=1d&s="+symbol;
58             }
59         }
60
61         function switchScreen(){
62             switch (currentScreenIndex){
63             case 1:
64                 stockDetailsLoader.sourceComponent = (stockDetailsScreen.width > stockDetailsScreen.height)? stockChartComponentLandscape : stockChartComponentPortrait;
65                 currentScreenIndex = currentScreenIndex + 1;
66                 break;
67             case 2:
68                 stockDetailsLoader.sourceComponent = stockDetailsComponent;
69                 currentScreenIndex = currentScreenIndex - 1;
70                 break;
71             default:
72                 //Do Nothing
73             }
74             logRequest("currentScreenIndex = "+currentScreenIndex);
75         }
76
77         function loadDetails(){
78             var queryURL = 'http://download.finance.yahoo.com/d/quotes.csv?s='+symbol+'&f=snl1d1t1c1p2mwvpj1&e=.csv';
79             //var queryURL = 'http://query.yahooapis.com/v1/public/yql?q=select Symbol,Name,LastTradePriceOnly,LastTradeDate,LastTradeTime,Change,ChangeinPercent,DaysRange,YearRange,Volume,PreviousClose,MarketCapitalization from yahoo.finance.quotes where symbol in ("'+symbol+'")&env=store://datatables.org/alltableswithkeys';
80             logRequest("Loading stock details from "+queryURL);
81             var response = new XMLHttpRequest();
82             response.onreadystatechange = function() {
83                 if (response.readyState === XMLHttpRequest.DONE) {
84                     refreshDetails(response.responseText);
85                 }
86             }
87
88             response.open("GET", queryURL);
89             response.send();
90         }
91
92         function loadNews(){
93             rssURL = "http://feeds.finance.yahoo.com/rss/2.0/headline?region=US&lang=en-US&s="+symbol;
94             logRequest("Loading news from "+rssURL);
95             var response = new XMLHttpRequest();
96             response.onreadystatechange = function() {
97                 if (response.readyState === XMLHttpRequest.DONE) {
98                     refreshNewsModel(response.responseXML);
99                 }
100             }
101
102             response.open("GET", rssURL);
103             response.send();
104         }
105
106         function refreshDetails(responseText){
107             if (!responseText) {
108                 logUtility("No responseText for quote "+symbol);
109                 return;
110             }
111
112             var quoteDetails = CSVUtility.csvToArray(responseText.trim());
113             if (quoteDetails && quoteDetails.length > 0){
114                 //We are only expecting one quote row per symbol.
115                 //snl1d1t1c1p2mwvpj1
116                 var lastTradedDate = "", lastTradedTime ="";
117                 stockName = quoteDetails[0][1];
118                 lastTradedPrice = quoteDetails[0][2];
119                 lastTradedDate = quoteDetails[0][3];
120                 lastTradedTime = quoteDetails[0][4];
121                 change = quoteDetails[0][5];
122                 changePercentage = quoteDetails[0][6];
123                 daysRange = quoteDetails[0][7];
124                 yearRange = quoteDetails[0][8];
125                 marketVolume = quoteDetails[0][9];
126                 prevClose = quoteDetails[0][10];
127                 marketCap = quoteDetails[0][11];
128                 if (lastTradedDate !== "") lastTradedDateTime = lastTradedDate + " " + lastTradedTime;
129             }
130             else {
131                 logRequest("No results for stock quote details");
132             }
133         }
134
135         function refreshNewsModel(responseXML){
136             if (!(responseXML && stockNewsDataModel)) return;
137
138             var xmlDoc = responseXML.documentElement;
139             var channel = xmlDoc.firstChild;
140
141             //Not the best code I ever wrote, but got no choice
142             //Refer to Memory leak issue with XMLListModel --> http://bugreports.qt.nokia.com/browse/QTBUG-15191
143
144             if (channel) {
145                 var itemNodes = channel.childNodes;
146                 if (itemNodes){
147
148                     logRequest("Clearing News Model");
149                     stockNewsDataModel.clear();
150
151                     var i = 0;
152                     for (i = 0; i < itemNodes.length; i++) {
153
154                         if (itemNodes[i].nodeName === 'item'){
155                             var newsElements = itemNodes[i].childNodes;
156                             var j = 0;
157                             var newsTitle,newsLink
158                             for (j = 0; j < newsElements.length; j++){
159
160                                 switch (newsElements[j].nodeName){
161                                     case 'title':
162                                         newsTitle = newsElements[j].childNodes[0].nodeValue;
163                                         break;
164                                     case 'link':
165                                         newsLink = newsElements[j].childNodes[0].nodeValue;
166                                         break;
167                                     default:
168                                 }
169                             }
170                             stockNewsDataModel.append({"title":newsTitle,"link":newsLink});
171                         }
172                     }
173                 }
174             }
175         }
176
177         ListModel{
178             id: stockNewsDataModel
179         }
180
181         Component {
182             id: stockNewsDelegate
183
184             Item {
185                 id: newsWrapper; width: stockDetailsLoader.width; height: itemHeight
186                 Item {
187                     anchors.fill: parent
188                     Rectangle { color: "black"; opacity: index % 2 ? 0.2 : 0.4; height: newsWrapper.height - 2; width: newsWrapper.width; y: 1 }
189                     Text {
190                         anchors {verticalCenter: parent.verticalCenter;left: parent.left;leftMargin: 10;right: parent.right}
191                         text: title; font.pixelSize: stockDetailsScreen.fontSizeSmall
192                         font.bold: false;
193                         verticalAlignment: Text.AlignVCenter
194                         horizontalAlignment: Text.AlignLeft
195                         elide: Text.ElideRight;
196                         color: "white";
197                         style: Text.Raised;
198                         styleColor: "black"
199                     }
200                     Library.CustomGestureArea {
201                         anchors.fill: parent
202                         onDoubleClicked: {
203                             logRequest("Opening news article: "+link);
204                             Qt.openUrlExternally(link);
205                         }
206                         onSwipeLeft: detailsRect.switchScreen()
207                         onSwipeRight: detailsRect.switchScreen()
208                     }
209                 }
210             }
211         }
212
213         Component {
214             id: stockDetailsComponent
215
216             Item {
217                 anchors.fill: parent
218
219                 Text {
220                     id: stockNameLabel
221                     anchors.top: parent.top
222                     width: parent.width
223                     anchors.horizontalCenter: parent.horizontalCenter
224                     height: stockDetailsScreen.fontSizeMed + 15
225                     horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter
226                     font.pixelSize: stockDetailsScreen.fontSizeMed; font.bold: true; elide: Text.ElideMiddle; color: "#B8B8B8"; style: Text.Raised; styleColor: "black"
227                     text: (stockName != "")? (stockName +" ("+symbol+")"):symbol
228                 }
229
230
231                 Rectangle {
232                     id: stockDetailsSection
233                     border.width: 1
234                     border.color: "#BFBFBF"
235                     color:"#2E2E2E"
236                     anchors {top: stockNameLabel.bottom;left: parent.left;right: parent.right}
237                     height: (stockDetailsScreen.width > stockDetailsScreen.height)? 105:155
238                     radius: 15
239
240                     Column{
241                         id: stockDetailsColumn
242                         anchors {top: parent.top; left: parent.left; leftMargin: 10; right: parent.right; rightMargin: 10}
243                         //spacing: 10
244                         //width: parent.width
245
246                         StockDetailsRow{
247                             label1: "Last Traded"
248                             value1: lastTradedPrice
249                             cell1Width: stockDetailsColumn.width/2
250                             multilineLabel1: false
251                             landscape: (stockDetailsScreen.width > stockDetailsScreen.height)
252
253                             label2: "Day's Range"
254                             value2: daysRange
255                             cell2Width: stockDetailsColumn.width/2
256                             multilineLabel2: true
257                         }
258
259                         StockDetailsRow{
260                             label1: "Last Trade Time"
261                             value1: lastTradedDateTime
262                             cell1Width: stockDetailsColumn.width/2
263                             multilineLabel1: true
264                             landscape: (stockDetailsScreen.width > stockDetailsScreen.height)
265
266                             label2: "52w Range"
267                             value2: yearRange
268                             cell2Width: stockDetailsColumn.width/2
269                             multilineLabel2: true
270                         }
271
272                         StockDetailsRow{
273                             label1: "Change"
274                             value1: ((change != "" && changePercentage != "")? change + " ("+changePercentage+")":"")
275                             cell1Width: stockDetailsColumn.width/2
276                             landscape: (stockDetailsScreen.width > stockDetailsScreen.height)
277
278                             label2: "Volume"
279                             value2: marketVolume
280                             cell2Width: stockDetailsColumn.width/2
281                         }
282
283                         StockDetailsRow{
284                             label1: "Prev. Close"
285                             value1: prevClose
286                             cell1Width: stockDetailsColumn.width/2
287                             landscape: (stockDetailsScreen.width > stockDetailsScreen.height)
288
289                             label2: "Market Cap"
290                             value2: marketCap
291                             cell2Width: stockDetailsColumn.width/2
292                         }
293                     }
294                 }
295
296                 Rectangle{
297                     border.width: 1
298                     border.color: "#BFBFBF"
299                     color:"#2E2E2E"
300                     width: parent.width
301                     anchors {top: stockDetailsSection.bottom;topMargin: 10;
302                              bottom: parent.bottom;
303                              left: parent.left;
304                              right: parent.right}
305                     ListView {
306                         flickDeceleration: 500
307                         anchors.fill: parent
308                         model: stockNewsDataModel
309                         delegate: stockNewsDelegate
310                         focus:true
311                         snapMode: ListView.SnapToItem
312                     }
313                 }
314
315             }
316         }
317
318         Loader {
319           id: stockDetailsLoader
320           anchors{top: parent.top; bottom: parent.bottom; bottomMargin: 20;
321                   left: parent.left; leftMargin: 10
322                   right:  parent.right; rightMargin: 10}
323           sourceComponent: stockDetailsComponent
324
325           Connections{
326               target: stockDetailsScreen
327               onOrientationChanged: {
328                   logRequest("Orientation Changed");
329                   logRequest("New orientation is "+stockDetailsScreen.orientation);
330                   logRequest("Margins for StockDetailsLoader are "+stockDetailsLoader.anchors.leftMargin+", "+stockDetailsLoader.anchors.rightMargin);
331
332                   if (currentScreenIndex === 2){
333                       if (stockDetailsScreen.orientation == "Landscape")
334                           stockDetailsLoader.sourceComponent = stockChartComponentLandscape;
335                       else
336                           stockDetailsLoader.sourceComponent = stockChartComponentPortrait;
337                   }
338               }
339           }
340         }
341
342         Rectangle{
343             id: footerText
344             width: parent.width
345             height: 20
346             z: 5
347             color: "#343434"
348             anchors.top: stockDetailsLoader.bottom
349             Text {
350                 id: footerMessage
351                 anchors.fill: parent
352                 text: "Swipe horizontally to switch between details and charts."
353                 horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignVCenter
354                 width: parent.width; font.pixelSize: 12; elide: Text.ElideRight;
355                 color: "#cccccc"
356                 style: Text.Raised; styleColor: "black"
357             }
358
359             Timer {
360                 id: footerMessageTimer
361                 interval: 10000
362                 repeat: false
363                 onTriggered: {
364                     footerMessage.text = "";
365                 }
366             }
367
368             Component.onCompleted: {
369                 footerMessageTimer.start();
370             }
371         }
372
373         Component{
374             id: stockChartComponentLandscape
375
376             Item {
377                 id: chartAreaWrapperLand
378                 anchors.fill: parent
379
380                 Rectangle {
381                     id: chartAreaLand
382                     border.width: 1
383                     border.color: "#BFBFBF"
384                     //color:"#2E2E2E"
385                     color:"white"
386                     anchors { top: parent.top;topMargin: 40;
387                               bottom: parent.bottom; bottomMargin: 20;
388                               left: parent.left; right: parent.right}
389                     radius: 10
390
391                     Library.Loading { anchors.centerIn: parent; visible: chartImg.status == Image.Loading}
392
393                     Image {
394                         id: chartImg
395                         anchors {left: parent.left; leftMargin: 10; verticalCenter: parent.verticalCenter}
396                         source: chartURL
397                         sourceSize.width: 512
398                         sourceSize.height: 288
399                         smooth: true
400                         fillMode: Image.PreserveAspectFit
401                         onStatusChanged: {
402                             switch(status){
403                                 case Image.Ready:
404                                     logRequest("Image is ready");
405                                     break;
406                                 case Image.Loading:
407                                     logRequest("Image is loading");
408                                     break;
409                                 case Image.Error:
410                                     logRequest("Image loading failed");
411                                     break;
412                                 default:
413                                     logRequest("No image specified");
414                                     break;
415                             }
416
417                         }
418
419                         Connections{
420                             target: stockDetailsScreen
421                             onLoadChart: {
422                                 chartURL = baseChartURL+"&t="+duration+"&s="+symbol;
423                                 logRequest(chartURL);
424                             }
425                         }
426                     }
427
428                     Column {
429                         width: 130
430                         spacing: 20
431                         anchors {top: parent.top; topMargin: 40; bottom: parent.bottom;
432                                  right: chartAreaLand.right;rightMargin: 60}
433
434                         Row {
435                             height: 40
436                             spacing: 20
437                             anchors.horizontalCenter: parent.horizontalCenter
438                             Library.Button {
439                                 text:  "1d"
440                                 anchors { verticalCenter: parent.verticalCenter}
441                                 width: 75; height: 40
442                                 onClicked: loadChart("1d");
443                             }
444
445                             Library.Button {
446                                 text:  "5d"
447                                 anchors { verticalCenter: parent.verticalCenter}
448                                 width: 75; height: 40
449                                 onClicked: loadChart("5d");
450                             }
451                         }
452
453                         Row {
454                             height: 40
455                             spacing: 20
456                             anchors.horizontalCenter: parent.horizontalCenter
457                             Library.Button {
458                                 text:  "3m"
459                                 anchors { verticalCenter: parent.verticalCenter}
460                                 width: 75; height: 40
461                                 onClicked: loadChart("3m");
462                             }
463
464                             Library.Button {
465                                 text:  "6m"
466                                 anchors { verticalCenter: parent.verticalCenter}
467                                 width: 75; height: 40
468                                 onClicked: loadChart("6m");
469                             }
470                         }
471                         Row {
472                             height: 40
473                             spacing: 20
474                             anchors.horizontalCenter: parent.horizontalCenter
475                             Library.Button {
476                                 text:  "1y"
477                                 anchors { verticalCenter: parent.verticalCenter}
478                                 width: 75; height: 40
479                                 onClicked: loadChart("1y");
480                             }
481
482                             Library.Button {
483                                 text:  "2y"
484                                 anchors { verticalCenter: parent.verticalCenter}
485                                 width: 75; height: 40
486                                 onClicked: loadChart("2y");
487                             }
488                         }
489                         Row {
490                             height: 40
491                             spacing: 20
492                             anchors.horizontalCenter: parent.horizontalCenter
493                             Library.Button {
494                                 text:  "5y"
495                                 anchors { verticalCenter: parent.verticalCenter}
496                                 width: 75; height: 40
497                                 onClicked: loadChart("5y");
498                             }
499
500                             Library.Button {
501                                 text:  "max"
502                                 anchors { verticalCenter: parent.verticalCenter}
503                                 width: 75; height: 40
504                                 onClicked: loadChart("my");
505                             }
506                         }
507                     }
508                 }
509             }
510         }
511
512         Component{
513             id: stockChartComponentPortrait
514
515             Item {
516                 id: chartAreaWrapperPort
517                 anchors.fill: parent
518
519                 Rectangle {
520                     id: chartAreaPort
521                     border.width: 1
522                     border.color: "#BFBFBF"
523                     //color:"#2E2E2E"
524                     color:"white"
525                     anchors { top: parent.top;topMargin: 40;
526                               bottom: parent.bottom; bottomMargin: 20;
527                               left: parent.left; right: parent.right}
528                     radius: 10
529
530                     Library.Loading { anchors.centerIn: parent; visible: chartImgPort.status == Image.Loading}
531
532                     Image {
533                         id: chartImgPort
534                         //anchors {left: parent.left; leftMargin: 10; horizontalCenter: parent.horizontalCenter}
535                         anchors {horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: 40}
536                         source: chartURL
537                         sourceSize.width: 512
538                         sourceSize.height: 288
539                         smooth: true
540                         fillMode: Image.PreserveAspectFit
541                         width: parent.width - 20
542                         onStatusChanged: {
543                             switch(status){
544                                 case Image.Ready:
545                                     logRequest("Image is ready");
546                                     break;
547                                 case Image.Loading:
548                                     logRequest("Image is loading");
549                                     break;
550                                 case Image.Error:
551                                     logRequest("Image loading failed");
552                                     break;
553                                 default:
554                                     logRequest("No image specified");
555                                     break;
556                             }
557
558                         }
559
560                         Connections{
561                             target: stockDetailsScreen
562                             onLoadChart: {
563                                 chartURL = baseChartURL+"&t="+duration+"&s="+symbol;
564                                 logRequest(chartURL);
565                             }
566                         }
567                     }
568
569                     Column {
570                         width: 280
571                         spacing: 20
572                         anchors {verticalCenter: parent.verticalCenter;verticalCenterOffset: 100; horizontalCenter: parent.horizontalCenter}
573
574                         Row {
575                             height: 40
576                             spacing: 20
577                             anchors.horizontalCenter: parent.horizontalCenter
578                             Library.Button {
579                                 text:  "1d"
580                                 anchors { verticalCenter: parent.verticalCenter}
581                                 width: 75; height: 40
582                                 onClicked: loadChart("1d");
583                             }
584
585                             Library.Button {
586                                 text:  "5d"
587                                 anchors { verticalCenter: parent.verticalCenter}
588                                 width: 75; height: 40
589                                 onClicked: loadChart("5d");
590                             }
591
592                             Library.Button {
593                                 text:  "3m"
594                                 anchors { verticalCenter: parent.verticalCenter}
595                                 width: 75; height: 40
596                                 onClicked: loadChart("3m");
597                             }
598
599                             Library.Button {
600                                 text:  "6m"
601                                 anchors { verticalCenter: parent.verticalCenter}
602                                 width: 75; height: 40
603                                 onClicked: loadChart("6m");
604                             }
605                         }
606
607                         Row {
608                             height: 40
609                             spacing: 20
610                             anchors.horizontalCenter: parent.horizontalCenter
611                             Library.Button {
612                                 text:  "1y"
613                                 anchors { verticalCenter: parent.verticalCenter}
614                                 width: 75; height: 40
615                                 onClicked: loadChart("1y");
616                             }
617
618                             Library.Button {
619                                 text:  "2y"
620                                 anchors { verticalCenter: parent.verticalCenter}
621                                 width: 75; height: 40
622                                 onClicked: loadChart("2y");
623                             }
624
625                             Library.Button {
626                                 text:  "5y"
627                                 anchors { verticalCenter: parent.verticalCenter}
628                                 width: 75; height: 40
629                                 onClicked: loadChart("5y");
630                             }
631
632                             Library.Button {
633                                 text:  "max"
634                                 anchors { verticalCenter: parent.verticalCenter}
635                                 width: 75; height: 40
636                                 onClicked: loadChart("my");
637                             }
638                         }
639                     }
640                 }
641             }
642         }
643
644     }
645 }