Conf saving and Mastodon instance login

Worst login implementation in the history of worst logins implementation
This commit is contained in:
Dusko Angirevic 2017-06-02 15:51:17 +02:00
parent e0b81bf291
commit 01ac966b35
12 changed files with 425 additions and 467 deletions

View file

@ -18,7 +18,6 @@ SOURCES += src/harbour-tooter.cpp
OTHER_FILES += qml/harbour-tooter.qml \
qml/cover/CoverPage.qml \
qml/pages/FirstPage.qml \
qml/pages/SecondPage.qml \
rpm/harbour-tooter.changes.in \
rpm/harbour-tooter.spec \
@ -40,5 +39,7 @@ TRANSLATIONS += translations/harbour-tooter-de.ts
DISTFILES += \
qml/lib/API.js \
qml/pages/MyList.qml \
qml/pages/Tweet.qml
qml/pages/MainPage.qml \
qml/pages/LoginPage.qml \
qml/pages/JSONListModel.qml \
qml/lib/jsonpath.js

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.0.1, 2017-06-01T15:42:11. -->
<!-- Written by QtCreator 4.0.1, 2017-06-02T15:45:28. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
@ -8,7 +8,7 @@
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
@ -768,7 +768,7 @@
<value type="QString" key="MerRunConfiguration.QmlLiveTargetWorkspace"></value>
<value type="int" key="PE.EnvironmentAspect.Base">1</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">harbour-tooter (on Mer Device)</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">harbour-tooter (on Remote Device)</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QmakeProjectManager.MerRunConfiguration:harbour-tooter</value>
<value type="QString" key="Qt4ProjectManager.MaemoRunConfiguration.Arguments"></value>

View file

@ -45,12 +45,16 @@ ApplicationWindow
console.log('confLoaded');
console.log(JSON.stringify(Logic.conf))
if (Logic.conf['instance']) {
Logic.api = Logic.MastodonAPI({ instance: Logic.conf['instance'], api_user_token: "" });
Logic.api = new Logic.MastodonAPI({ instance: Logic.conf['instance'], api_user_token: "" });
}
if (Logic.conf['login']) {
Logic.api.setConfig("api_user_token", Logic.conf['api_user_token'])
pageStack.push(Qt.resolvedUrl("./pages/MainPage.qml"), {})
} else {
pageStack.push(Qt.resolvedUrl("./pages/LoginPage.qml"), {})
}
pageStack.push(Qt.resolvedUrl("./pages/FirstPage.qml"), {})
});
Logic.init()
}

View file

@ -57,13 +57,19 @@ var init = function(){
};
function saveData() {
console.log("SAVING CONF TO DB")
db.transaction(function(tx) {
for (var key in conf) {
if (conf.hasOwnProperty(key)){
console.log(key + "\t>\t"+conf[key]);
if (typeof conf[key] === "object" && conf[key] === null) {
tx.executeSql('DELETE FROM settings WHERE key=? ', [key])
} else {
tx.executeSql('INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?) ', [key, JSON.stringify(conf[key])])
}
}
}
console.log("ENF OF SAVING")
});
}
@ -268,27 +274,34 @@ var MastodonAPI = function(config) {
if (typeof scopes !== "string") {
scopes = scopes.join(" ");
}
$.ajax({
url: apiBase + "apps",
type: "POST",
data: {
client_name: client_name,
redirect_uris: redirect_uri,
scopes: scopes,
website: website
},
success: function (data, textStatus) {
console.log("Registered Application: " + data);
callback(data);
var http = new XMLHttpRequest()
var url = apiBase + "apps";
var params = 'client_name=' + client_name + '&redirect_uris=' + redirect_uri + '&scopes=' + scopes + '&website=' + website;
console.log(params)
http.open("POST", url, true);
// Send the proper header information along with the request
http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
http.onreadystatechange = function() { // Call a function when the state changes.
if (http.readyState == 4) {
if (http.status == 200) {
console.log("Registered Application: " + http.response);
callback(http.response)
} else {
console.log("error: " + http.status)
}
});
}
}
http.send(params);
},
generateAuthLink: function (client_id, redirect_uri, responseType, scopes) {
return config.instance + "/oauth/authorize?client_id=" + client_id + "&redirect_uri=" + redirect_uri +
"&response_type=" + responseType + "&scope=" + scopes.join("+");
},
getAccessTokenFromAuthCode: function (client_id, client_secret, redirect_uri, code, callback) {
$.ajax({
/*$.ajax({
url: config.instance + "/oauth/token",
type: "POST",
data: {
@ -302,7 +315,27 @@ var MastodonAPI = function(config) {
console.log("Got Token: " + data);
callback(data);
}
});
});*/
var http = new XMLHttpRequest()
var url = config.instance + "/oauth/token";
var params = 'client_id=' + client_id + '&client_secret=' + client_secret + '&redirect_uri=' + redirect_uri + '&grant_type=authorization_code&code=' + code;
console.log(params)
http.open("POST", url, true);
// Send the proper header information along with the request
http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
http.onreadystatechange = function() { // Call a function when the state changes.
if (http.readyState == 4) {
if (http.status == 200) {
console.log("Got Token: " + http.response);
callback(http.response)
} else {
console.log("error: " + http.status)
}
}
}
http.send(params);
}
};
};

88
qml/lib/jsonpath.js Normal file
View file

@ -0,0 +1,88 @@
/* JSONPath 0.8.5 - XPath for JSON
*
* Copyright (c) 2007 Stefan Goessner (goessner.net)
* Licensed under the MIT (MIT-LICENSE.txt) licence.
*
*/
function jsonPath(obj, expr, arg) {
var P = {
resultType: arg && arg.resultType || "VALUE",
result: [],
normalize: function(expr) {
var subx = [];
return expr.replace(/[\['](\??\(.*?\))[\]']|\['(.*?)'\]/g, function($0,$1,$2){return "[#"+(subx.push($1||$2)-1)+"]";}) /* http://code.google.com/p/jsonpath/issues/detail?id=4 */
.replace(/'?\.'?|\['?/g, ";")
.replace(/;;;|;;/g, ";..;")
.replace(/;$|'?\]|'$/g, "")
.replace(/#([0-9]+)/g, function($0,$1){return subx[$1];});
},
asPath: function(path) {
var x = path.split(";"), p = "$";
for (var i=1,n=x.length; i<n; i++)
p += /^[0-9*]+$/.test(x[i]) ? ("["+x[i]+"]") : ("['"+x[i]+"']");
return p;
},
store: function(p, v) {
if (p) P.result[P.result.length] = P.resultType == "PATH" ? P.asPath(p) : v;
return !!p;
},
trace: function(expr, val, path) {
if (expr !== "") {
var x = expr.split(";"), loc = x.shift();
x = x.join(";");
if (val && val.hasOwnProperty(loc))
P.trace(x, val[loc], path + ";" + loc);
else if (loc === "*")
P.walk(loc, x, val, path, function(m,l,x,v,p) { P.trace(m+";"+x,v,p); });
else if (loc === "..") {
P.trace(x, val, path);
P.walk(loc, x, val, path, function(m,l,x,v,p) { typeof v[m] === "object" && P.trace("..;"+x,v[m],p+";"+m); });
}
else if (/^\(.*?\)$/.test(loc)) // [(expr)]
P.trace(P.eval(loc, val, path.substr(path.lastIndexOf(";")+1))+";"+x, val, path);
else if (/^\?\(.*?\)$/.test(loc)) // [?(expr)]
P.walk(loc, x, val, path, function(m,l,x,v,p) { if (P.eval(l.replace(/^\?\((.*?)\)$/,"$1"), v instanceof Array ? v[m] : v, m)) P.trace(m+";"+x,v,p); }); // issue 5 resolved
else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) // [start:end:step] phyton slice syntax
P.slice(loc, x, val, path);
else if (/,/.test(loc)) { // [name1,name2,...]
for (var s=loc.split(/'?,'?/),i=0,n=s.length; i<n; i++)
P.trace(s[i]+";"+x, val, path);
}
}
else
P.store(path, val);
},
walk: function(loc, expr, val, path, f) {
if (val instanceof Array) {
for (var i=0,n=val.length; i<n; i++)
if (i in val)
f(i,loc,expr,val,path);
}
else if (typeof val === "object") {
for (var m in val)
if (val.hasOwnProperty(m))
f(m,loc,expr,val,path);
}
},
slice: function(loc, expr, val, path) {
if (val instanceof Array) {
var len=val.length, start=0, end=len, step=1;
loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, function($0,$1,$2,$3){start=parseInt($1||start);end=parseInt($2||end);step=parseInt($3||step);});
start = (start < 0) ? Math.max(0,start+len) : Math.min(len,start);
end = (end < 0) ? Math.max(0,end+len) : Math.min(len,end);
for (var i=start; i<end; i+=step)
P.trace(i+";"+expr, val, path);
}
},
eval: function(x, _v, _vname) {
try { return $ && _v && eval(x.replace(/(^|[^\\])@/g, "$1_v").replace(/\\@/g, "@")); } // issue 7 : resolved ..
catch(e) { throw new SyntaxError("jsonPath: " + e.message + ": " + x.replace(/(^|[^\\])@/g, "$1_v").replace(/\\@/g, "@")); } // issue 7 : resolved ..
}
};
var $ = obj;
if (expr && obj && (P.resultType == "VALUE" || P.resultType == "PATH")) {
P.trace(P.normalize(expr).replace(/^\$;?/,""), obj, "$"); // issue 6 resolved
return P.result.length ? P.result : false;
}
}

View file

@ -0,0 +1,51 @@
/* JSONListModel - a QML ListModel with JSON and JSONPath support
*
* Copyright (c) 2012 Romain Pokrzywka (KDAB) (romain@kdab.com)
* Licensed under the MIT licence (http://opensource.org/licenses/mit-license.php)
*/
import QtQuick 2.0
import "../lib/jsonpath.js" as JSONPath
Item {
property string source: ""
property string json: ""
property string query: ""
property ListModel model : ListModel { id: jsonModel }
property alias count: jsonModel.count
onSourceChanged: {
var xhr = new XMLHttpRequest;
xhr.open("GET", source);
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE)
json = xhr.responseText;
}
xhr.send();
}
onJsonChanged: updateJSONModel()
onQueryChanged: updateJSONModel()
function updateJSONModel() {
jsonModel.clear();
if ( json === "" )
return;
var objectArray = parseJSONString(json, query);
for ( var key in objectArray ) {
var jo = objectArray[key];
jsonModel.append( jo );
}
}
function parseJSONString(jsonString, jsonPathQuery) {
var objectArray = JSON.parse(jsonString);
if ( jsonPathQuery !== "" )
objectArray = JSONPath.jsonPath(objectArray, jsonPathQuery);
return objectArray;
}
}

188
qml/pages/LoginPage.qml Normal file
View file

@ -0,0 +1,188 @@
/*
Copyright (C) 2013 Jolla Ltd.
Contact: Thomas Perl <thomas.perl@jollamobile.com>
All rights reserved.
You may use this file under the terms of BSD license as follows:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Jolla Ltd nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import QtQuick 2.0
import QtWebKit 3.0
import Sailfish.Silica 1.0
import "../lib/API.js" as Logic
Page {
id: loginPage
// The effective value will be restricted by ApplicationWindow.allowedOrientations
allowedOrientations: Orientation.All
SilicaFlickable {
anchors.fill: parent
contentHeight: column.height + Theme.paddingLarge
VerticalScrollDecorator {}
Column {
id: column
width: parent.width
PageHeader { title: "Login" }
SectionHeader {
text: "Instance"
}
TextField {
id: instance
focus: true
label: "Enter the name mastodon instance"
placeholderText: label
text: "https://mastodon.social"
width: parent.width
EnterKey.iconSource: "image://theme/icon-m-enter-next"
EnterKey.onClicked: {
Logic.api = new Logic.MastodonAPI({ instance: instance.text, api_user_token: "" });
Logic.api.registerApplication("Tooter",
'http://localhost/harbour-tooter', // redirect uri, we will need this later on
["read", "write", "follow"], //scopes
"http://grave-design.com/harbour-tooter", //website on the login screen
function(data) {
console.log(data)
var conf = JSON.parse(data)
conf.instance = instance.text;
conf.login = false;
/*conf['login'] = false;
conf['mastodon_client_id'] = data['mastodon_client_id'];
conf['mastodon_client_secret'] = data['mastodon_client_secret'];
conf['mastodon_client_redirect_uri'] = data['mastodon_client_redirect_uri'];
delete Logic.conf;*/
Logic.conf = conf;
console.log(JSON.stringify(conf))
console.log(JSON.stringify(Logic.conf))
// we got our application
// our user to it!
var url = Logic.api.generateAuthLink(Logic.conf["client_id"],
Logic.conf["redirect_uri"],
"code", // oauth method
["read", "write", "follow"] //scopes
);
console.log(url)
webView.url = url
webView.visible = true
}
);
}
}
Label {
anchors {
left: parent.left
right: parent.right
leftMargin: Theme.paddingLarge
rightMargin: Theme.paddingLarge
}
width: parent.width
wrapMode: Text.WordWrap
color: Theme.secondaryHighlightColor
font.pixelSize: Theme.fontSizeExtraSmall
text: "Mastodon is a free, open-source social network. A decentralized alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Pick a server that you trust — whichever you choose, you can interact with everyone else. Anyone can run their own Mastodon instance and participate in the social network seamlessly."
}
}
}
SilicaWebView {
id: webView
visible: false
anchors {
top: parent.top
left: parent.left
right: parent.right
bottom: parent.bottom
}
opacity: 0
onLoadingChanged: {
console.log(url)
if ((url+"").substr(0, 37) === 'http://localhost/harbour-tooter?code=') {
visible = false;
var authCode = (url+"").substr(37)
console.log(authCode)
Logic.api.getAccessTokenFromAuthCode(
Logic.conf["client_id"],
Logic.conf["client_secret"],
Logic.conf["redirect_uri"],
authCode,
function(data) {
// AAAND DATA CONTAINS OUR TOKEN!
console.log(data)
data = JSON.parse(data)
console.log(JSON.stringify(data))
console.log(JSON.stringify(data.access_token))
Logic.conf["api_user_token"] = data.access_token
Logic.conf["login"] = true;
Logic.api.setConfig("api_user_token", Logic.conf["api_user_token"])
pageStack.replace(Qt.resolvedUrl("MainPage.qml"), {})
}
)
}
switch (loadRequest.status)
{
case WebView.LoadSucceededStatus:
opacity = 1
break
case WebView.LoadFailedStatus:
//opacity = 0
break
default:
//opacity = 0
break
}
}
FadeAnimation on opacity {}
PullDownMenu {
MenuItem {
text: "Reload"
onClicked: webView.reload()
}
}
}
}

View file

@ -34,7 +34,7 @@ import "../lib/API.js" as Logic
Page {
id: page
id: mainPage
// The effective value will be restricted by ApplicationWindow.allowedOrientations
allowedOrientations: Orientation.All
@ -43,16 +43,28 @@ Page {
SilicaFlickable {
anchors.fill: parent
PageHeader {
title: "Tooter"
}
// PullDownMenu and PushUpMenu must be declared in SilicaFlickable, SilicaListView or SilicaGridView
PullDownMenu {
MenuItem {
text: qsTrId("Set demo conf")
text: Logic.conf['login'] ? qsTrId("Logout"): qsTrId("Login")
onClicked: {
if (Logic.conf['login']) {
Logic.conf['login'] = false
Logic.conf['instance'] = null;
Logic.conf['api_user_token'] = null;
Logic.conf['dysko'] = null;
} else {
Logic.conf['login'] = true
Logic.conf['instance'] = "https://mastodon.social";
Logic.conf['api_user_token'] = '6d8cb23e3ebf3c7a97dd9adf204e47ad159f1a3d07dbbd0325e98981368d8c51';
}
}
}
MenuItem {
text: qsTr("Show Page 2")
onClicked: pageStack.push(Qt.resolvedUrl("SecondPage.qml"))
@ -60,13 +72,7 @@ Page {
}
MyList {
id: myList
anchors.fill: parent
model: ListModel {
id: model
}
}
}
Component.onCompleted: {
@ -76,37 +82,8 @@ Page {
// data is the http response object
//sidenote: please do not actually execute this request, you could be bullied by your friends
});*/
var tootParser = function(data){
console.log(data)
var ret = {};
ret.id = data.id
ret.content = data.content
ret.created_at = data.created_at
ret.in_reply_to_account_id = data.in_reply_to_account_id
ret.in_reply_to_id = data.in_reply_to_id
ret.user_id = data.account.id
ret.user_locked = data.account.locked
ret.username = data.account.username
ret.display_name = data.account.display_name
ret.avatar_static = data.account.avatar_static
ret.favourited = data.favourited ? true : false
ret.favourites_count = data.favourites_count ? data.favourites_count : 0
ret.reblog = data.reblog ? true : false
ret.reblogged = data.reblogged ? true : false
ret.reblogs_count = data.reblogs_count ? data.reblogs_count : false
ret.muted = data.muted ? true : false
ret.sensitive = data.sensitive ? true : false
ret.visibility = data.visibility ? data.visibility : false
ret.section = (new Date(ret.created_at)).toLocaleDateString()
console.log(ret)
return ret;
}
Logic.api.get("timelines/home", [
/*Logic.api.get("timelines/home", [
//["since_id", 420],
//["max_id", 1337]
], function(data) {
@ -115,12 +92,11 @@ Page {
//model.append(data)
for (var i in data) {
if (data.hasOwnProperty(i)) {
var toot = tootParser(data[i])
model.append(toot)
console.log(JSON.stringify(data[i]))
}
}
console.log(model.count)
});
});*/
console.log(Logic.test)
}
}

View file

@ -1,177 +0,0 @@
import QtQuick 2.0
import Sailfish.Silica 1.0
SilicaListView {
id: myList
property var locale: Qt.locale()
property bool loadStarted : false;
property int scrollOffset;
property string action: ""
property variant vars
property variant conf
signal notify (string what, int num)
onNotify: {
console.log(what + " - " + num)
}
signal openDrawer (bool setDrawer)
onOpenDrawer: {
//console.log("Open drawer: " + setDrawer)
}
signal send (string notice)
onSend: {
console.log("LIST send signal emitted with notice: " + notice)
}
BusyIndicator {
size: BusyIndicatorSize.Large
running: myList.model.count === 0 && !viewPlaceHolder.visible
anchors.centerIn: parent
}
WorkerScript {
id: worker
source: "../../lib/Worker.js"
onMessage: {
if (messageObject.error){
viewPlaceHolder.visible = true;
viewPlaceHolder.text = "Error"
viewPlaceHolder.hintText = messageObject.message
console.log(JSON.stringify(messageObject))
}
if (messageObject.notifyNewItems){
console.log(JSON.stringify(messageObject.notifyNewItems))
notify(action, messageObject.notifyNewItems)
}
}
}
Timer {
interval: 5*60*1000;
running: true;
repeat: true
onTriggered: loadData("prepend")
}
Component.onCompleted: {
var msg = {
'bgAction' : action,
'params' : vars,
'model' : model,
'conf' : conf
};
worker.sendMessage(msg);
}
function loadData(mode){
var msg = {
'bgAction' : action,
'params' : vars,
'model' : model,
'mode' : mode,
'conf' : conf
};
worker.sendMessage(msg);
}
ViewPlaceholder {
id: viewPlaceHolder
enabled: model.count === 0
text: ""
hintText: ""
}
PullDownMenu {
MenuItem {
text: qsTr("Settings")
onClicked: pageStack.push(Qt.resolvedUrl("../Settings.qml"))
}
MenuItem {
visible: action === 'statuses_userTimeline'
text: (following ? "Unfollow" : "Follow")
onClicked: {
var msg = { 'action': following ? "friendships_destroy" : "friendships_create", 'screen_name': username, 'conf' : conf
};
worker.sendMessage(msg);
following = !following
}
}
MenuItem {
text: qsTr("Load more")
onClicked: {
loadData("prepend")
}
}
}
PushUpMenu {
MenuItem {
text: qsTr("Load more")
onClicked: {
loadData("append")
}
}
}
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
right: parent.right
}
clip: true
section {
property: 'section'
criteria: ViewSection.FullString
delegate: SectionHeader {
text: {
var dat = Date.fromLocaleDateString(locale, section);
dat = Format.formatDate(dat, Formatter.TimepointRelativeCurrentDay)
if (dat === "00:00:00" || dat === "00:00") {
visible = false;
height = 0;
return " ";
}else {
return dat;
}
}
}
}
delegate: Tweet {
onSend: {
myList.send(notice)
}
}
add: Transition {
NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 800 }
NumberAnimation { property: "x"; duration: 800; easing.type: Easing.InOutBack }
}
displaced: Transition {
NumberAnimation { properties: "x,y"; duration: 800; easing.type: Easing.InOutBack }
}
onCountChanged: {
contentY = scrollOffset
console.log("CountChanged!")
//last_id_MN
}
onContentYChanged: {
if (contentY > scrollOffset) {
openDrawer(false)
} else {
if (contentY < 100 && !loadStarted){
}
openDrawer(true)
}
scrollOffset = contentY
}
VerticalScrollDecorator {}
}

View file

@ -1,192 +0,0 @@
import QtQuick 2.0
import Sailfish.Silica 1.0
import QtGraphicalEffects 1.0
BackgroundItem {
signal send (string notice)
id: delegate
//property string text: "0"
width: parent.width
signal navigateTo(string link)
height: lblText.paintedHeight + (lblText.text.length > 0 ? Theme.paddingLarge : 0 )+ lblName.paintedHeight + lblScreenName.paintedHeight + (reblog ? Theme.paddingLarge + iconRT.height : 0)
Image {
id: iconRT
y: Theme.paddingLarge
anchors {
right: avatar.right
}
visible: reblog
width: Theme.iconSizeExtraSmall
height: width
source: "image://theme/icon-s-retweet?" + (pressed ? Theme.primaryColor : Theme.secondaryColor)
}
Label {
id: lblRtByName
visible: reblog
anchors {
left: lblName.left
bottom: iconRT.bottom
}
text: 'retweeted by @' + retweetScreenName
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
}
Image {
id: avatar
x: Theme.horizontalPageMargin
y: Theme.paddingLarge + (isRetweet ? iconRT.height+Theme.paddingMedium : 0)
asynchronous: true
width: Theme.iconSizeMedium
height: width
smooth: true
source: avatar_static
visible: true
MouseArea {
anchors.fill: parent
onClicked: {
pageStack.push(Qt.resolvedUrl("../Profile.qml"), {
"name": name,
"username": screenName,
"profileImage": profileImageUrl
})
}
}
}
Label {
id: lblName
anchors {
top: avatar.top
topMargin: 0
left: avatar.right
leftMargin: Theme.paddingMedium
}
text: username
font.weight: Font.Bold
font.pixelSize: Theme.fontSizeSmall
color: (pressed ? Theme.highlightColor : Theme.primaryColor)
}
Image {
id: iconVerified
y: Theme.paddingLarge
anchors {
left: lblName.right
leftMargin: Theme.paddingSmall
verticalCenter: lblName.verticalCenter
}
visible: isVerified
width: isVerified ? Theme.iconSizeExtraSmall*0.8 : 0
opacity: 0.8
height: width
source: "../../verified.svg"
}
ColorOverlay {
anchors.fill: iconVerified
source: iconVerified
color: (pressed ? Theme.secondaryHighlightColor : Theme.secondaryColor)
}
Label {
id: lblScreenName
anchors {
left: iconVerified.right
right: lblDate.left
leftMargin: Theme.paddingMedium
baseline: lblName.baseline
}
truncationMode: TruncationMode.Fade
text: '@'+display_name
font.pixelSize: Theme.fontSizeExtraSmall
color: (pressed ? Theme.secondaryHighlightColor : Theme.secondaryColor)
}
Label {
function timestamp() {
var txt = Format.formatDate(created_at, Formatter.Timepoint)
var elapsed = Format.formatDate(created_at, Formatter.DurationElapsedShort)
return (elapsed ? elapsed : txt )
}
id: lblDate
color: (pressed ? Theme.highlightColor : Theme.primaryColor)
text: Format.formatDate(created_at, new Date() - created_at < 60*60*1000 ? Formatter.DurationElapsedShort : Formatter.TimeValueTwentyFourHours)
font.pixelSize: Theme.fontSizeExtraSmall
horizontalAlignment: Text.AlignRight
anchors {
right: parent.right
baseline: lblName.baseline
rightMargin: Theme.paddingLarge
}
}
Label {
id: lblText
anchors {
left: lblName.left
right: parent.right
top: lblScreenName.bottom
topMargin: Theme.paddingSmall
rightMargin: Theme.paddingLarge
}
height: content.length ? paintedHeight : 0
//text: (highlights.length > 0 ? Theme.highlightText(plainText, new RegExp(highlights, "igm"), Theme.highlightColor) : plainText)
//textFormat:Text.RichText
onLinkActivated: {
console.log(link)
if (link[0] === "@") {
pageStack.push(Qt.resolvedUrl("../Profile.qml"), {
"name": "",
"username": link.substring(1),
"profileImage": ""
})
} else if (link[0] === "#") {
pageStack.pop(pageStack.find(function(page) {
var check = page.isFirstPage === true;
if (check)
page.onLinkActivated(link)
return check;
}));
send(link)
} else {
pageStack.push(Qt.resolvedUrl("../Browser.qml"), {"href" : link})
}
}
text: content
textFormat:Text.RichText
linkColor : Theme.highlightColor
wrapMode: Text.Wrap
font.pixelSize: Theme.fontSizeSmall
color: (pressed ? Theme.highlightColor : Theme.primaryColor)
}
/*MediaBlock {
id: mediaImg
anchors {
left: lblName.left
right: parent.right
top: lblText.bottom
topMargin: Theme.paddingSmall
rightMargin: Theme.paddingLarge
}
model: (media ? media : '')
width: lblDate.x - lblName.x- Theme.paddingLarge
height: 100
}
onClicked: {
pageStack.push(Qt.resolvedUrl("../TweetDetails.qml"), {
"tweets": myList.model,
"screenName": screenName,
"selected": index
})
}*/
}

View file

@ -3,27 +3,20 @@
<TS version="2.1">
<context>
<name></name>
<message id="Set demo conf">
<message id="Logout">
<source></source>
<translation type="unfinished"></translation>
</message>
<message id="Login">
<source></source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FirstPage</name>
<name>MainPage</name>
<message>
<source>Show Page 2</source>
<translation>Zur Seite 2</translation>
</message>
</context>
<context>
<name>MyList</name>
<message>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Load more</source>
<translation type="unfinished"></translation>
<translation type="unfinished">Zur Seite 2</translation>
</message>
</context>
<context>

View file

@ -3,29 +3,22 @@
<TS version="2.1">
<context>
<name></name>
<message id="Set demo conf">
<message id="Logout">
<source></source>
<translation type="unfinished">Tooter</translation>
</message>
<message id="Login">
<source></source>
<translation type="unfinished">Tooter</translation>
</message>
</context>
<context>
<name>FirstPage</name>
<name>MainPage</name>
<message>
<source>Show Page 2</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MyList</name>
<message>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Load more</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SecondPage</name>
<message>