diff options
Diffstat (limited to 'examples/quick/demos/tweetsearch')
6 files changed, 180 insertions, 53 deletions
diff --git a/examples/quick/demos/tweetsearch/content/TweetDelegate.qml b/examples/quick/demos/tweetsearch/content/TweetDelegate.qml index 8cd22110d4..e8bfff437b 100644 --- a/examples/quick/demos/tweetsearch/content/TweetDelegate.qml +++ b/examples/quick/demos/tweetsearch/content/TweetDelegate.qml @@ -103,7 +103,7 @@ Item { Text { id: name - text: Helper.realName(model.name) + text: model.name anchors { left: avatar.right; leftMargin: 10; top: avatar.top; topMargin: -3 } font.pixelSize: 12 font.bold: true @@ -121,7 +121,7 @@ Item { color: "#adebff" linkColor: "white" onLinkActivated: { - var tag = link.split("http://search.twitter.com/search?q=%23") + var tag = link.split("https://twitter.com/search?q=%23") var user = link.split("https://twitter.com/") if (tag[1] != undefined) { mainListView.positionViewAtBeginning() @@ -166,7 +166,7 @@ Item { Text { id: username - text: Helper.twitterName(model.name) + text: model.twitterName x: 10; anchors { top: avatar2.top; topMargin: -3 } font.pixelSize: 12 font.bold: true diff --git a/examples/quick/demos/tweetsearch/content/TweetsModel.qml b/examples/quick/demos/tweetsearch/content/TweetsModel.qml index cd91a787b7..7d813d18c8 100644 --- a/examples/quick/demos/tweetsearch/content/TweetsModel.qml +++ b/examples/quick/demos/tweetsearch/content/TweetsModel.qml @@ -39,53 +39,87 @@ ****************************************************************************/ import QtQuick 2.0 -import QtQuick.XmlListModel 2.0 +import "tweetsearch.js" as Helper Item { id: wrapper - property variant model: xmlModel + // Insert valid consumer key and secret tokens below + // See https://dev.twitter.com/apps +//! [auth tokens] + property string consumerKey : "" + property string consumerSecret : "" +//! [auth tokens] + property string bearerToken : "" + + property variant model: tweets property string from : "" property string phrase : "" - property string mode : "everyone" - property int status: xmlModel.status - - function reload() { xmlModel.reload(); } - - property bool isLoading: status == XmlListModel.Loading + property int status: XMLHttpRequest.UNSENT + property bool isLoading: status === XMLHttpRequest.LOADING property bool wasLoading: false signal isLoaded - XmlListModel { - id: xmlModel + ListModel { id: tweets } - onStatusChanged: { - if (status == XmlListModel.Ready && wasLoading == true) - wrapper.isLoaded() - if (status == XmlListModel.Loading) - wasLoading = true; - else - wasLoading = false; - } + function encodePhrase(x) { return encodeURIComponent(x); } - function encodePhrase(x) { return encodeURIComponent(x); } + function reload() { + tweets.clear() - source: (from == "" && phrase == "") ? "" : - 'http://search.twitter.com/search.atom?from='+from+"&rpp=10&phrase="+encodePhrase(phrase) + if (from == "" && phrase == "") + return; - namespaceDeclarations: "declare default element namespace 'http://www.w3.org/2005/Atom'; " + - "declare namespace twitter=\"http://api.twitter.com/\";"; +//! [requesting] + var req = new XMLHttpRequest; + req.open("GET", "https://api.twitter.com/1.1/search/tweets.json?from=" + from + + "&count=10&q=" + encodePhrase(phrase)); + req.setRequestHeader("Authorization", "Bearer " + bearerToken); + req.onreadystatechange = function() { + status = req.readyState; + if (status === XMLHttpRequest.DONE) { + var objectArray = JSON.parse(req.responseText); + if (objectArray.errors !== undefined) + console.log("Error fetching tweets: " + objectArray.errors[0].message) + else { + for (var key in objectArray.statuses) { + var jsonObject = objectArray.statuses[key]; + tweets.append(jsonObject); + } + } + if (wasLoading == true) + wrapper.isLoaded() + } + wasLoading = (status === XMLHttpRequest.LOADING); + } + req.send(); +//! [requesting] + } - query: "/feed/entry" + onPhraseChanged: reload(); + onFromChanged: reload(); - XmlRole { name: "id"; query: "id/string()" } - XmlRole { name: "content"; query: "content/string()" } - XmlRole { name: "published"; query: "published/string()" } - XmlRole { name: "source"; query: "twitter:source/string()" } - XmlRole { name: "name"; query: "author/name/string()" } - XmlRole { name: "uri"; query: "author/uri/string()" } - XmlRole { name: "image"; query: "link[@rel = 'image']/@href/string()" } + Component.onCompleted: { + if (consumerKey === "" || consumerSecret == "") { + bearerToken = encodeURIComponent(Helper.demoToken()) + return; + } + var authReq = new XMLHttpRequest; + authReq.open("POST", "https://api.twitter.com/oauth2/token"); + authReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); + authReq.setRequestHeader("Authorization", "Basic " + Qt.btoa(consumerKey + ":" + consumerSecret)); + authReq.onreadystatechange = function() { + if (authReq.readyState === XMLHttpRequest.DONE) { + var jsonResponse = JSON.parse(authReq.responseText); + if (jsonResponse.errors !== undefined) + console.log("Authentication error: " + jsonResponse.errors[0].message) + else + bearerToken = jsonResponse.access_token; + } + } + authReq.send("grant_type=client_credentials"); } + } diff --git a/examples/quick/demos/tweetsearch/content/tweetsearch.js b/examples/quick/demos/tweetsearch/content/tweetsearch.js index 9b8638f69e..42a76c99fc 100644 --- a/examples/quick/demos/tweetsearch/content/tweetsearch.js +++ b/examples/quick/demos/tweetsearch/content/tweetsearch.js @@ -1,19 +1,62 @@ .pragma library -function twitterName(str) +function formatDate(date) { - var s = str.split("(") - return s[0] + var da = new Date(date) + return da.toDateString() } -function realName(str) +function demoToken() { - var s = str.split("(") - return s[1].substring(0, s[1].length-1) + var a = new Array(22).join('A') + return a + String.fromCharCode(0x44, 0x69, 0x4a, 0x52, 0x51, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x74, 0x2b, 0x72, 0x6a, 0x6c, 0x2b, 0x71, + 0x6d, 0x7a, 0x30, 0x72, 0x63, 0x79, 0x2b, 0x42, 0x62, + 0x75, 0x58, 0x42, 0x42, 0x73, 0x72, 0x55, 0x48, 0x47, + 0x45, 0x67, 0x3d, 0x71, 0x30, 0x45, 0x4b, 0x32, 0x61, + 0x57, 0x71, 0x51, 0x4d, 0x62, 0x31, 0x35, 0x67, 0x43, + 0x5a, 0x4e, 0x77, 0x5a, 0x6f, 0x39, 0x79, 0x71, 0x61, + 0x65, 0x30, 0x68, 0x70, 0x65, 0x32, 0x46, 0x44, 0x73, + 0x53, 0x39, 0x32, 0x57, 0x41, 0x75, 0x30, 0x67) } -function formatDate(date) +function linkForEntity(entity) { - var da = new Date(date) - return da.toDateString() + return (entity.url ? entity.url : + (entity.screen_name ? 'https://twitter.com/' + entity.screen_name : + 'https://twitter.com/search?q=%23' + entity.text)) +} + +function textForEntity(entity) +{ + return (entity.display_url ? entity.display_url : + (entity.screen_name ? entity.screen_name : entity.text)) +} + +function insertLinks(text, entities) +{ + if (typeof text !== 'string') + return ""; + + if (!entities) + return text; + + // Add all links (urls, usernames and hashtags) to an array and sort them in + // descending order of appearance in text + var links = [] + if (entities.urls) + links = entities.urls.concat(entities.hashtags, entities.user_mentions) + else if (entities.url) + links = entities.url.urls + + links.sort(function(a, b) { return b.indices[0] - a.indices[0] }) + + for (var i = 0; i < links.length; i++) { + var offset = links[i].url ? 0 : 1 + text = text.substring(0, links[i].indices[0] + offset) + + '<a href=\"' + linkForEntity(links[i]) + '\">' + + textForEntity(links[i]) + '</a>' + + text.substring(links[i].indices[1]) + } + return text.replace(/\n/g, '<br>'); } diff --git a/examples/quick/demos/tweetsearch/doc/src/tweetsearch.qdoc b/examples/quick/demos/tweetsearch/doc/src/tweetsearch.qdoc index 9ba252fcb5..a56ed0d7e9 100644 --- a/examples/quick/demos/tweetsearch/doc/src/tweetsearch.qdoc +++ b/examples/quick/demos/tweetsearch/doc/src/tweetsearch.qdoc @@ -32,5 +32,48 @@ \brief A Twitter search client with 3D effects. \image qtquick-demo-tweetsearch-med-1.png \image qtquick-demo-tweetsearch-med-2.png -*/ + \section1 Demo Introduction + + The Tweet Search demo searches items posted to Twitter service + using a number of query parameters. Search can be done for tweets + from a specified user, a hashtag or a search phrase. + + The search result is a list of items showing the contents of the + tweet as well as the name and image of the user who posted it. + Hashtags, names and links in the content are clickable. Clicking + on the image will flip the item to reveal more information. + + \section1 Running the Demo + + Tweet Search uses Twitter API v1.1 for running seaches. + + \section2 Authentication + + Each request must be authenticated on behalf of the application. + For demonstration purposes, the application uses a hard-coded + token for identifying itself to the Twitter service. However, this + token is subject to rate limits for the number of requests as well + as possible expiration. + + If you are having authentication or rate limit problems running the + demo, obtain a set of application-specific tokens (consumer + key and consumer secret) by registering a new application on + \l{https://dev.twitter.com/apps}. + + Type in the two token values in \e {TweetsModel.qml}: + + \snippet demos/tweetsearch/content/TweetsModel.qml auth tokens + + Rebuild and run the demo. + + \section2 JSON Parsing + + Search results are returned in JSON (JavaScript Object Notation) + format. \c TweetsModel uses an \l XMLHTTPRequest object to send + an HTTP GET request, and calls JSON.parse() on the returned text + string to convert it to a JavaScript object. Each object + representing a tweet is then added to a \l ListModel: + + \snippet demos/tweetsearch/content/TweetsModel.qml requesting +*/ diff --git a/examples/quick/demos/tweetsearch/tweetsearch.pro b/examples/quick/demos/tweetsearch/tweetsearch.pro index b063cc4106..27c34bac5d 100644 --- a/examples/quick/demos/tweetsearch/tweetsearch.pro +++ b/examples/quick/demos/tweetsearch/tweetsearch.pro @@ -4,5 +4,10 @@ QT += quick qml SOURCES += main.cpp RESOURCES += tweetsearch.qrc +OTHER_FILES = tweetsearch.qml \ + content/*.qml \ + content/*.js \ + content/resources/* + target.path = $$[QT_INSTALL_EXAMPLES]/quick/demos/tweetsearch INSTALLS += target diff --git a/examples/quick/demos/tweetsearch/tweetsearch.qml b/examples/quick/demos/tweetsearch/tweetsearch.qml index d7e77ceb4b..19d3b5e708 100644 --- a/examples/quick/demos/tweetsearch/tweetsearch.qml +++ b/examples/quick/demos/tweetsearch/tweetsearch.qml @@ -40,6 +40,7 @@ import QtQuick 2.0 import "content" +import "content/tweetsearch.js" as Helper Rectangle { id: main @@ -47,7 +48,6 @@ Rectangle { height: 480 color: "#d6d6d6" - property string searchTerms: "" property int inAnimDur: 250 property int counter: 0 property alias isLoading: tweetsModel.isLoading @@ -85,13 +85,15 @@ Rectangle { onTriggered: { main.counter--; var id = tweetsModel.model.get(idx[main.counter]).id - mainListView.add( { "statusText": tweetsModel.model.get(main.counter).content, - "name": tweetsModel.model.get(main.counter).name, - "userImage": tweetsModel.model.get(main.counter).image, - "source": tweetsModel.model.get(main.counter).source, - "id": id, - "uri": tweetsModel.model.get(main.counter).uri, - "published": tweetsModel.model.get(main.counter).published } ); + var item = tweetsModel.model.get(main.counter) + mainListView.add( { "statusText": Helper.insertLinks(item.text, item.entities), + "twitterName": item.user.screen_name, + "name" : item.user.name, + "userImage": item.user.profile_image_url, + "source": item.source, + "id": id, + "uri": Helper.insertLinks(item.user.url, item.user.entities), + "published": item.created_at } ); ids.push(id) } } @@ -107,7 +109,7 @@ Rectangle { PropertyAction { property: "appear"; value: 250 } } - onDragEnded: if (header.refresh) { tweetsModel.model.reload() } + onDragEnded: if (header.refresh) { tweetsModel.reload() } ListHeader { id: header |