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