diff --git a/README.md b/README.md
index 651edf8..31bb64a 100644
--- a/README.md
+++ b/README.md
@@ -22,17 +22,20 @@ Currently supported:
* Liking, disliking, favoriting
* Update fetches new posts since last in local DB
* More shows older posts from local DB
-* New Message with images, also possible as DM, Contact selection
+* New Message with images, also possible as DM, Contact/Group access rights
+
ToDo:
-* Test new Aspargus features
-* Nice symbols for liking, disliking and menu for deletion, reposting and answering (currently press on Contact picture, press on news would interfere with external link functionality)
-* Videos as attachment (sending and receiving)
+* Nice symbols for liking, disliking
+* Videos and other binary data as attachment (sending and receiving)
* Rich text editing in Send Dialog
+* Attachments for Direct messages (currently not supported in API
+* Better image selection (currently ugly file dialog)
# Friends #
Currently supported:
+* Tabs for friends, other contacts and groups
* Grid of all known contacts with locally downloaded pictures
* Large friend item for addional information and functionality
* Show news of contact from local database
@@ -42,7 +45,6 @@ Currently supported:
ToDo:
* More information for contact from description page, possibly private information for friends
-* Filter contacts for friends, forums,groups etc.
* Groups: create, change, delete
@@ -53,25 +55,24 @@ Currently supported:
* Show albums and images of contacts
ToDo:
-* Better download mechanism for own images
* Private images of friends
* Support for all themes of friends
* Delete downloaded own images
+* Pinch to zoom, swipe to scroll
# Config #
Currently supported:
* Multiple accounts
* Maximum news (deleted after use of Quit button)
+* View mode for news (tree or timeline)
ToDo
* OAuth?
-* View mode for news (chronological or tree)
# Other #
ToDo
-* Friendstab and Newstab buggy after account change, restart helps
* Calendar tab, video tab
* Photo upload to album (needs API change)
* Translation
@@ -81,5 +82,5 @@ ToDo
## License ##
v0.001 for Friendica < 3.5
v0.002 for Friendica >= 3.5
-Pubished under the [GPL v3](http://gplv3.fsf.org).
+Published under the [GPL v3](http://gplv3.fsf.org).
diff --git a/Screenshots/ConfigTab.jpg b/Screenshots/ConfigTab.jpg
index 77b8d21..c63a34d 100644
Binary files a/Screenshots/ConfigTab.jpg and b/Screenshots/ConfigTab.jpg differ
diff --git a/Screenshots/FriendsTab.jpg b/Screenshots/FriendsTab.jpg
index 609fad9..94d3fe4 100644
Binary files a/Screenshots/FriendsTab.jpg and b/Screenshots/FriendsTab.jpg differ
diff --git a/Screenshots/NewsTab.jpg b/Screenshots/NewsTab.jpg
index 6c1c2ce..ac283c4 100644
Binary files a/Screenshots/NewsTab.jpg and b/Screenshots/NewsTab.jpg differ
diff --git a/Screenshots/PhotoTab.jpg b/Screenshots/PhotoTab.jpg
index 5f98a9e..bb51e5f 100644
Binary files a/Screenshots/PhotoTab.jpg and b/Screenshots/PhotoTab.jpg differ
diff --git a/TODO.txt b/TODO.txt
deleted file mode 100644
index 5fcdc04..0000000
--- a/TODO.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-TODO
-======
-
-
-Layout
-____________
-
-
-
-Config
-____________
-
-- Server Domain
-- SSL Check
-- OAuth?
-
-Fotos erhalten
-_______________
-
-- Liste der Fotos mit api/friendica/photos/list
-- Daten der Fotos als JSON über api/friendica/photo.xml?photo_id=[ID], "album" als Feld vorhanden
-- Raw data in File?
-- Download/Sortieren der Fotos?
-
-Fotos senden
-____________
-
-- Upload über api/media/upload.xml?media= (raw data)?
-- Berechtigungen? Album?
-- Album erstellen/bearbeiten
-
-
-
-
-
diff --git a/v0.002/Release/Friendiqa_v0.002.apk b/v0.002/Release/Friendiqa_v0.002.apk
new file mode 100644
index 0000000..26d5321
Binary files /dev/null and b/v0.002/Release/Friendiqa_v0.002.apk differ
diff --git a/v0.002/Release/source-android/android/AndroidManifest.xml b/v0.002/Release/source-android/android/AndroidManifest.xml
new file mode 100644
index 0000000..a22ecbc
--- /dev/null
+++ b/v0.002/Release/source-android/android/AndroidManifest.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/v0.002/Release/source-android/android/res/drawable/friendiqa.png b/v0.002/Release/source-android/android/res/drawable/friendiqa.png
new file mode 100644
index 0000000..d77cd59
Binary files /dev/null and b/v0.002/Release/source-android/android/res/drawable/friendiqa.png differ
diff --git a/v0.002/Release/source-android/android/res/drawable/icon.png b/v0.002/Release/source-android/android/res/drawable/icon.png
new file mode 100644
index 0000000..d77cd59
Binary files /dev/null and b/v0.002/Release/source-android/android/res/drawable/icon.png differ
diff --git a/v0.002/Release/source-android/android/res/drawable/splash.png b/v0.002/Release/source-android/android/res/drawable/splash.png
new file mode 100644
index 0000000..25a3580
Binary files /dev/null and b/v0.002/Release/source-android/android/res/drawable/splash.png differ
diff --git a/v0.002/Release/source-android/android/res/values/libs.xml b/v0.002/Release/source-android/android/res/values/libs.xml
new file mode 100644
index 0000000..4d68673
--- /dev/null
+++ b/v0.002/Release/source-android/android/res/values/libs.xml
@@ -0,0 +1,25 @@
+
+
+
+ - https://download.qt-project.org/ministro/android/qt5/qt-5.4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/v0.002/Release/source-android/application.qrc b/v0.002/Release/source-android/application.qrc
new file mode 100644
index 0000000..ecabb77
--- /dev/null
+++ b/v0.002/Release/source-android/application.qrc
@@ -0,0 +1,27 @@
+
+
+ qml/friendiqa.qml
+ qml/newsqml/NewsTab.qml
+ qml/newsqml/Newsitem.qml
+ qml/newsqml/MessageSend.qml
+ qml/newsqml/PermissionDialog.qml
+ qml/contactqml/FriendsTab.qml
+ qml/contactqml/GroupComponent.qml
+ qml/contactqml/ContactComponent.qml
+ qml/contactqml/ContactDetailsComponent.qml
+ qml/genericqml/BlueButton.qml
+ qml/photoqml/PhotoComponent.qml
+ qml/photoqml/PhotogroupComponent.qml
+ qml/photoqml/PhotoTab.qml
+ qml/configqml/InfoBox.qml
+ qml/configqml/ConfigTab.qml
+ js/layout.js
+ js/photoworker.js
+ js/service.js
+ js/news.js
+ js/newsworker.js
+ js/helper.js
+ images/defaultcontact.jpg
+ qml/newsqml/Conversation.qml
+
+
diff --git a/v0.002/Release/source-android/common/filesystem.cpp b/v0.002/Release/source-android/common/filesystem.cpp
new file mode 100644
index 0000000..758f7c8
--- /dev/null
+++ b/v0.002/Release/source-android/common/filesystem.cpp
@@ -0,0 +1,49 @@
+#include "filesystem.h"
+
+FILESYSTEM *FILESYSTEM::instance()
+{
+ static FILESYSTEM filesystem;
+ return &filesystem;
+}
+
+FILESYSTEM::FILESYSTEM(QObject *parent) : QObject(parent){}
+
+void FILESYSTEM::setDirectory(QString Directory)
+{
+ if (Directory!=m_Directory) {
+ m_Directory = Directory;
+ emit directoryChanged();
+ }
+}
+
+QString FILESYSTEM::Directory() const
+{
+ return m_Directory;
+}
+
+void FILESYSTEM::makeDir(QString name)
+{
+ QDir dir(m_Directory);
+ if (dir.mkdir(name)){
+ emit success(name);
+ }
+ else {emit error(name,1);}
+}
+
+void FILESYSTEM::rmDir()
+{
+ QDir dir(m_Directory);
+ if (dir.removeRecursively()){
+ emit success(m_Directory);
+ }
+ else {emit error(m_Directory,1);}
+}
+
+void FILESYSTEM::rmFile(QString name)
+{
+ QDir dir(m_Directory);
+ if(dir.remove(name)){
+ emit success(name);
+ }
+ else {emit error(name,1);}
+}
diff --git a/v0.002/Release/source-android/common/filesystem.h b/v0.002/Release/source-android/common/filesystem.h
new file mode 100644
index 0000000..53d2caf
--- /dev/null
+++ b/v0.002/Release/source-android/common/filesystem.h
@@ -0,0 +1,33 @@
+#ifndef FILESYSTEM_H
+#define FILESYSTEM_H
+
+#include
+#include
+
+class FILESYSTEM : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString Directory READ Directory WRITE setDirectory NOTIFY directoryChanged)
+
+public:
+ static FILESYSTEM *instance();
+ explicit FILESYSTEM(QObject *parent = 0);
+ void setDirectory(QString Directory);
+ QString Directory() const;
+
+signals:
+ void directoryChanged();
+ void success(QString data);
+ void error(QString data, int code);
+
+public slots:
+ //void setDirectory(QString Directory);
+ void makeDir(QString name);
+ void rmDir();
+ void rmFile(QString name);
+
+private:
+ QString m_Directory;
+};
+
+#endif // FILSYSTEM_H
diff --git a/v0.002/Release/source-android/common/friendiqa.cpp b/v0.002/Release/source-android/common/friendiqa.cpp
new file mode 100644
index 0000000..de055f0
--- /dev/null
+++ b/v0.002/Release/source-android/common/friendiqa.cpp
@@ -0,0 +1,22 @@
+#include
+#include
+#include
+#include "xhr.h"
+#include "filesystem.h"
+
+int main(int argc, char *argv[]) {
+ QApplication app(argc, argv);
+ QQuickView view;
+
+
+ XHR* xhr = XHR::instance();
+ view.rootContext()->setContextProperty("xhr", xhr);
+ FILESYSTEM* filesystem = FILESYSTEM::instance();
+ view.rootContext()->setContextProperty("filesystem", filesystem);
+ view.setSource(QUrl("qrc:/qml/friendiqa.qml"));
+ view.show();
+ view.connect(view.rootContext()->engine(), SIGNAL(quit()), &app, SLOT(quit()));
+ return app.exec();
+
+}
+
diff --git a/v0.002/Release/source-android/common/uploadableimage.cpp b/v0.002/Release/source-android/common/uploadableimage.cpp
new file mode 100644
index 0000000..81bea1a
--- /dev/null
+++ b/v0.002/Release/source-android/common/uploadableimage.cpp
@@ -0,0 +1,90 @@
+#include "uploadableimage.h"
+
+
+#include
+#include
+#include
+#include
+
+void UploadableImage::setSource(const QString &a) {
+ if (a != m_source) {
+ m_source = a;
+ //m_base64 = "";
+ m_mimetype = "";
+ m_filename = "";
+
+ qDebug() << "UploadableImage::setSource : " << m_source;
+
+ if (m_source=="") {
+ emit sourceChanged();
+ //emit base64Changed();
+ emit mimetypeChanged();
+ emit filenameChanged();
+ return;
+ }
+
+ QImage fullimage = QImage(QUrl(m_source).toLocalFile());
+ if (fullimage.width() > 800 || fullimage.height() > 800) {
+ if (fullimage.width() > fullimage.height()) {
+ m_image = fullimage.scaledToWidth(800);
+ } else {
+ m_image = fullimage.scaledToHeight(800);
+ }
+ } else {
+ m_image = fullimage;
+ }
+ qDebug() << "UploadableImage::setSource : " << m_image.width() << "x" << m_image.height();
+ emit sourceChanged();
+
+ QFileInfo fi(m_source);
+
+ m_filename = fi.fileName();
+ emit filenameChanged();
+
+ QString filetype = fi.suffix().toUpper();
+ if (filetype!="PNG" && filetype!="JPG") {
+ filetype = "JPG";
+ }
+ qDebug() << "UploadableImage::setSource : " << "Saving as " << filetype;
+
+ m_mimetype = "image/"+filetype.toLower();
+ emit mimetypeChanged();
+
+ /*
+ QByteArray byteArray;
+ QBuffer buffer(&byteArray);
+ m_image.save(&buffer, filetype.toLatin1().constData());
+ QString b64 = QString::fromLatin1(byteArray.toBase64().data());
+
+ for(int k=0; k
+#include
+#include
+
+class UploadableImage : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
+ //Q_PROPERTY(QString base64 READ base64 NOTIFY base64Changed)
+ Q_PROPERTY(QString filename READ filename NOTIFY filenameChanged)
+ Q_PROPERTY(QString mimetype READ mimetype NOTIFY mimetypeChanged)
+ Q_PROPERTY(QByteArray bytes READ bytes)
+
+public:
+ void setSource(const QString &a);
+ QString source() const;
+
+ //QString base64() const;
+ QString filename() const;
+ QString mimetype() const;
+
+ QByteArray bytes();
+signals:
+ void sourceChanged();
+ //void base64Changed();
+ void filenameChanged();
+ void mimetypeChanged();
+
+private:
+ QString m_source;
+ QImage m_image;
+ //QString m_base64;
+ QString m_filename;
+ QString m_mimetype;
+};
+
+#endif // UPLOADABLEIMAGE_H
diff --git a/v0.002/Release/source-android/common/xhr.cpp b/v0.002/Release/source-android/common/xhr.cpp
new file mode 100644
index 0000000..9661273
--- /dev/null
+++ b/v0.002/Release/source-android/common/xhr.cpp
@@ -0,0 +1,235 @@
+#include "xhr.h"
+
+#include
+#include
+#include
+
+#include "uploadableimage.h"
+
+XHR *XHR::instance()
+{
+ static XHR xhr;
+ return &xhr;
+}
+
+XHR::XHR(QObject *parent) : QObject(parent)
+{
+ request.setSslConfiguration(QSslConfiguration::defaultConfiguration());
+}
+
+void XHR::setUrl(QString url)
+{
+ if (url!=m_url) {
+ m_url = url;
+ emit urlChanged();
+ }
+}
+
+void XHR::setLogin(QString login)
+{
+ if (login!=m_login) {
+ m_login = login;
+ emit loginChanged();
+ }
+}
+
+void XHR::setFilename(QString filename)
+{
+ if (filename!=m_filename) {
+ m_filename = filename;
+ emit filenameChanged();
+ }
+}
+
+void XHR::setDownloadtype(QString downloadtype)
+{
+ if (downloadtype!=m_downloadtype) {
+ m_downloadtype = downloadtype;
+ emit downloadtypeChanged();
+ }
+}
+
+QString XHR::url() const
+{
+ return m_url;
+}
+
+QString XHR::login() const
+{
+ return m_login;
+}
+
+QString XHR::filename() const
+{
+ return m_filename;
+}
+
+QString XHR::downloadtype() const
+{
+ return m_downloadtype;
+}
+
+void XHR::setParam(QString name, QString value)
+{
+ params.insert(name, value);
+}
+
+void XHR::setImageFileParam(QString name, QString url)
+{
+ files.insert(name, url);
+}
+
+void XHR::clearParams()
+{
+ files.clear();
+ params.clear();
+}
+
+void XHR::download()
+{
+ QUrl requrl(m_url);
+ //qDebug()<< "replyerror"<error();
+// qDebug() << "start download of " << requrl;
+ request.setUrl(requrl);
+ reply = manager.get(request);
+// qDebug() << "reply " << reply->header(QNetworkRequest::LocationHeader)<header(QNetworkRequest::LastModifiedHeader);
+ // qDebug() << "request " << request.url();
+ // reply->ignoreSslErrors();
+ connect(reply, &QNetworkReply::readyRead,this, &XHR::onReadyRead);
+ //connect(reply,SIGNAL(downloadProgress(qint64,qint64)), this,SLOT(updateDownloadProgress(qint64,qint64)));
+ connect(reply, &QNetworkReply::finished,this, &XHR::onRequestFinished);
+ //connect(reply, SIGNAL(finished()),this, SLOT(onRequestFinished()));
+ connect(reply, &QNetworkReply::sslErrors, this, &XHR::onSSLError);
+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onReplyError(QNetworkReply::NetworkError)));
+}
+
+void XHR::get()
+{
+ QUrlQuery query;
+
+ QHashIterator i(params);
+ while(i.hasNext()) {
+ i.next();
+ query.addQueryItem(i.key(), i.value());
+ }
+
+ QUrl requrl(m_url);
+ requrl.setQuery(query);
+
+ QByteArray loginData = m_login.toLocal8Bit().toBase64();
+ QString headerData = "Basic " + loginData;
+ request.setRawHeader("Authorization", headerData.toLocal8Bit());
+
+
+ request.setUrl(requrl);
+ reply = manager.get(request);
+
+ connect(reply, &QNetworkReply::finished, this, &XHR::onReplySuccess);
+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onReplyError(QNetworkReply::NetworkError)));
+ connect(reply, &QNetworkReply::readyRead, this, &XHR::onReadyRead);
+ connect(reply, &QNetworkReply::sslErrors, this, &XHR::onSSLError);
+}
+
+void XHR::post()
+{
+ qDebug() << "start post to " << m_url;
+ QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
+
+ QHashIterator iparams(params);
+ while(iparams.hasNext()) {
+ iparams.next();
+ qDebug() << "\t add param " << iparams.key() << " : " << iparams.value();
+ QHttpPart textPart;
+ textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"" + iparams.key() + "\""));
+
+
+ textPart.setBody(iparams.value().toUtf8());
+ multiPart->append(textPart);
+ }
+
+ UploadableImage uimg;
+ QHashIterator ifiles(files);
+ while(ifiles.hasNext()) {
+ ifiles.next();
+
+ uimg.setSource(ifiles.value());
+ qDebug() << "\t image: " << uimg.mimetype() << ", " << ifiles.key();
+
+ QHttpPart imagePart;
+ imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(uimg.mimetype()));
+ imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"" + ifiles.key() + "\"; filename=\""+uimg.filename()+"\""));
+ imagePart.setBody(uimg.bytes());
+ multiPart->append(imagePart);
+ }
+
+ QByteArray loginData = m_login.toLocal8Bit().toBase64();
+ QString headerData = "Basic " + loginData;
+ request.setRawHeader(QByteArray("Authorization"), headerData.toLocal8Bit());
+
+ request.setUrl(m_url);
+ reply = manager.post(request, multiPart);
+ qDebug() << "\t request sent";
+ connect(reply, &QNetworkReply::finished, this, &XHR::onReplySuccess);
+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onReplyError(QNetworkReply::NetworkError)));
+ connect(reply, &QNetworkReply::readyRead, this, &XHR::onReadyRead);
+ connect(reply, &QNetworkReply::sslErrors, this, &XHR::onSSLError);
+ qDebug() << "\t reply signals connected";
+}
+
+void XHR::onReplyError(QNetworkReply::NetworkError code)
+{
+ qDebug() << code;
+ emit this->error( bufferToString(), (int) code);
+ reply->deleteLater();
+}
+
+void XHR::onReplySuccess()
+{
+ qDebug() << "!";
+ emit this->success( bufferToString() );
+ reply->deleteLater();
+}
+
+void XHR::onRequestFinished()
+{
+ // Save the file here
+ //qDebug() << "buffer downloaded "<error(m_downloadtype,1);}
+ else {QFile file(m_filename);
+ file.open(QIODevice::WriteOnly);
+ file.write(buffer);
+ buffer.clear();
+ file.close();
+ //qDebug() << m_url << "File downloaded "<downloaded(m_downloadtype);
+ //reply->deleteLater();
+ }
+}
+
+void XHR::onReadyRead()
+{
+ qDebug() << ".";
+ buffer += reply->readAll();
+}
+
+//void XHR::updateDownloadProgress(qint64 bytesRead, qint64 totalBytes)
+//{
+// qDebug() << "Bytes: " << bytesRead<<" / "< &errors)
+{
+ qDebug() << "XHR::onSSLError :" ;
+ QListIterator ierrs(errors);
+ while(ierrs.hasNext()) {
+ qDebug() << "\t" << ierrs.next().errorString();
+ }
+}
+
+QString XHR::bufferToString()
+{
+ return QTextCodec::codecForName("utf-8")->toUnicode(buffer);
+}
+
+
diff --git a/v0.002/Release/source-android/common/xhr.h b/v0.002/Release/source-android/common/xhr.h
new file mode 100644
index 0000000..6e93c6c
--- /dev/null
+++ b/v0.002/Release/source-android/common/xhr.h
@@ -0,0 +1,73 @@
+#ifndef XHR_H
+#define XHR_H
+
+#include
+#include
+#include
+#include
+
+class XHR : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
+ Q_PROPERTY(QString login READ login WRITE setLogin NOTIFY loginChanged)
+ Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
+ Q_PROPERTY(QString downloadtype READ downloadtype WRITE setDownloadtype NOTIFY downloadtypeChanged)
+
+
+public:
+ static XHR *instance();
+
+ explicit XHR(QObject *parent = 0);
+
+ QString url() const;
+ QString login() const;
+ QString filename() const;
+ QString downloadtype() const;
+
+signals:
+ void urlChanged();
+ void loginChanged();
+ void filenameChanged();
+ void downloadtypeChanged();
+ void downloaded(QString data);
+ void success(QString data);
+ void error(QString data, int code);
+
+public slots:
+ void setUrl(QString url);
+ void setLogin(QString login);
+ void setDownloadtype(QString downloadtype);
+ void setFilename(QString filename);
+ void setParam(QString name, QString value);
+ void setImageFileParam(QString name, QString url);
+ void clearParams();
+ void post();
+ void get();
+ void download();
+
+private slots:
+ void onReplyError(QNetworkReply::NetworkError code);
+ void onReplySuccess();
+ void onRequestFinished();
+ void onReadyRead();
+ void onSSLError(const QList &errors);
+ //void updateDownloadProgress(qint64 bytesRead, qint64 totalBytes);
+
+private:
+ QByteArray buffer;
+ QString m_url;
+ QString m_login;
+ QString m_filename;
+ QString m_downloadtype;
+ QHash params;
+ QHash files;
+
+ QNetworkAccessManager manager;
+ QNetworkRequest request;
+ QNetworkReply *reply;
+
+ QString bufferToString();
+};
+
+#endif // XHR_H
diff --git a/v0.002/Release/source-android/friendiqa.pro b/v0.002/Release/source-android/friendiqa.pro
new file mode 100644
index 0000000..484b220
--- /dev/null
+++ b/v0.002/Release/source-android/friendiqa.pro
@@ -0,0 +1,55 @@
+# NOTICE:
+#
+# Application name defined in TARGET has a corresponding QML filename.
+# If name defined in TARGET is changed, the following needs to be done
+# to match new name:
+# - corresponding QML filename must be changed
+# - desktop icon filename must be changed
+# - desktop filename must be changed
+# - icon definition filename in desktop file must be changed
+# - translation filenames have to be changed
+
+# The name of your application
+TARGET = friendiqa
+CONFIG += debug
+QT += qml quick gui widgets
+
+SOURCES += common/friendiqa.cpp \
+ common/uploadableimage.cpp \
+ common/xhr.cpp \
+ common/filesystem.cpp
+
+RESOURCES = application.qrc
+
+OTHER_FILES += qml/friendiqa.qml \
+ translations/*.ts \
+ qml/*.qml
+ qml/newsqml/*.qml
+ qml/contactqml/*.qml
+ qml/photoqml/*.qml
+ qml/configqml/*.qml
+ js/*.js
+
+# German translation is enabled as an example. If you aren't
+# planning to localize your app, remember to comment out the
+# following TRANSLATIONS line. And also do not forget to
+# modify the localized app name in the the .desktop file.
+TRANSLATIONS += translations/friendiqa-de.ts
+
+HEADERS += \
+ common/uploadableimage.h \
+ common/xhr.h \
+ common/filesystem.h
+
+DISTFILES += \
+ android/AndroidManifest.xml \
+ android/gradle/wrapper/gradle-wrapper.jar \
+ android/gradlew \
+ android/res/values/libs.xml \
+ android/build.gradle \
+ android/gradle/wrapper/gradle-wrapper.properties \
+ android/gradlew.bat
+
+ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
+
+
diff --git a/v0.002/Release/source-android/images/defaultcontact.jpg b/v0.002/Release/source-android/images/defaultcontact.jpg
new file mode 100644
index 0000000..bb7bce2
Binary files /dev/null and b/v0.002/Release/source-android/images/defaultcontact.jpg differ
diff --git a/v0.002/Release/source-android/js/friendworker.js b/v0.002/Release/source-android/js/friendworker.js
new file mode 100644
index 0000000..6b6c5be
--- /dev/null
+++ b/v0.002/Release/source-android/js/friendworker.js
@@ -0,0 +1,12 @@
+WorkerScript.onMessage = function(msg) {
+ msg.model.clear();
+ for (var j=0;j 0 ) {
+ for (var i in list) { if (list[i][prop] === val) {
+ return i;
+ }
+ }
+ } return -1;
+}
+function cleanArray(array) {
+var arraystring=JSON.stringify(array);
+ arraystring=arraystring.replace(/[\[\]]/g , '');
+return arraystring;
+}
diff --git a/v0.002/Release/source-android/js/layout.js b/v0.002/Release/source-android/js/layout.js
new file mode 100644
index 0000000..23815fd
--- /dev/null
+++ b/v0.002/Release/source-android/js/layout.js
@@ -0,0 +1,44 @@
+function showFriends(db) {
+ Service.readActiveConfig(db,function(login){
+ Service.requestFriends(login.url,login.user,login.password,displayFriends);
+ });
+}
+function displayFriends(obj){
+ for (var i=0; i= c.x)
+ f.contentX = c.x;
+ else if (f.contentX+f.width <= c.x+c.width)
+ f.contentX = c.x+c.width-f.width;
+ if (f.contentY >= c.y)
+ f.contentY = c.y;
+ else if (f.contentY+f.height <= c.y+c.height)
+ f.contentY = c.y+c.height-f.height;
+}
+
+function createObject(objectQml,qmlParameters,parentitem,callback) {
+ var component = Qt.createComponent(objectQml);
+ if (component.status === Component.Ready || component.status === Component.Error)
+ finishCreation(component,qmlParameters,parentitem,callback);
+ else
+ component.statusChanged.connect(finishCreation(qmlParameters));
+}
+
+function finishCreation(component,qmlParameters,parentitem,callback) {
+ if (component.status === Component.Ready) {
+ var createdObject = component.createObject(parentitem, qmlParameters);
+ if (createdObject === null)
+ print("Error creating image"); }
+ else if (component.status === Component.Error)
+ print("Error loading component:"+component.errorString());
+ else {print("created")}
+ //callback(createdObject);
+}
+
diff --git a/v0.002/Release/source-android/js/news.js b/v0.002/Release/source-android/js/news.js
new file mode 100644
index 0000000..a23fb58
--- /dev/null
+++ b/v0.002/Release/source-android/js/news.js
@@ -0,0 +1,399 @@
+.pragma library
+.import QtQuick.LocalStorage 2.0 as Sql
+.import "qrc:/js/helper.js" as Helperjs
+
+function requestFriends(login,database,rootwindow,callback){
+// return array of friends
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ db.transaction( function(tx) {
+ var result = tx.executeSql('UPDATE contacts SET isFriend=0 where username="'+login.username+'"')}); // clean old friends
+ Helperjs.friendicaRequest(login,"/api/statuses/friends", rootwindow,function (obj){
+ var friends=JSON.parse(obj);
+ for (var i=0;i'+lastDate);
+ var result2 = tx.executeSql('SELECT id from contacts WHERE username="'+login.username+'" AND isFriend=0 AND imageAge > '+lastDate);
+ for (var j=0;j0){
+ for (var j=0;j0){
+ for (var k=0;j1){
+ var helpernews=newsrs.rows.item(0);
+ helpernews.newscount=newsrs.rows.length;
+ helpernews=fetchUsersForNews(database,user,helpernews)
+ newsArray.push(helpernews);
+ //}
+}
+ callback(newsArray);
+ })}
+
+function inArray(list, prop, val) {
+ if (list.length > 0 ) {
+ for (var i in list) {if (list[i][prop] == val) {
+ return true;
+ }
+ }
+ } return false;
+}
+
+function cleanDate(date){
+var cleanedDate= date.slice(0,3)+", "+date.slice(8,11)+date.slice(4,7)+date.slice(25,30)+date.slice(10,25);
+return cleanedDate
+}
diff --git a/v0.002/Release/source-android/js/newsworker.js b/v0.002/Release/source-android/js/newsworker.js
new file mode 100644
index 0000000..9ae91b6
--- /dev/null
+++ b/v0.002/Release/source-android/js/newsworker.js
@@ -0,0 +1,63 @@
+WorkerScript.onMessage = function(msg) {
+ if(msg.appendnews!==true){ msg.model.clear()};
+
+ for (var j=0;j0){
+ if (newsitemobject.like.length==1){likeText= Qt.atob(newsitemobject.like[0].name)+" "+ qsTr("likes this.")}
+ else {likeText= newsitemobject.like.length+" "+ qsTr("like this.")}
+ }
+ if (newsitemobject.dislike.length>0){
+ if (newsitemobject.dislike.length==1){dislikeText= QT.atob(newsitemobject.dislike[0].name)+" "+ qsTr("doesn't like this.")}
+ else {dislikeText= newsitemobject.dislike.length+" "+ qsTr("don't like this.")}
+ }
+ if (newsitemobject.attendyes.length>0){
+ if (newsitemobject.attendyes.length==1){attendyesText= Qt.atob(newsitemobject.attendyes[0].name)+" "+ qsTr("will attend.")}
+ else {attendyesText= newsitemobject.attendyes.length+" "+ qsTr("persons will attend.")}
+ }
+ if (newsitemobject.attendno.length>0){
+ if (newsitemobject.attendno.length==1){attendnoText= Qt.atob(newsitemobject.attendno[0].name)+" "+ qsTr("will not attend.")}
+ else {attendnoText= newsitemobject.attendno.length+" "+ qsTr("persons will not attend.")}
+ }
+ if (newsitemobject.attendmaybe.length>0){
+ if (newsitemobject.attendmaybe.length==1){attendmaybeText= Qt.atob(newsitemobject.attendmaybe[0].name)+" "+ qsTr("may attend.")}
+ else {attendmaybeText= newsitemobject.attendmaybe.length+" "+ qsTr("persons may attend.")}
+ }
+ var friendica_activities_self=JSON.parse(newsitemobject.friendica_activities_self);
+ if (friendica_activities_self.indexOf(3)!=-1){self.attending=qsTr("yes")}
+ if (friendica_activities_self.indexOf(4)!=-1){self.attending=qsTr("no")}
+ if (friendica_activities_self.indexOf(5)!=-1){self.attending=qsTr("maybe")}
+ if (friendica_activities_self.indexOf(1)!=-1){self.liked=1}
+ if (friendica_activities_self.indexOf(2)!=-1){self.disliked=1}
+ }
+ var friendica_activities={likeText:likeText,dislikeText:dislikeText,attendyesText:attendyesText,attendnoText:attendnoText,attendmaybeText:attendmaybeText,self:self}
+ //print(JSON.stringify(friendica_activities) ) ;
+ var seconds=(msg.currentTime-newsitemobject.created_at)/1000;
+ var timestring="";
+ if (seconds<60) {timestring=seconds+" "+qsTr("seconds") +" "+qsTr("ago");}
+ else if (seconds<90){timestring=Math.round(seconds/60)+" "+qsTr("minute") +" "+qsTr("ago");}
+ else if (seconds<3600){timestring=Math.round(seconds/60)+" "+qsTr("minutes") +" "+qsTr("ago");}
+ else if (seconds<5400){timestring=Math.round(seconds/3600)+" "+qsTr("hour") +" "+qsTr("ago");}
+ else if (seconds<86400){timestring=Math.round(seconds/3600)+" "+qsTr("hours") +" "+qsTr("ago");}
+ else if (seconds<129600){timestring=Math.round(seconds/86400)+" "+qsTr("day") +" "+qsTr("ago");}
+ else if (seconds<3888000){timestring=Math.round(seconds/86400)+" "+qsTr("days") +" "+qsTr("ago");}
+ else if (seconds<5832000){timestring=Math.round(seconds/3888000)+" "+qsTr("month") +" "+qsTr("ago");}
+ else if (seconds<69984000){timestring=Math.round(seconds/3888000)+" "+qsTr("months") +" "+qsTr("ago");}
+ else {timestring=Math.round(seconds/69984000)+" "+qsTr("years") +" "+qsTr("ago");}
+ var data=({"newsitemobject": newsitemobject,"dateDiff":timestring,"friendica_activities":friendica_activities})}
+ //print("News:"+j+msg.news.length+JSON.stringify(data));
+ msg.model.append(data);}
+ if (j==msg.news.length){
+ msg.model.sync()
+ };
+}
diff --git a/v0.002/Release/source-android/js/photoworker.js b/v0.002/Release/source-android/js/photoworker.js
new file mode 100644
index 0000000..36646c7
--- /dev/null
+++ b/v0.002/Release/source-android/js/photoworker.js
@@ -0,0 +1,15 @@
+WorkerScript.onMessage = function(msg) {
+ if (msg.firstalbum==0){msg.model.clear();}
+ var limit=0; if (msg.albums.length-msg.firstalbum<20){limit=msg.albums.length} else{limit=msg.firstalbum+20}
+ for (var j=msg.firstalbum;j0){
+ for(var i=0;i< AllStoredImages.length;i++){
+ var position=Helperjs.inArray(obj,"resourceID",AllStoredImages[i]);
+ if (position>-1){obj.splice(position,1)}
+ //obj.splice(obj.indexOf(AllStoredImages[i]),1);// list of objects instead of list!!!
+ }}
+ callback(obj)
+ })
+})}
+
+function dataRequest(login,photoID,database,rootwindow) {
+ // check if image exist and call download function
+ Helperjs.friendicaRequest(login,"/api/friendica/photo?photo_id="+photoID, rootwindow, function (image){
+ try{ if(image==""){currentimageno=currentimageno+1}else{
+ var obj = JSON.parse(image);
+ var helpfilename=obj.filename.substring(0,obj.filename.lastIndexOf("."));
+ var filesuffix="";
+ if (obj.type=="image/jpeg"){filesuffix=".jpg"}
+ else if (obj.type=="image/png"){filesuffix=".png"}
+ else {filesuffix=""}
+
+ if (helpfilename==""){// check if file has any filename
+ obj.filename=obj["id"]+filesuffix;
+ }
+ else{obj.filename=helpfilename+filesuffix}
+
+ var link="";
+ if(obj["link"][0]){link=obj["link"][0]} else{link=obj["link"]["4"]}
+ xhr.setUrl(Qt.resolvedUrl(link));
+ xhr.setFilename(login.imagestore+'albums/'+obj.album+"/"+obj["filename"]);
+ xhr.setDownloadtype("picture");
+ xhr.download();
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ db.transaction( function(tx) {
+ var result = tx.executeSql('SELECT * from imageData where id = "'+obj["id"]+'"');
+ if(result.rows.length === 1) {// use update
+ result = tx.executeSql('UPDATE imageData SET username ="' +login.username+ '",id="'+obj.id+'", created="'+obj.created+'", edited="'+obj.edited+'", profile="'+obj.profile+'", link="'+obj["link"]["4"]+'", filename="'+obj.filename+'",title="'+obj.title+'", desc="'+obj.desc+'", type="'+obj.type+'", width="'+obj.width+'", height="'+obj.height+'", album="'+obj.album+'", location="file://'+login.imagestore+'albums/'+obj.album+'/" where id="'+obj["id"]+'"');
+ } else {// use insert print('... does not exists, create it')
+ result = tx.executeSql('INSERT INTO imageData VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)', [login.username,obj.id,obj.created,obj.edited, obj.title, obj.desc, obj.album, obj.filename, obj.type, obj.height, obj.width,obj. profile,obj["link"]["4"],'file://'+login.imagestore+'albums/'+obj.album+"/"]);
+ }
+ })}}
+ catch (e){print("Data retrieval failure! "+ e+obj);}
+})}
+
+
+function deleteImageData(database,user,field,selection,callback) { // does nothing useful at the moment
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ //print(' delete Image Data() for ' + field +"="+selection)
+ db.transaction( function(tx) {
+ result = tx.executeSql('UPDATE imageData SET data="" where '+ field +'="'+selection+'"');
+ callback(result)})
+}
+
+function requestFriendsAlbumPictures(login,friend,rootwindow,callback){
+// screenscraping of albums page of contact without user and password
+ Helperjs.friendicaWebRequest(friend.url.replace("profile","photos"),rootwindow,function(photohtml){
+ //print(photohtml);
+ var photoarray=[];
+ var arr = photohtml.split("sidebar-photos-albums-li");
+ for (var i=2;i')-1);
+ var album={'link':albumlink,'name':albumname}
+ photoarray.push(album);
+ }
+ callback(photoarray)
+ })
+}
+
+function requestFriendsPictures(link,rootwindow,callback){
+// screenscraping of pictures page for given album
+ Helperjs.friendicaWebRequest(link,rootwindow,function(photohtml){
+ var photoarray=[];
+ var basehtml=photohtml.substring(photohtml.indexOf('',photohtml.indexOf('-1){ //theme 1
+ var arr = photohtml.split("photo-album-image-wrapper-end");}
+
+// other themes
+ if (photohtml.indexOf("photo-album-wrapper")>-1){ //theme 2
+ var photoarea=photohtml.substring(photohtml.indexOf("photo-album-wrapper"),photohtml.indexOf("photo-album-end"))
+ var arr = photoarea.split("");}
+
+ for (var i=0;i0){
+ for(var i = 0; i < rs.rows.length; i++) {
+ rsArray.push(rs.rows.item(i))
+ }
+ var rsObject={server:rsArray[0].server,username:rsArray[0].username, password:rsArray[0].password,imagestore:rsArray[0].imagestore,maxnews:rsArray[0].maxnews,isActive:rsArray[0].isActive,timerInterval:rsArray[0].timerInterval, newsViewType:rsArray[0].newsViewType,permissions:JSON.parse(rsArray[0].permissions),maxContactAge:rsArray[0].maxContactAge,APIVersion:rsArray[0].APIVersion};
+ } else {var rsObject=""}
+ callback(rsObject)}}
+ )
+}
+
+function readActiveConfig(database){
+ var obj;
+ readConfig(database,function(config){obj=config},"isActive", 0);
+ return obj;
+}
+
+function deleteConfig(database,userobj,callback) { // delete user data from DB
+ if (userobj){var where = " WHERE username='"+ userobj.username+"' and server='"+userobj.server+"'";} else { return "no user selected!";}
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ if(!db) { return; }
+ db.transaction( function(tx) {
+ var rs = tx.executeSql('delete from config'+where);
+ callback(rs);
+ })
+}
+
+function cleanNews(database,callback){
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ db.transaction( function(tx) {
+ var maxnewsrs = tx.executeSql("SELECT DISTINCT maxnews FROM config");
+ var maxnews=maxnewsrs.rows.item(0).maxnews;
+ var newscountrs = tx.executeSql('SELECT COUNT(*) from news');
+ var newscount = newscountrs.rows.item(0)["COUNT(*)"];
+ if (newscount>maxnews){var lastvalidtimers= tx.executeSql('select created_at from news ORDER BY created_at DESC LIMIT ' +(newscount-maxnews));
+ var lastvalidtime=lastvalidtimers.rows.item(maxnews).created_at;
+ var deleters = tx.executeSql('DELETE from news WHERE created_at<='+lastvalidtime)}
+ callback()
+ })
+ }
+
+function processNews(callback){
+ Newsjs.getCurrentContacts(login,db,function(contacts){
+ contactlist=contacts});
+
+ if (contactLoadType=="news"){
+ if(root.news.length==0){}
+ else{// show news
+ Newsjs.storeNews(login,db,news,root,function(dbnews){
+ root.newsSignal(dbnews);
+ newstab.newstabstatus=login.newsViewType
+ })}
+ }
+ else if (contactLoadType=="friends"){// show friends
+ root.friendsSignal(login.username);
+ Newsjs.getCurrentContacts(login,db,function(contacts){
+ contactlist=contacts;
+ })}
+
+ else if (contactLoadType=="conversation"){
+ var conversationid=news[0].statusnet_conversation_id
+ if (!isNaN(parseInt(conversationid))){//no directmessage conversation
+ Newsjs.storeNews(root.login,root.db,news,root,function(){
+ Newsjs.conversationfromdb(db,root.login.username,conversationid, function(newsarray){
+ newstab.conversation=newsarray;
+ })
+ })}
+ else {newstab.conversation=news}//only DM conversations from database
+ }
+ else if (contactLoadType=="favorites"){//show favorited news
+ Newsjs.storeNews(root.login,root.db,news,root,function(){
+ Newsjs.favoritesfromdb(db,login.username,function(newsarray){
+ root.newsSignal(newsarray);
+ newstab.newstabstatus="Favorites";
+ })
+ })}
+ callback()
+}
+
+function updateContactInDB(login,database,isFriend,contact){// for newstab and friendstab
+ var suffix=contact.profile_image_url.substring(contact.profile_image_url.lastIndexOf("."), contact.profile_image_url.length);
+ var imagename=login.imagestore+"contacts/"+contact.screen_name.trim()+suffix;
+ contacttimer.restart();
+ var currentTime=Date.now();
+ xhr.setUrl(Qt.resolvedUrl(contact.profile_image_url));
+ xhr.setFilename(imagename);
+ xhr.setDownloadtype("contact");
+ xhr.download();
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ var result;
+ db.transaction( function(tx) {
+ result = tx.executeSql('SELECT * from contacts where username="'+root.login.username+'" AND id = '+contact.id); // check for news id
+ if(result.rows.length === 1) {// use update
+ result = tx.executeSql('UPDATE contacts SET username="'+login.username+'", id='+contact.id+', name="'+Qt.btoa(contact.name)+'", screen_name="'+contact.screen_name+'", location="'+contact.location+'",imageAge='+currentTime+', profile_image_url="'+contact.profile_image_url+'", description="'+Qt.btoa(contact.description)+'", profile_image="'+imagename+'", url="'+contact.url+'" , protected="'+contact.protected+'", followers_count='+contact.followers_count+', friends_count='+contact.friends_count+', created_at="'+ Date.parse(Newsjs.cleanDate(contact.created_at))+'", favourites_count="'+contact.favorites_count+'", utc_offset="'+contact.utc_offset+'", time_zone="'+contact.time_zone+'", statuses_count='+contact.statuses_count+', following="'+contact.following+'", verified ="'+contact.verified+'", statusnet_blocking="'+contact.statusnet_blocking+'", notifications="'+contact.notifictions+'", statusnet_profile_url="'+contact.statusnet_profile_url+'", cid='+contact.cid+', network="'+contact.network+'", isFriend='+isFriend+' where username="'+root.login.username+'" AND id='+contact.id);
+ } else {// use insert
+ result = tx.executeSql('INSERT INTO contacts VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', [login.username,contact.id,Qt.btoa(contact.name),contact.screen_name,contact.location,currentTime,contact.profile_image_url, Qt.btoa(contact.description),imagename,contact.url,contact.protected,contact.followers_count, contact.friends_count,Date.parse(Newsjs.cleanDate(contact.created_at)),contact.favorites_count,contact.utc_offset,contact.time_zone,contact.statuses_count,contact.following,contact.verfied,contact.statusnet_blocking,contact.notifications,contact.statusnet_profile_url,contact.cid,contact.network,isFriend]);}
+ });
+}
diff --git a/v0.002/Release/source-android/qml/configqml/ConfigTab.qml b/v0.002/Release/source-android/qml/configqml/ConfigTab.qml
new file mode 100644
index 0000000..c7dd2a3
--- /dev/null
+++ b/v0.002/Release/source-android/qml/configqml/ConfigTab.qml
@@ -0,0 +1,314 @@
+import QtQuick 2.0
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls 1.2
+import "qrc:/js/service.js" as Service
+import "qrc:/js/layout.js" as Layoutjs
+import "qrc:/js/helper.js" as Helperjs
+import "qrc:/qml/configqml"
+import "qrc:/qml/genericqml"
+
+StackView{
+ id: configStack
+ anchors.fill:parent
+ initialItem: Flickable{
+ width:root.width-5*mm
+ height:root.height-12*mm
+ contentHeight: configBackground.height
+ boundsBehavior: Flickable.StopAtBounds
+ Rectangle{
+ id:configBackground
+ color: "grey"
+ width:parent.width
+ height:Math.max(90*mm,root.height-12*mm)
+ property var users:[]
+ BlueButton{
+ id:userButton
+ text:qsTr("User")
+ y:mm
+ width: root.width/2
+ onClicked:{
+ var useritems="";
+ for (var i=0;iFriendiqa v0.002
Licensed under GPL 3
"+
+ "Profile https://freunde.ma-nic.de/profile/friendiqa
"+
+ "Sourcecode: https://github.com/LubuWest/Friendica
"+
+ "C++ code by Fabio
"+
+ "QML and Javascript code by Marco"
+ onLinkActivated:{
+ Qt.openUrlExternally(link)}
+ }
+ BlueButton{
+ text:qsTr("Close")
+ onClicked:{configStack.pop()}
+ anchors.top:infoBoxText.bottom
+ }
+ }
diff --git a/v0.002/Release/source-android/qml/contactqml/ContactComponent.qml b/v0.002/Release/source-android/qml/contactqml/ContactComponent.qml
new file mode 100644
index 0000000..26432bc
--- /dev/null
+++ b/v0.002/Release/source-android/qml/contactqml/ContactComponent.qml
@@ -0,0 +1,67 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.3
+import "qrc:/js/layout.js" as Layoutjs
+import "qrc:/qml/genericqml"
+
+Item {
+id: contactComponent
+property var createdAtDate: new Date(contact.created_at)
+property var linkUrl: contact.network!=="dfrn"?contact.url:contact.url.replace("profile","dfrn_request")
+Rectangle {
+ id: wrapper
+ width: 16*mm
+ height: 15*mm
+ border.color: "grey"
+ color:"white"
+ Image {
+ id: photoImage
+ x:1
+ y:1
+ width: 10*mm
+ height:10*mm
+ source:(contact.profile_image!="")? "file://"+contact.profile_image : contact.profile_image_url
+ onStatusChanged: if (photoImage.status == Image.Error) {source="qrc:/images/defaultcontact.jpg"}
+ }
+
+ Label {
+ id: namelabel
+ x: 1
+ width: wrapper.width-4
+ height: 3*mm
+ text: contact.screen_name
+ elide:Text.ElideRight
+ anchors.topMargin: 0
+ anchors.left: photoImage.left
+ color: "#303030"
+ font.pixelSize: 3*mm
+ anchors.top: photoImage.bottom
+ }
+ BlueButton{
+ id:infobutton
+ width: 5*mm
+ height: 5*mm
+ color:"transparent"
+ text:"?"
+ anchors.left: photoImage.right
+ anchors.leftMargin: 3
+ anchors.topMargin: 3
+ anchors.top: parent.top
+ onClicked:{
+ contactComponent.state="large";
+ var component = Qt.createComponent("qrc:/qml/contactqml/ContactDetailsComponent.qml");
+ if (component.status== Component.Ready){
+ var contactDetails = component.createObject(wrapper,{"contact": contact})}
+ }
+ }
+ }
+states: [
+ State {
+ name: "large"
+ PropertyChanges { target: namelabel; font.pixelSize: 4*mm; width:friendsTabView.width-4*mm; text:Qt.atob(contact.name)+" (@"+contact.screen_name+")"}
+ PropertyChanges { target: contactComponent; z: 2 }
+ PropertyChanges { target: wrapper; width:friendsTabView.width-3*mm;height:friendsTabView.height-10*mm}
+ PropertyChanges { target: photoImage; width:15*mm;height:15*mm }
+ PropertyChanges { target:contactComponent.GridView.view;contentY:contactComponent.y;contentX:contactComponent.x;interactive:false}
+ }
+]
+}
diff --git a/v0.002/Release/source-android/qml/contactqml/ContactDetailsComponent.qml b/v0.002/Release/source-android/qml/contactqml/ContactDetailsComponent.qml
new file mode 100644
index 0000000..da82738
--- /dev/null
+++ b/v0.002/Release/source-android/qml/contactqml/ContactDetailsComponent.qml
@@ -0,0 +1,81 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.3
+import "qrc:/qml/genericqml"
+
+Rectangle{
+ id: detailsrectangle
+ anchors.top: namelabel.bottom
+ anchors.topMargin: 2*mm
+ //opacity: 0
+
+ ScrollView{
+ horizontalScrollBarPolicy:Qt.ScrollBarAlwaysOff
+ frameVisible: true
+ //Flickable{
+ id:namelabelflickable
+ width: root.width-10*mm
+ height:friendsTabView.height-45*mm
+ //boundsBehavior:Flickable.StopAtBounds
+ //flickableDirection:Flickable.VerticalFlick
+ //contentWidth:width
+ //contentHeight: namelabeltext.height
+ clip:true
+ Text{
+ id:namelabeltext
+ width: namelabelflickable.width
+ height: implicitHeight
+ font.pixelSize: 3*mm
+ textFormat:Text.RichText
+ wrapMode: Text.Wrap
+ text:""+qsTr("Description")+": "+Qt.atob(contact.description)+"
"+qsTr("Location")+": "+contact.location+"
"+qsTr("Posts")+": "+contact.statuses_count+
+ "
"+qsTr("URL")+": "+linkUrl+"
"+
+ qsTr("Created at")+": "+createdAtDate.toLocaleString(Qt.locale())
+ onLinkActivated: {
+ Qt.openUrlExternally(link)}
+ }
+ }
+
+ Row{
+ anchors.top: namelabelflickable.bottom
+ anchors.topMargin: 2*mm
+ spacing:4
+
+ BlueButton{
+ id:photobutton
+ text:"Photos"
+ visible:contact.network=="dfrn"? 1:0
+ onClicked:{contactComponent.state="";detailsrectangle.destroy();
+ root.currentIndex=2;
+ fotostab.active=true;
+ root.fotoSignal(contact) ;
+ }
+ }
+
+ BlueButton{
+ id:messagebutton
+ text:"Messages"
+ onClicked:{contactComponent.state="";detailsrectangle.destroy();
+ root.currentIndex=0;
+ newstab.active=true;
+ root.messageSignal(contact.id) ;
+ }
+ }
+
+ BlueButton{
+ id:dmbutton
+ visible: contact.following=="true"?true:false
+ text: "DM"
+ onClicked:{contactComponent.state="";detailsrectangle.destroy();
+ root.currentIndex=0;
+ newstab.active=true;
+ root.directmessageSignal(contact.screen_name);
+ }
+ }
+
+ BlueButton{
+ id: closeButton
+ text: "close"
+ onClicked:{contactComponent.state="";detailsrectangle.destroy()}
+ }
+ }
+ }
diff --git a/v0.002/Release/source-android/qml/contactqml/FriendsTab.qml b/v0.002/Release/source-android/qml/contactqml/FriendsTab.qml
new file mode 100644
index 0000000..c2e3521
--- /dev/null
+++ b/v0.002/Release/source-android/qml/contactqml/FriendsTab.qml
@@ -0,0 +1,197 @@
+import QtQuick 2.0
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls 1.2
+import QtQuick.Controls.Styles 1.4
+import "qrc:/js/service.js" as Service
+import "qrc:/js/helper.js" as Helperjs
+import "qrc:/js/news.js" as Newsjs
+import "qrc:/qml/contactqml"
+import "qrc:/qml/genericqml"
+
+Rectangle {
+ y:1
+ color: "white"
+ TabView{
+ id:friendsTabView
+ tabPosition: Qt.TopEdge
+ x:mm
+ y:mm
+ width: root.width-2*mm
+ height: root.height-10*mm
+ currentIndex: 0
+
+ signal contactsSignal(var username)
+ signal groupsSignal(var username)
+ onCurrentIndexChanged:{
+ if (currentIndex==0){root.friendsSignal(root.login.username)}
+ else if (currentIndex==1){contactsSignal(root.login.username)}
+ else if (currentIndex==2){groupsSignal(root.login.username)}
+ }
+ style: TabViewStyle {
+ frameOverlap: 1
+ tab: Rectangle {
+ color: "white"
+ //border.color: "light grey"
+ implicitWidth: root.width/3-2*mm
+ implicitHeight: 4*mm
+ Text { id: text
+ anchors.centerIn: parent
+ text: styleData.title
+ color: "dark grey"
+ font.pixelSize:2.5*mm
+ font.bold: styleData.selected
+ }
+ }
+ frame: Rectangle { color: "light grey" }
+ tabsAlignment:Qt.AlignHCenter
+ }
+
+ Tab{
+ title: qsTr("Friends")
+ Rectangle{
+ id: friendsGridTab
+ function showFriends(username){
+ try {friendsModel.clear()} catch(e){print(e)};
+ Helperjs.readData(db,"contacts",username,function(friendsobject){
+ for (var i=0;i0){// download first contact image and update db
+ Service.updateContactInDB(login,db,newContacts[currentContact].isFriend,newContacts[currentContact])}
+ else if (contactLoadType!=""){
+ Service.processNews(function(){
+ root.contactLoadType="";
+ root.news=[];
+ })}
+ }
+
+ onCurrentContactChanged:{// download next contact image after photoplaceholder is finished saving and update db
+ if(currentContact0) {xhr.setParam("group_allow", Helperjs.cleanArray(group_allow))};
+ if (group_deny.length>0) {xhr.setParam("group_deny", Helperjs.cleanArray(group_deny))};
+ if (contact_allow.length>0) {xhr.setParam("contact_allow", Helperjs.cleanArray(contact_allow))};
+ if (contact_deny.length>0) {xhr.setParam("contact_deny", Helperjs.cleanArray(contact_deny))};
+ if (attachImageURL!=="") {xhr.setImageFileParam("media", attachImageURL )};
+ xhr.post();
+ }
+
+ function dmUpdate(title,text,replyto,screen_name,attachImageURL) {
+ xhr.url= login.server + "/api/direct_messages/new.xml";
+ xhr.setLogin(login.username+":"+Qt.atob(login.password));
+ xhr.clearParams();
+ xhr.setParam("text", text);
+ xhr.setParam("screen_name", screen_name);
+ if (parentId!="") {xhr.setParam("replyto", replyto)};
+ if (title!=="") {xhr.setParam("title", title)};
+ xhr.post();
+ }
+
+ Column {
+ id:messageColumn
+ spacing: 2
+ width: parent.width
+ TextField {
+ id: titleField
+ width: parent.width
+ placeholderText: qsTr("Title (optional)")
+ visible: messageSend.parentId === ""
+ }
+
+ TextArea {
+ id: bodyField
+ width: parent.width
+ height: 30*mm
+ wrapMode: TextEdit.Wrap
+ textFormat: TextEdit.PlainText
+ }
+
+ Row{
+ spacing: 2
+ CheckBox{
+ id:dmCheckbox
+ text:"DM"
+ enabled: false
+ checked: (directmessage==1)?true:false
+ onClicked:{
+ if(dmCheckbox.checkedState==Qt.Checked){directmessage=1}
+ else if(dmCheckbox.checkedState==Qt.Unchecked){directmessage=0}
+ }
+ }
+
+ BlueButton{
+ text:qsTr("Url")
+ onClicked: {
+ if(bodyField.selectedText==""){Helperjs.showMessage("Error","No text selected",messageSend)}
+ else{urlTextEdit.text="";
+ urlRectangle.visible=true}}
+ }
+ Rectangle{
+ id:urlRectangle
+ height:parent.height
+ width:37*mm
+ visible:false
+ TextField{
+ id:urlTextEdit
+ width:30*mm
+ height:parent.height
+ }
+ BlueButton{
+ anchors.left:urlTextEdit.right
+ anchors.leftMargin:mm
+ text:qsTr("\u2713")
+ onClicked: {if(urlTextEdit.text!=""){
+ var start = bodyField.selectionStart;
+ var end = bodyField.selectionEnd;
+ var text = bodyField.getText(start,end);
+ text = "[url="+urlTextEdit.text+"]" + text + "[/url]";
+ bodyField.remove(start,end);
+ bodyField.insert(start,text);}
+ urlRectangle.visible=false}
+ }
+ }
+ }
+
+
+ Row{
+ spacing:2
+ BlueButton{
+ visible: (directmessage==1)?false:true
+ text:qsTr("Permissions")
+ onClicked: {
+ var component = Qt.createComponent("qrc:/qml/newsqml/PermissionDialog.qml");
+ var permissions = component.createObject(messageColumn);
+ }}
+ BlueButton {
+ id: attachButton
+ text: qsTr("Attach")
+ visible:(directmessage==0)
+ onClicked: {
+ if (attachImageURL!=""){
+ Helperjs.showMessage( qsTr("Error"),qsTr("Only one attachment. Remove other attachment first!"), messageColumn)}
+ else{
+ try{imageAttachmentObject.destroy()}catch(e){print(e)}
+ imageAttachmentDialog.open()}
+ }
+ }
+ BlueButton{
+ id:contactButton
+ text:qsTr("cc")
+ visible:(directmessage==0)
+ onClicked:{
+ var contactitems="";
+ for (var i=0;i0){
+ print(Qt.atob(root.newContacts[root.currentContact].name))
+ }
+ }
+ }
+ function showNews(newsToShow){
+ if (newsStack.depth>1){newsStack.pop()}
+ newsBusy.running=false;
+ var currentTime= new Date();
+ var msg = {'currentTime': currentTime, 'model': newsModel,'news':newsToShow};
+ newsWorker.sendMessage(msg);
+ }
+
+ function onFriendsMessages(friend){
+ newstab.newstabstatus="Contact";
+ Newsjs.newsfromdb(db,root.login.username, function(dbnews){showNews(dbnews)},friend)
+ }
+
+ function onDirectMessage(friend){
+ newstab.newstabstatus="SendMessage"
+ newsStack.push({item:"qrc:/qml/newsqml/MessageSend.qml",properties:{"reply_to_user": friend,"directmessage":1,"login":root.login}});
+ }
+
+
+
+ StackView{
+ id: newsStack
+ anchors.fill:parent
+
+ initialItem:Rectangle {
+ y:1
+ color: "white"
+ width:root.width-2*mm
+ height:root.height-8*mm
+
+ BlueButton{
+ id:newstabstatusButton
+ anchors.top: parent.top
+ anchors.topMargin: 0.5*mm
+ text: qsTr(newstab.newstabstatus)
+ onClicked: {newstabmenu.popup()}
+ }
+
+ Row{
+ spacing: mm
+ anchors.top: parent.top
+ anchors.topMargin: 0.5*mm
+ anchors.right: parent.right
+
+ BlueButton {
+ id: newMessageButton
+ width:10*mm
+ text: qsTr("+")
+ onClicked: {
+ var groups=[];
+ Helperjs.readData(root.db,"groups",root.login.username,function(groupobject){
+ groups=groupobject});
+ newstab.newstabstatus="SendMessage"
+ Helperjs.readData(root.db,"contacts",root.login.username,function(friends){
+ newsStack.push({item:"qrc:/qml/newsqml/MessageSend.qml",properties:{"contacts": friends,"login":root.login}})
+ },"isFriend",1);
+ }
+ }
+ BlueButton {
+ id: quitButton
+ width:10*mm
+ text: qsTr("Quit")
+ onClicked: {Service.cleanNews(root.db,function(){Qt.quit() })}
+ }
+ BlueButton {
+ id: update
+ text: "Update"
+ onClicked: {
+ newsBusy.running=true;
+ newstab.newstabstatus=login.newsViewType;
+ root.contactLoadType="news";
+ var onlynew=true;
+ Newsjs.getFriendsTimeline(login,db,contactlist,onlynew,newstab,function(ns,nc){
+ root.news=ns;root.newContacts=nc;root.currentContact=0;
+ if (ns.length==0){// update last 20 existing news for changes and likes
+ onlynew=false;
+ Newsjs.getFriendsTimeline(login,db,contactlist,onlynew,newstab,function(rns,rnc){
+ root.contactLoadType="news";
+ root.news=rns;root.newContacts=rnc;root.currentContact=0})
+ }
+ })}
+ }
+
+ }
+ Component { id:footerComponent
+ Rectangle{
+ border.color: "#EEEEEE"
+ border.width: 1
+ width:parent.width
+ height:6*mm
+ Text{
+ font.pixelSize: 1.5*mm
+ anchors.centerIn: parent
+ text:qsTr("More")
+ }
+ MouseArea{anchors.fill:parent
+ onClicked:{
+ var currentTime= new Date();
+ if(newstab.newstabstatus=="Timeline"){
+ var lastnews_id=newsModel.get(newsModel.count-1).newsitemobject.created_at;
+ Newsjs.newsfromdb(root.db,root.login.username, function(news){
+ var msg = {'currentTime': currentTime, 'model': newsModel,'news':news,'appendnews':true};
+ newsWorker.sendMessage(msg);
+ },false,lastnews_id)}
+ if(newstab.newstabstatus=="Tree"){
+ var lastnews_id=newsModel.get(newsModel.count-1).newsitemobject.created_at;
+ Newsjs.chatsfromdb(root.db,root.login.username, function(news){
+ var msg = {'currentTime': currentTime, 'model': newsModel,'news':news,'appendnews':true};
+ newsWorker.sendMessage(msg);
+ },lastnews_id)}
+ else if(newstab.newstabstatus=="Contact"){
+ Newsjs.newsfromdb(root.db,root.login.username, function(news){
+ var msg = {'currentTime': currentTime, 'model': newsModel,'news':news,'appendnews':true};
+ newsWorker.sendMessage(msg);
+ },newsModel.get(newsModel.count-1).newsitemobject.uid,newsModel.get(newsModel.count-1).newsitemobject.created_at)}
+ }}
+ }
+ }
+ ListView {
+ id: newsView
+ anchors.fill: parent
+ anchors.topMargin: 8*root.mm
+ anchors.leftMargin: 3*root.mm; anchors.rightMargin: root.mm
+ anchors.bottomMargin: 1*root.mm
+ clip: true
+ spacing: 0
+ footer: footerComponent
+ model: newsModel
+ delegate: Newsitem{}
+ }
+
+ ListModel{id: newsModel}
+
+ WorkerScript {
+ id: newsWorker
+ source: "qrc:/js/newsworker.js"
+ }
+
+ BusyIndicator{
+ id: newsBusy
+ anchors.horizontalCenter: newsView.horizontalCenter
+ anchors.top:newsView.top
+ anchors.topMargin: 2*mm
+ width:10*mm
+ height: 10*mm
+ }
+
+ Menu {
+ id:newstabmenu
+ MenuItem {
+ text: qsTr("Timeline")
+ onTriggered: {
+ newstab.newstabstatus="Timeline";
+ newsModel.clear();
+ Newsjs.newsfromdb(root.db,root.login.username, function(dbnews){
+ showNews(dbnews)
+ })}
+ }
+ MenuItem {
+ text: qsTr("Favorites")
+ onTriggered:{
+ newstab.newstabstatus="Favorites";
+ root.contactLoadType="favorites";
+ newsBusy.running=true;
+ Newsjs.requestFavorites(root.login,db,root.contactlist,root,function(ns,nc){
+ root.news=ns; root.newContacts=nc;root.currentContact=0;
+ })
+ }
+ }
+
+ MenuItem {
+ text: qsTr("Tree")
+ onTriggered:{
+ newsModel.clear();
+ newstab.newstabstatus="Tree";
+ Newsjs.chatsfromdb(db,root.login.username,function(news){showNews(news)})
+ }
+ }
+ MenuItem {
+ text: qsTr("Notifications")
+ onTriggered:{
+ newstab.newstabstatus="Notifications";
+ newsBusy.running=true;
+ Newsjs.getNotifications(root.login,db,root,function(news){
+ showNews(news)}
+ )}
+ }
+ }
+ Component.onCompleted: {
+ root.messageSignal.connect(onFriendsMessages);
+ root.directmessageSignal.connect(onDirectMessage);
+ root.newsSignal.connect(showNews);
+ try{newsModel.clear()} catch(e){}
+ if(login.newsViewType=="Timeline"){Newsjs.newsfromdb(db,login.username,function(dbnews){showNews(dbnews)})}
+ else{Newsjs.chatsfromdb(db,login.username,function(dbnews){showNews(dbnews)})}
+ }
+ }
+ }
+}
diff --git a/v0.002/Release/source-android/qml/newsqml/Newsitem.qml b/v0.002/Release/source-android/qml/newsqml/Newsitem.qml
new file mode 100644
index 0000000..e6a85fa
--- /dev/null
+++ b/v0.002/Release/source-android/qml/newsqml/Newsitem.qml
@@ -0,0 +1,343 @@
+import QtQuick 2.0
+import QtQuick.LocalStorage 2.0
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "qrc:/js/news.js" as Newsjs
+
+
+Item {
+ id: newsitem
+ width: newsView.width
+ height:Math.max((itemMessage.height+topFlow.height+friendicaActivities.height+4*mm),profileImage.height+user_name.height+mm)
+
+ property string conversation_id: ""
+ property string attending: ""
+ onAttendingChanged: {attendLabel.visible=true;
+ attendLabel.text= qsTr("attending: ")+ qsTr(attending)}
+ signal replyto(string parent_id)
+ function showConversation(){
+ conversationsymbol.color="black";
+ newsBusy.running=true;
+ root.contactLoadType="conversation";
+ //newstabstatus="Conversation";
+
+ if(newsitemobject.messagetype==0){
+ Newsjs.requestConversation(root.login,db,newsitemobject.status_id,root.contactlist,root,function(ns,nc){
+ root.news=ns;root.newContacts=nc;root.currentContact=0;
+ })}
+ else{Newsjs.conversationfromdb(root.db,root.login.username,newsitemobject.statusnet_conversation_id, function(newsarray){
+ root.news=newsarray;root.newContacts=[];root.currentContact=1;
+ })}}
+
+ Rectangle{width:newsitem.width; height: 1; anchors.bottom: newsitem.bottom; color:"light grey"}
+ //MouseArea{
+ // anchors.fill: parent;
+ // enabled: (newstabstatus=="Chats")
+ // onClicked: {showConversation()}
+ //}
+ Rectangle{
+ width:newsitem.width
+ height:newsitem.height-1
+ color: (newsitemobject.messagetype==1)?"#ffe6e6" : "white"
+
+ Column {
+ id: authorcolumn
+ width: 8*mm
+
+ Image {
+ id:profileImage
+ source: (newsitemobject.user.profile_image!="")? "file://"+newsitemobject.user.profile_image : newsitemobject.user.profile_image_url
+ x:1
+ width: 7*mm
+ height: 7*mm
+ MouseArea{
+ anchors.fill: parent
+ onPressAndHold: { newsmenu.popup()}
+ }
+ onStatusChanged: if (profileImage.status == Image.Error) {source="qrc:/images/defaultcontact.jpg"}
+ }
+ Label {
+ id:user_name
+ color: "grey"
+ //height:3.5*mm
+ width:parent.width
+ font.pixelSize: 1.5*mm
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ text: Qt.atob(newsitemobject.user.name)
+ }
+ }
+ Column {
+ id:newscolumn
+ width: newsitem.width-8*mm
+ anchors.left: authorcolumn.right
+
+ Flow{
+ id:topFlow
+ spacing: mm
+ width:parent.width
+ Label {
+ id:messageTypeLabel
+ color: "grey"
+ text: if (newsitemobject.messagetype==0){qsTr("Source: ")+newsitemobject.source
+ } else if (newsitemobject.messagetype==1){ qsTr("Direct Message")} else {" Notification"}
+ font.pixelSize: 1.5*mm
+ }
+ Label {
+ id:createdAtLabel
+ color: "grey"
+ //height:3.5*mm
+ font.pixelSize: 1.5*mm
+ horizontalAlignment: Label.AlignRight
+ text: dateDiff
+ }
+ Label {
+ id:replytoLabel
+ color: "grey"
+ //height:3.5*mm
+ font.pixelSize: 1.5*mm
+ horizontalAlignment: Label.AlignRight
+ text: try {qsTr("In reply to ")+newsitemobject.reply_user.screen_name
+ }catch(e){" "}
+ }
+
+ Label {
+ id:newscountLabel
+ visible:((newstabstatus=="Tree")&&(newsitemobject.newscount>1))?true:false
+ color: "grey"
+ height:3.5*mm
+ font.pixelSize: 1.5*mm
+ font.bold: true
+ horizontalAlignment: Label.AlignRight
+ text: try {(newsitemobject.newscount-1)+qsTr(" comments") }catch(e){" "}
+ MouseArea{
+ anchors.fill:parent
+ onClicked: showConversation()
+ }
+ }
+ }
+
+ Text {
+ color: "#404040"
+ linkColor: "light green"
+ id: itemMessage
+ textFormat: Text.RichText
+ text: Qt.atob(newsitemobject.statusnet_html)
+ width: newsitem.width-8*mm-2
+ height: implicitHeight
+ wrapMode: Text.Wrap
+ onLinkActivated:{
+ Qt.openUrlExternally(link)}
+ }
+
+ Row{id:friendicaActivities
+ spacing:mm
+ Label{color: "grey"
+ font.pixelSize: 1.5*mm
+ text: friendica_activities.likeText
+ }
+ Label{color: "grey"
+ font.pixelSize: 1.5*mm
+ text: friendica_activities.dislikeText
+ }
+ Label{color: "grey"
+ font.pixelSize: 1.5*mm
+ text: friendica_activities.attendyesText
+ }
+ Label{color: "grey"
+ font.pixelSize: 1.5*mm
+ text: friendica_activities.attendnoText
+ }
+ Label{color: "grey"
+ font.pixelSize: 1.5*mm
+ text: friendica_activities.attendmaybeText
+ }
+ }
+ Row {
+ CheckBox{id:likeCheckbox
+ height:3*mm
+ width:8*mm
+ visible: (newsitemobject.messagetype==0)? true:false
+ checked:(friendica_activities.self.liked==1)?true:false
+ style: CheckBoxStyle {
+ background: Rectangle {
+ implicitWidth: 7*mm
+ implicitHeight: 3*mm
+ color:"white"
+ }
+ indicator:
+ Rectangle{
+ implicitWidth: 3*mm
+ implicitHeight:3*mm
+ color:control.checked?"yellow":"white"
+ x: 5*mm
+ Text{
+ font.pixelSize: 2*mm
+ color:"grey"
+ text:":-)"
+ }}
+ }
+ onClicked: {
+ if(likeCheckbox.checked==true){Newsjs.like(root.login,root.db,1,"like",newsitemobject.status_id,root);dislikeCheckbox.checked=false; model.friendica_activities.self.liked=0 }
+ else{Newsjs.like(root.login,root.db,0,"like",newsitemobject.status_id,root); model.friendica_activities.self.liked=1}}
+ }
+ CheckBox{id: dislikeCheckbox
+ height:3*mm
+ width:8*mm
+ visible: (newsitemobject.messagetype==0)? true:false
+ checked: (friendica_activities.self.disliked==1)?true:false
+ style: CheckBoxStyle {
+ background: Rectangle {
+ implicitWidth: 7*mm
+ implicitHeight:3*mm
+ color:"white"
+ }
+ indicator:
+ Rectangle{
+ implicitWidth: 3*mm
+ implicitHeight:3*mm
+ color:control.checked?"yellow":"white"
+ x:5*mm
+ Text{
+ font.pixelSize: 2*mm
+ color:"grey"
+ text:":-("
+ }}
+ }
+ onClicked: {
+ if (dislikeCheckbox.checked==true){Newsjs.like(root.login,root.db,1,"dislike",newsitemobject.status_id,root);likeCheckbox.checked=false; model.friendica_activities.self.disliked=0}
+ else {Newsjs.like(root.login,root.db,0,"dislike",newsitemobject.status_id,root); model.friendica_activities.self.disliked=1}}
+ }
+ CheckBox {
+ id:favoritedCheckbox
+ visible:(newsitemobject.messagetype==0)
+ style: CheckBoxStyle {
+ background: Rectangle {
+ implicitWidth: 6*mm
+ implicitHeight:3*mm
+ color:"transparent"
+ }
+ indicator:
+ Rectangle{x:3*mm
+ width: 3*mm
+ implicitHeight:3*mm
+ Text{
+ anchors.centerIn: parent
+ color:control.checked?"black":"grey"
+ text:"\u2605"
+ }}
+ }
+ checked:(newsitemobject.favorited>0)
+ onClicked:{
+ if(favoritedCheckbox.checkedState==Qt.Checked)
+ {Newsjs.favorite(login,true,newsitemobject.status_id,root); model.newsitemobject.favorited=1}
+ else if(favoritedCheckbox.checkedState==Qt.Unchecked)
+ {Newsjs.favorite(login,false,newsitemobject.status_id,root);model.newsitemobject.favorited=0}
+ }
+ }
+ Rectangle{
+ width: 4*mm
+ height: 3*mm
+ color:"transparent"
+ Text{
+ id:newsmenusymbol
+ color: "grey"
+ anchors.centerIn: parent
+ font.pixelSize: 2*mm
+ font.bold: true
+ text: "\u22EE"
+ }
+ MouseArea{
+ anchors.fill:parent
+ onClicked: {newsmenu.popup()}}
+
+ }
+
+ Rectangle{
+ width: 4*mm
+ height: 3*mm
+ color:"transparent"
+ //visible:(newsitemobject.in_reply_to_status_id!="")?true:false
+ Text{
+ id:conversationsymbol
+ color: "grey"
+ anchors.centerIn: parent
+ font.pixelSize: 2*mm
+ text: "\u21C4"
+ }
+ MouseArea{
+ anchors.fill:parent
+ onClicked: showConversation()
+ }
+ }
+ Label {
+ id:attendLabel
+ visible: false
+ color: "grey"
+ height:3.5*mm
+ font.pixelSize: 1.5*mm
+ horizontalAlignment: Label.AlignRight
+ text: (friendica_activities.self.attending)?qsTr("attending: ")+ qsTr(attending):""
+ }
+ }
+ }
+
+ Menu {
+ id:newsmenu
+ MenuItem {
+ text: qsTr("Reply")
+ onTriggered: {
+ var directmessage=0;
+ if (newsitemobject.messagetype==1){ directmessage=1}
+ newsStack.push({item:"qrc:/qml/newsqml/MessageSend.qml",properties:{"reply_to_user": newsitemobject.user.screen_name,"parentId":newsitemobject.status_id,"login":root.login,"directmessage":directmessage}});
+ }
+ }
+ MenuItem {
+ text: qsTr("DM")
+ onTriggered: {
+ root.directmessageSignal(newsitemobject.user.screen_name);
+ }
+ }
+ MenuItem {
+ text: qsTr("Repost")
+ onTriggered: {
+ Newsjs.retweetNews(root.login,db,newsitemobject.status_id,root,function(reply){
+ print(reply);
+ })
+ }
+ }
+ MenuItem {
+ text: qsTr("Conversation")
+ onTriggered: showConversation()
+ }
+
+ Menu{
+ title: qsTr("Attending")
+ MenuItem{text:qsTr("yes")
+ onTriggered: {Newsjs.attend(root.login,db,"yes",newsitemobject.status_id,root,function(){
+ newsitem.attending="yes";
+ attendLabel.visible=true})}
+ }
+
+ MenuItem{text:qsTr("maybe")
+ onTriggered: {Newsjs.attend(root.login,db,"maybe",newsitemobject.status_id,root,function(){
+ newsitem.attending="maybe"})}
+ }
+
+ MenuItem{text:qsTr("no")
+ onTriggered: {Newsjs.attend(root.login,db,"no",newsitemobject.status_id,root,function(){
+ newsitem.attending="no"})}
+ }
+ }
+
+ MenuItem {
+ text: qsTr("Delete")
+ onTriggered: {
+ Newsjs.deleteNews(root.login,root.db,newsitemobject.status_id,newsitemobject.messagetype,root,function(reply){
+ newsModel.remove(index);
+ })
+ }
+ }
+}
+}
+}
+
diff --git a/v0.002/Release/source-android/qml/newsqml/PermissionDialog.qml b/v0.002/Release/source-android/qml/newsqml/PermissionDialog.qml
new file mode 100644
index 0000000..d7e659a
--- /dev/null
+++ b/v0.002/Release/source-android/qml/newsqml/PermissionDialog.qml
@@ -0,0 +1,191 @@
+import QtQuick 2.0
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls 1.4
+import QtQml.Models 2.1
+import "qrc:/js/service.js" as Service
+import "qrc:/js/helper.js" as Helperjs
+import "qrc:/qml/genericqml"
+
+Rectangle{
+ id:permissionDialog
+ x: mm
+ width: messageColumn.width-5*mm
+ height:root.height/3
+ function updatePerms(){
+ for (var i=0;i-1){contactstatus="positive";print(contacts[name].cid+" pos")}
+ else if (contact_deny.indexOf(contacts[name].cid)>-1){contactstatus="negative"}
+ contactModel.append({"contact":contacts[name],"contactstatus":contactstatus})
+ }},"isFriend",1);
+
+ Helperjs.readData(db,"groups",login.username,function(owngroups){
+ for (var number in owngroups){
+ var groupstatus= "neutral";
+ if (group_allow.indexOf(owngroups[number].gid)>-1){groupstatus="positive"}
+ else if (group_deny.indexOf(owngroups[number].gid)>-1){groupstatus="negative"}
+ groupModel.append({"group":owngroups[number],"groupstatus":groupstatus})
+ }});
+ }
+}
diff --git a/v0.002/Release/source-android/qml/photoqml/PhotoComponent.qml b/v0.002/Release/source-android/qml/photoqml/PhotoComponent.qml
new file mode 100644
index 0000000..9884d71
--- /dev/null
+++ b/v0.002/Release/source-android/qml/photoqml/PhotoComponent.qml
@@ -0,0 +1,99 @@
+import QtQuick 2.0
+//import QtQuick.LocalStorage 2.0
+import QtQuick.Controls 1.2
+
+Package {
+ Item { id: stackItem; Package.name: 'stack'; z: stackItem.PathView.z;width:16.5*mm;height:16.5*mm}
+ Item { id: listItem; Package.name: 'list'; width: root.width-1*mm; height: root.height-8*mm; }
+ Item { id: gridItem; Package.name: 'grid';}
+
+ Item {
+ id: photoWrapper
+ width: 16.5*mm; height: 16.5*mm
+ z: stackItem.PathView.z
+ property string hqphotolink: photoLink
+
+ Rectangle {
+ id: placeHolder
+ color: 'lightblue'; antialiasing: true
+ anchors.fill:parent
+ }
+
+ BusyIndicator { anchors.centerIn: parent; running: realImage.status != Image.Ready }
+ Image {
+ id: realImage;
+ // property string hqphotolink: photoLink
+ width: photoWrapper.width; height: photoWrapper.height
+ antialiasing: true;
+ asynchronous: true
+ cache: false
+ fillMode: Image.PreserveAspectFit;
+ source: imageLocation
+ // onStatusChanged: if (realImage.status == Image.Ready) print(realImage.paintedHeight+"x"+realImage.paintedWidth)
+ }
+ Rectangle{
+ id:phototextRectangle
+ color:"black"
+ z:3
+ opacity: 0.5
+ width:phototext.contentWidth
+ height: phototext.contentHeight
+ anchors.bottom: photoWrapper.bottom
+ }
+ Text {
+ id:phototext
+ z:4
+ text: photoDescription.trim()
+ width:15*mm
+ anchors.bottom: photoWrapper.bottom
+ color: "white"
+ font.pixelSize: 2*mm
+ wrapMode:Text.Wrap
+ }
+ MouseArea {
+ width: realImage.paintedWidth; height: realImage.paintedHeight; anchors.centerIn: realImage
+ onClicked: {
+ if (albumWrapper.state == 'inGrid') {
+ gridItem.GridView.view.currentIndex = index;
+ //print("photoLink"+realImage.photoLink)
+ albumWrapper.state = 'fullscreen'
+ } else {
+ gridItem.GridView.view.currentIndex = index;
+ albumWrapper.state = 'inGrid'
+ }
+ }
+ }
+
+ states: [
+ State {
+ name: 'stacked'; when: albumWrapper.state == ''
+ ParentChange { target: photoWrapper; parent: stackItem; }//x: 1*mm; y: 1*mm }
+ PropertyChanges { target: photoWrapper; opacity: stackItem.PathView.onPath ? 1.0 : 0.0 }
+ PropertyChanges { target: phototext; opacity: 0.0 }
+ PropertyChanges { target: phototextRectangle; opacity: 0.0 }
+ },
+ State {
+ name: 'inGrid'; when: albumWrapper.state == 'inGrid'
+ ParentChange { target: photoWrapper; parent: gridItem; x: 1*mm; y: 1*mm;}
+ PropertyChanges { target: phototext; opacity: 1.0 }
+ PropertyChanges { target: phototextRectangle; opacity: 0.5 }
+ PropertyChanges { target: placeHolder; opacity: 1.0 }
+ },
+ State {
+ name: 'fullscreen'; when: albumWrapper.state == 'fullscreen'
+ ParentChange {
+ target: photoWrapper; parent: listItem; x: 1; y: 1;
+ width: root.width-mm; height: root.height-8*mm
+ }
+ PropertyChanges { target: placeHolder; opacity: 0.0 }
+ PropertyChanges { target: realImage; source: photoWrapper.hqphotolink}
+ PropertyChanges { target: phototext; anchors.bottom: realImage.bottom}
+ PropertyChanges { target: phototext; width:realImage.width }
+ PropertyChanges { target: phototextRectangle; anchors.bottom: realImage.bottom }
+ PropertyChanges { target: realImage; width: Math.min(listItem.width,sourceSize.width);height: Math.min(listItem.height,sourceSize.height) }
+ }
+ ]
+
+ }
+}
+
diff --git a/v0.002/Release/source-android/qml/photoqml/PhotoPlaceholder.qml b/v0.002/Release/source-android/qml/photoqml/PhotoPlaceholder.qml
new file mode 100644
index 0000000..162529c
--- /dev/null
+++ b/v0.002/Release/source-android/qml/photoqml/PhotoPlaceholder.qml
@@ -0,0 +1,21 @@
+import QtQuick 2.0
+import "qrc:/js/helper.js" as Helperjs
+
+Image {
+id:photoPlaceholder
+property string imageName:"x.jpg"
+property string downloadtype:""
+fillMode: Image.PreserveAspectFit
+onStatusChanged: {
+ if (photoPlaceholder.status == Image.Ready) {
+ //print("Source: "+source+"Photo width"+width+" height"+height+" Ratio "+ fillMode);
+ var saveprocess=photoPlaceholder.grabToImage(function(result){
+ var saveresult=result.saveToFile(imageName.toString());
+ if (saveresult){
+ if ((downloadtype=="picture")&&(newImages.length>0)){
+ photoPlaceholder.destroy();
+ currentImageNo=currentImageNo+1
+ }
+S }});
+}}}
+
diff --git a/v0.002/Release/source-android/qml/photoqml/PhotoTab.qml b/v0.002/Release/source-android/qml/photoqml/PhotoTab.qml
new file mode 100644
index 0000000..edada91
--- /dev/null
+++ b/v0.002/Release/source-android/qml/photoqml/PhotoTab.qml
@@ -0,0 +1,181 @@
+import QtQuick 2.0
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls 1.4
+import QtQml.Models 2.1
+import "qrc:/js/service.js" as Service
+import "qrc:/js/helper.js" as Helperjs
+import "qrc:/qml/photoqml"
+import "qrc:/qml/genericqml"
+
+Rectangle {
+ id:fotorectangle
+ property string phototabstatus:"Images"
+ onPhototabstatusChanged:{phototabstatusButton.text=qsTr(phototabstatus)}
+
+ y:1
+ width:root.width-mm
+ height:root.height-5*mm
+ color: '#fff'
+ property var newimages:[]
+ property int currentimageno: 0
+//onLoginChanged:{var msg = {'model': photogroupModel,'albums':[],'firstalbum':0,'foreignPicture':false};
+// photoWorker.sendMessage(msg);
+//}
+ onNewimagesChanged:{
+ if(newimages.length>0){
+ Helperjs.readField("album",root.db,"imageData",root.login.username,function(albums){
+ //print("albums"+JSON.stringify(albums)+JSON.stringify(newimages[currentimageno]));
+ for (var i=0;i
+
+ qml/friendiqa.qml
+ qml/newsqml/NewsTab.qml
+ qml/newsqml/Newsitem.qml
+ qml/newsqml/MessageSend.qml
+ qml/newsqml/PermissionDialog.qml
+ qml/contactqml/FriendsTab.qml
+ qml/contactqml/GroupComponent.qml
+ qml/contactqml/ContactComponent.qml
+ qml/contactqml/ContactDetailsComponent.qml
+ qml/genericqml/BlueButton.qml
+ qml/photoqml/PhotoComponent.qml
+ qml/photoqml/PhotogroupComponent.qml
+ qml/photoqml/PhotoTab.qml
+ qml/configqml/InfoBox.qml
+ qml/configqml/ConfigTab.qml
+ js/layout.js
+ js/photoworker.js
+ js/service.js
+ js/news.js
+ js/newsworker.js
+ js/helper.js
+ images/defaultcontact.jpg
+ qml/newsqml/Conversation.qml
+
+
diff --git a/v0.002/Release/source-linux/common/filesystem.cpp b/v0.002/Release/source-linux/common/filesystem.cpp
new file mode 100644
index 0000000..758f7c8
--- /dev/null
+++ b/v0.002/Release/source-linux/common/filesystem.cpp
@@ -0,0 +1,49 @@
+#include "filesystem.h"
+
+FILESYSTEM *FILESYSTEM::instance()
+{
+ static FILESYSTEM filesystem;
+ return &filesystem;
+}
+
+FILESYSTEM::FILESYSTEM(QObject *parent) : QObject(parent){}
+
+void FILESYSTEM::setDirectory(QString Directory)
+{
+ if (Directory!=m_Directory) {
+ m_Directory = Directory;
+ emit directoryChanged();
+ }
+}
+
+QString FILESYSTEM::Directory() const
+{
+ return m_Directory;
+}
+
+void FILESYSTEM::makeDir(QString name)
+{
+ QDir dir(m_Directory);
+ if (dir.mkdir(name)){
+ emit success(name);
+ }
+ else {emit error(name,1);}
+}
+
+void FILESYSTEM::rmDir()
+{
+ QDir dir(m_Directory);
+ if (dir.removeRecursively()){
+ emit success(m_Directory);
+ }
+ else {emit error(m_Directory,1);}
+}
+
+void FILESYSTEM::rmFile(QString name)
+{
+ QDir dir(m_Directory);
+ if(dir.remove(name)){
+ emit success(name);
+ }
+ else {emit error(name,1);}
+}
diff --git a/v0.002/Release/source-linux/common/filesystem.h b/v0.002/Release/source-linux/common/filesystem.h
new file mode 100644
index 0000000..53d2caf
--- /dev/null
+++ b/v0.002/Release/source-linux/common/filesystem.h
@@ -0,0 +1,33 @@
+#ifndef FILESYSTEM_H
+#define FILESYSTEM_H
+
+#include
+#include
+
+class FILESYSTEM : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString Directory READ Directory WRITE setDirectory NOTIFY directoryChanged)
+
+public:
+ static FILESYSTEM *instance();
+ explicit FILESYSTEM(QObject *parent = 0);
+ void setDirectory(QString Directory);
+ QString Directory() const;
+
+signals:
+ void directoryChanged();
+ void success(QString data);
+ void error(QString data, int code);
+
+public slots:
+ //void setDirectory(QString Directory);
+ void makeDir(QString name);
+ void rmDir();
+ void rmFile(QString name);
+
+private:
+ QString m_Directory;
+};
+
+#endif // FILSYSTEM_H
diff --git a/v0.002/Release/source-linux/common/friendiqa.cpp b/v0.002/Release/source-linux/common/friendiqa.cpp
new file mode 100644
index 0000000..de055f0
--- /dev/null
+++ b/v0.002/Release/source-linux/common/friendiqa.cpp
@@ -0,0 +1,22 @@
+#include
+#include
+#include
+#include "xhr.h"
+#include "filesystem.h"
+
+int main(int argc, char *argv[]) {
+ QApplication app(argc, argv);
+ QQuickView view;
+
+
+ XHR* xhr = XHR::instance();
+ view.rootContext()->setContextProperty("xhr", xhr);
+ FILESYSTEM* filesystem = FILESYSTEM::instance();
+ view.rootContext()->setContextProperty("filesystem", filesystem);
+ view.setSource(QUrl("qrc:/qml/friendiqa.qml"));
+ view.show();
+ view.connect(view.rootContext()->engine(), SIGNAL(quit()), &app, SLOT(quit()));
+ return app.exec();
+
+}
+
diff --git a/v0.002/Release/source-linux/common/uploadableimage.cpp b/v0.002/Release/source-linux/common/uploadableimage.cpp
new file mode 100644
index 0000000..81bea1a
--- /dev/null
+++ b/v0.002/Release/source-linux/common/uploadableimage.cpp
@@ -0,0 +1,90 @@
+#include "uploadableimage.h"
+
+
+#include
+#include
+#include
+#include
+
+void UploadableImage::setSource(const QString &a) {
+ if (a != m_source) {
+ m_source = a;
+ //m_base64 = "";
+ m_mimetype = "";
+ m_filename = "";
+
+ qDebug() << "UploadableImage::setSource : " << m_source;
+
+ if (m_source=="") {
+ emit sourceChanged();
+ //emit base64Changed();
+ emit mimetypeChanged();
+ emit filenameChanged();
+ return;
+ }
+
+ QImage fullimage = QImage(QUrl(m_source).toLocalFile());
+ if (fullimage.width() > 800 || fullimage.height() > 800) {
+ if (fullimage.width() > fullimage.height()) {
+ m_image = fullimage.scaledToWidth(800);
+ } else {
+ m_image = fullimage.scaledToHeight(800);
+ }
+ } else {
+ m_image = fullimage;
+ }
+ qDebug() << "UploadableImage::setSource : " << m_image.width() << "x" << m_image.height();
+ emit sourceChanged();
+
+ QFileInfo fi(m_source);
+
+ m_filename = fi.fileName();
+ emit filenameChanged();
+
+ QString filetype = fi.suffix().toUpper();
+ if (filetype!="PNG" && filetype!="JPG") {
+ filetype = "JPG";
+ }
+ qDebug() << "UploadableImage::setSource : " << "Saving as " << filetype;
+
+ m_mimetype = "image/"+filetype.toLower();
+ emit mimetypeChanged();
+
+ /*
+ QByteArray byteArray;
+ QBuffer buffer(&byteArray);
+ m_image.save(&buffer, filetype.toLatin1().constData());
+ QString b64 = QString::fromLatin1(byteArray.toBase64().data());
+
+ for(int k=0; k
+#include
+#include
+
+class UploadableImage : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
+ //Q_PROPERTY(QString base64 READ base64 NOTIFY base64Changed)
+ Q_PROPERTY(QString filename READ filename NOTIFY filenameChanged)
+ Q_PROPERTY(QString mimetype READ mimetype NOTIFY mimetypeChanged)
+ Q_PROPERTY(QByteArray bytes READ bytes)
+
+public:
+ void setSource(const QString &a);
+ QString source() const;
+
+ //QString base64() const;
+ QString filename() const;
+ QString mimetype() const;
+
+ QByteArray bytes();
+signals:
+ void sourceChanged();
+ //void base64Changed();
+ void filenameChanged();
+ void mimetypeChanged();
+
+private:
+ QString m_source;
+ QImage m_image;
+ //QString m_base64;
+ QString m_filename;
+ QString m_mimetype;
+};
+
+#endif // UPLOADABLEIMAGE_H
diff --git a/v0.002/Release/source-linux/common/xhr.cpp b/v0.002/Release/source-linux/common/xhr.cpp
new file mode 100644
index 0000000..9661273
--- /dev/null
+++ b/v0.002/Release/source-linux/common/xhr.cpp
@@ -0,0 +1,235 @@
+#include "xhr.h"
+
+#include
+#include
+#include
+
+#include "uploadableimage.h"
+
+XHR *XHR::instance()
+{
+ static XHR xhr;
+ return &xhr;
+}
+
+XHR::XHR(QObject *parent) : QObject(parent)
+{
+ request.setSslConfiguration(QSslConfiguration::defaultConfiguration());
+}
+
+void XHR::setUrl(QString url)
+{
+ if (url!=m_url) {
+ m_url = url;
+ emit urlChanged();
+ }
+}
+
+void XHR::setLogin(QString login)
+{
+ if (login!=m_login) {
+ m_login = login;
+ emit loginChanged();
+ }
+}
+
+void XHR::setFilename(QString filename)
+{
+ if (filename!=m_filename) {
+ m_filename = filename;
+ emit filenameChanged();
+ }
+}
+
+void XHR::setDownloadtype(QString downloadtype)
+{
+ if (downloadtype!=m_downloadtype) {
+ m_downloadtype = downloadtype;
+ emit downloadtypeChanged();
+ }
+}
+
+QString XHR::url() const
+{
+ return m_url;
+}
+
+QString XHR::login() const
+{
+ return m_login;
+}
+
+QString XHR::filename() const
+{
+ return m_filename;
+}
+
+QString XHR::downloadtype() const
+{
+ return m_downloadtype;
+}
+
+void XHR::setParam(QString name, QString value)
+{
+ params.insert(name, value);
+}
+
+void XHR::setImageFileParam(QString name, QString url)
+{
+ files.insert(name, url);
+}
+
+void XHR::clearParams()
+{
+ files.clear();
+ params.clear();
+}
+
+void XHR::download()
+{
+ QUrl requrl(m_url);
+ //qDebug()<< "replyerror"<error();
+// qDebug() << "start download of " << requrl;
+ request.setUrl(requrl);
+ reply = manager.get(request);
+// qDebug() << "reply " << reply->header(QNetworkRequest::LocationHeader)<header(QNetworkRequest::LastModifiedHeader);
+ // qDebug() << "request " << request.url();
+ // reply->ignoreSslErrors();
+ connect(reply, &QNetworkReply::readyRead,this, &XHR::onReadyRead);
+ //connect(reply,SIGNAL(downloadProgress(qint64,qint64)), this,SLOT(updateDownloadProgress(qint64,qint64)));
+ connect(reply, &QNetworkReply::finished,this, &XHR::onRequestFinished);
+ //connect(reply, SIGNAL(finished()),this, SLOT(onRequestFinished()));
+ connect(reply, &QNetworkReply::sslErrors, this, &XHR::onSSLError);
+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onReplyError(QNetworkReply::NetworkError)));
+}
+
+void XHR::get()
+{
+ QUrlQuery query;
+
+ QHashIterator i(params);
+ while(i.hasNext()) {
+ i.next();
+ query.addQueryItem(i.key(), i.value());
+ }
+
+ QUrl requrl(m_url);
+ requrl.setQuery(query);
+
+ QByteArray loginData = m_login.toLocal8Bit().toBase64();
+ QString headerData = "Basic " + loginData;
+ request.setRawHeader("Authorization", headerData.toLocal8Bit());
+
+
+ request.setUrl(requrl);
+ reply = manager.get(request);
+
+ connect(reply, &QNetworkReply::finished, this, &XHR::onReplySuccess);
+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onReplyError(QNetworkReply::NetworkError)));
+ connect(reply, &QNetworkReply::readyRead, this, &XHR::onReadyRead);
+ connect(reply, &QNetworkReply::sslErrors, this, &XHR::onSSLError);
+}
+
+void XHR::post()
+{
+ qDebug() << "start post to " << m_url;
+ QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
+
+ QHashIterator iparams(params);
+ while(iparams.hasNext()) {
+ iparams.next();
+ qDebug() << "\t add param " << iparams.key() << " : " << iparams.value();
+ QHttpPart textPart;
+ textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"" + iparams.key() + "\""));
+
+
+ textPart.setBody(iparams.value().toUtf8());
+ multiPart->append(textPart);
+ }
+
+ UploadableImage uimg;
+ QHashIterator ifiles(files);
+ while(ifiles.hasNext()) {
+ ifiles.next();
+
+ uimg.setSource(ifiles.value());
+ qDebug() << "\t image: " << uimg.mimetype() << ", " << ifiles.key();
+
+ QHttpPart imagePart;
+ imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(uimg.mimetype()));
+ imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"" + ifiles.key() + "\"; filename=\""+uimg.filename()+"\""));
+ imagePart.setBody(uimg.bytes());
+ multiPart->append(imagePart);
+ }
+
+ QByteArray loginData = m_login.toLocal8Bit().toBase64();
+ QString headerData = "Basic " + loginData;
+ request.setRawHeader(QByteArray("Authorization"), headerData.toLocal8Bit());
+
+ request.setUrl(m_url);
+ reply = manager.post(request, multiPart);
+ qDebug() << "\t request sent";
+ connect(reply, &QNetworkReply::finished, this, &XHR::onReplySuccess);
+ connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onReplyError(QNetworkReply::NetworkError)));
+ connect(reply, &QNetworkReply::readyRead, this, &XHR::onReadyRead);
+ connect(reply, &QNetworkReply::sslErrors, this, &XHR::onSSLError);
+ qDebug() << "\t reply signals connected";
+}
+
+void XHR::onReplyError(QNetworkReply::NetworkError code)
+{
+ qDebug() << code;
+ emit this->error( bufferToString(), (int) code);
+ reply->deleteLater();
+}
+
+void XHR::onReplySuccess()
+{
+ qDebug() << "!";
+ emit this->success( bufferToString() );
+ reply->deleteLater();
+}
+
+void XHR::onRequestFinished()
+{
+ // Save the file here
+ //qDebug() << "buffer downloaded "<error(m_downloadtype,1);}
+ else {QFile file(m_filename);
+ file.open(QIODevice::WriteOnly);
+ file.write(buffer);
+ buffer.clear();
+ file.close();
+ //qDebug() << m_url << "File downloaded "<downloaded(m_downloadtype);
+ //reply->deleteLater();
+ }
+}
+
+void XHR::onReadyRead()
+{
+ qDebug() << ".";
+ buffer += reply->readAll();
+}
+
+//void XHR::updateDownloadProgress(qint64 bytesRead, qint64 totalBytes)
+//{
+// qDebug() << "Bytes: " << bytesRead<<" / "< &errors)
+{
+ qDebug() << "XHR::onSSLError :" ;
+ QListIterator ierrs(errors);
+ while(ierrs.hasNext()) {
+ qDebug() << "\t" << ierrs.next().errorString();
+ }
+}
+
+QString XHR::bufferToString()
+{
+ return QTextCodec::codecForName("utf-8")->toUnicode(buffer);
+}
+
+
diff --git a/v0.002/Release/source-linux/common/xhr.h b/v0.002/Release/source-linux/common/xhr.h
new file mode 100644
index 0000000..6e93c6c
--- /dev/null
+++ b/v0.002/Release/source-linux/common/xhr.h
@@ -0,0 +1,73 @@
+#ifndef XHR_H
+#define XHR_H
+
+#include
+#include
+#include
+#include
+
+class XHR : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
+ Q_PROPERTY(QString login READ login WRITE setLogin NOTIFY loginChanged)
+ Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
+ Q_PROPERTY(QString downloadtype READ downloadtype WRITE setDownloadtype NOTIFY downloadtypeChanged)
+
+
+public:
+ static XHR *instance();
+
+ explicit XHR(QObject *parent = 0);
+
+ QString url() const;
+ QString login() const;
+ QString filename() const;
+ QString downloadtype() const;
+
+signals:
+ void urlChanged();
+ void loginChanged();
+ void filenameChanged();
+ void downloadtypeChanged();
+ void downloaded(QString data);
+ void success(QString data);
+ void error(QString data, int code);
+
+public slots:
+ void setUrl(QString url);
+ void setLogin(QString login);
+ void setDownloadtype(QString downloadtype);
+ void setFilename(QString filename);
+ void setParam(QString name, QString value);
+ void setImageFileParam(QString name, QString url);
+ void clearParams();
+ void post();
+ void get();
+ void download();
+
+private slots:
+ void onReplyError(QNetworkReply::NetworkError code);
+ void onReplySuccess();
+ void onRequestFinished();
+ void onReadyRead();
+ void onSSLError(const QList &errors);
+ //void updateDownloadProgress(qint64 bytesRead, qint64 totalBytes);
+
+private:
+ QByteArray buffer;
+ QString m_url;
+ QString m_login;
+ QString m_filename;
+ QString m_downloadtype;
+ QHash params;
+ QHash files;
+
+ QNetworkAccessManager manager;
+ QNetworkRequest request;
+ QNetworkReply *reply;
+
+ QString bufferToString();
+};
+
+#endif // XHR_H
diff --git a/v0.002/Release/source-linux/friendiqa.pro b/v0.002/Release/source-linux/friendiqa.pro
new file mode 100644
index 0000000..31c1b2d
--- /dev/null
+++ b/v0.002/Release/source-linux/friendiqa.pro
@@ -0,0 +1,44 @@
+# NOTICE:
+#
+# Application name defined in TARGET has a corresponding QML filename.
+# If name defined in TARGET is changed, the following needs to be done
+# to match new name:
+# - corresponding QML filename must be changed
+# - desktop icon filename must be changed
+# - desktop filename must be changed
+# - icon definition filename in desktop file must be changed
+# - translation filenames have to be changed
+
+# The name of your application
+TARGET = friendiqa
+CONFIG += debug
+QT += qml quick gui widgets
+
+SOURCES += common/friendiqa.cpp \
+ common/uploadableimage.cpp \
+ common/xhr.cpp \
+ common/filesystem.cpp
+
+RESOURCES = application.qrc
+
+OTHER_FILES += qml/friendiqa.qml \
+ translations/*.ts \
+ qml/*.qml
+ qml/newsqml/*.qml
+ qml/contactqml/*.qml
+ qml/photoqml/*.qml
+ qml/configqml/*.qml
+ js/*.js
+
+# German translation is enabled as an example. If you aren't
+# planning to localize your app, remember to comment out the
+# following TRANSLATIONS line. And also do not forget to
+# modify the localized app name in the the .desktop file.
+TRANSLATIONS += translations/friendiqa-de.ts
+
+HEADERS += \
+ common/uploadableimage.h \
+ common/xhr.h \
+ common/filesystem.h
+
+
diff --git a/v0.002/Release/source-linux/images/defaultcontact.jpg b/v0.002/Release/source-linux/images/defaultcontact.jpg
new file mode 100644
index 0000000..bb7bce2
Binary files /dev/null and b/v0.002/Release/source-linux/images/defaultcontact.jpg differ
diff --git a/v0.002/Release/source-linux/js/friendworker.js b/v0.002/Release/source-linux/js/friendworker.js
new file mode 100644
index 0000000..6b6c5be
--- /dev/null
+++ b/v0.002/Release/source-linux/js/friendworker.js
@@ -0,0 +1,12 @@
+WorkerScript.onMessage = function(msg) {
+ msg.model.clear();
+ for (var j=0;j 0 ) {
+ for (var i in list) { if (list[i][prop] === val) {
+ return i;
+ }
+ }
+ } return -1;
+}
+function cleanArray(array) {
+var arraystring=JSON.stringify(array);
+ arraystring=arraystring.replace(/[\[\]]/g , '');
+return arraystring;
+}
diff --git a/v0.002/Release/source-linux/js/layout.js b/v0.002/Release/source-linux/js/layout.js
new file mode 100644
index 0000000..23815fd
--- /dev/null
+++ b/v0.002/Release/source-linux/js/layout.js
@@ -0,0 +1,44 @@
+function showFriends(db) {
+ Service.readActiveConfig(db,function(login){
+ Service.requestFriends(login.url,login.user,login.password,displayFriends);
+ });
+}
+function displayFriends(obj){
+ for (var i=0; i= c.x)
+ f.contentX = c.x;
+ else if (f.contentX+f.width <= c.x+c.width)
+ f.contentX = c.x+c.width-f.width;
+ if (f.contentY >= c.y)
+ f.contentY = c.y;
+ else if (f.contentY+f.height <= c.y+c.height)
+ f.contentY = c.y+c.height-f.height;
+}
+
+function createObject(objectQml,qmlParameters,parentitem,callback) {
+ var component = Qt.createComponent(objectQml);
+ if (component.status === Component.Ready || component.status === Component.Error)
+ finishCreation(component,qmlParameters,parentitem,callback);
+ else
+ component.statusChanged.connect(finishCreation(qmlParameters));
+}
+
+function finishCreation(component,qmlParameters,parentitem,callback) {
+ if (component.status === Component.Ready) {
+ var createdObject = component.createObject(parentitem, qmlParameters);
+ if (createdObject === null)
+ print("Error creating image"); }
+ else if (component.status === Component.Error)
+ print("Error loading component:"+component.errorString());
+ else {print("created")}
+ //callback(createdObject);
+}
+
diff --git a/v0.002/Release/source-linux/js/news.js b/v0.002/Release/source-linux/js/news.js
new file mode 100644
index 0000000..a23fb58
--- /dev/null
+++ b/v0.002/Release/source-linux/js/news.js
@@ -0,0 +1,399 @@
+.pragma library
+.import QtQuick.LocalStorage 2.0 as Sql
+.import "qrc:/js/helper.js" as Helperjs
+
+function requestFriends(login,database,rootwindow,callback){
+// return array of friends
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ db.transaction( function(tx) {
+ var result = tx.executeSql('UPDATE contacts SET isFriend=0 where username="'+login.username+'"')}); // clean old friends
+ Helperjs.friendicaRequest(login,"/api/statuses/friends", rootwindow,function (obj){
+ var friends=JSON.parse(obj);
+ for (var i=0;i'+lastDate);
+ var result2 = tx.executeSql('SELECT id from contacts WHERE username="'+login.username+'" AND isFriend=0 AND imageAge > '+lastDate);
+ for (var j=0;j0){
+ for (var j=0;j0){
+ for (var k=0;j1){
+ var helpernews=newsrs.rows.item(0);
+ helpernews.newscount=newsrs.rows.length;
+ helpernews=fetchUsersForNews(database,user,helpernews)
+ newsArray.push(helpernews);
+ //}
+}
+ callback(newsArray);
+ })}
+
+function inArray(list, prop, val) {
+ if (list.length > 0 ) {
+ for (var i in list) {if (list[i][prop] == val) {
+ return true;
+ }
+ }
+ } return false;
+}
+
+function cleanDate(date){
+var cleanedDate= date.slice(0,3)+", "+date.slice(8,11)+date.slice(4,7)+date.slice(25,30)+date.slice(10,25);
+return cleanedDate
+}
diff --git a/v0.002/Release/source-linux/js/newsworker.js b/v0.002/Release/source-linux/js/newsworker.js
new file mode 100644
index 0000000..9ae91b6
--- /dev/null
+++ b/v0.002/Release/source-linux/js/newsworker.js
@@ -0,0 +1,63 @@
+WorkerScript.onMessage = function(msg) {
+ if(msg.appendnews!==true){ msg.model.clear()};
+
+ for (var j=0;j0){
+ if (newsitemobject.like.length==1){likeText= Qt.atob(newsitemobject.like[0].name)+" "+ qsTr("likes this.")}
+ else {likeText= newsitemobject.like.length+" "+ qsTr("like this.")}
+ }
+ if (newsitemobject.dislike.length>0){
+ if (newsitemobject.dislike.length==1){dislikeText= QT.atob(newsitemobject.dislike[0].name)+" "+ qsTr("doesn't like this.")}
+ else {dislikeText= newsitemobject.dislike.length+" "+ qsTr("don't like this.")}
+ }
+ if (newsitemobject.attendyes.length>0){
+ if (newsitemobject.attendyes.length==1){attendyesText= Qt.atob(newsitemobject.attendyes[0].name)+" "+ qsTr("will attend.")}
+ else {attendyesText= newsitemobject.attendyes.length+" "+ qsTr("persons will attend.")}
+ }
+ if (newsitemobject.attendno.length>0){
+ if (newsitemobject.attendno.length==1){attendnoText= Qt.atob(newsitemobject.attendno[0].name)+" "+ qsTr("will not attend.")}
+ else {attendnoText= newsitemobject.attendno.length+" "+ qsTr("persons will not attend.")}
+ }
+ if (newsitemobject.attendmaybe.length>0){
+ if (newsitemobject.attendmaybe.length==1){attendmaybeText= Qt.atob(newsitemobject.attendmaybe[0].name)+" "+ qsTr("may attend.")}
+ else {attendmaybeText= newsitemobject.attendmaybe.length+" "+ qsTr("persons may attend.")}
+ }
+ var friendica_activities_self=JSON.parse(newsitemobject.friendica_activities_self);
+ if (friendica_activities_self.indexOf(3)!=-1){self.attending=qsTr("yes")}
+ if (friendica_activities_self.indexOf(4)!=-1){self.attending=qsTr("no")}
+ if (friendica_activities_self.indexOf(5)!=-1){self.attending=qsTr("maybe")}
+ if (friendica_activities_self.indexOf(1)!=-1){self.liked=1}
+ if (friendica_activities_self.indexOf(2)!=-1){self.disliked=1}
+ }
+ var friendica_activities={likeText:likeText,dislikeText:dislikeText,attendyesText:attendyesText,attendnoText:attendnoText,attendmaybeText:attendmaybeText,self:self}
+ //print(JSON.stringify(friendica_activities) ) ;
+ var seconds=(msg.currentTime-newsitemobject.created_at)/1000;
+ var timestring="";
+ if (seconds<60) {timestring=seconds+" "+qsTr("seconds") +" "+qsTr("ago");}
+ else if (seconds<90){timestring=Math.round(seconds/60)+" "+qsTr("minute") +" "+qsTr("ago");}
+ else if (seconds<3600){timestring=Math.round(seconds/60)+" "+qsTr("minutes") +" "+qsTr("ago");}
+ else if (seconds<5400){timestring=Math.round(seconds/3600)+" "+qsTr("hour") +" "+qsTr("ago");}
+ else if (seconds<86400){timestring=Math.round(seconds/3600)+" "+qsTr("hours") +" "+qsTr("ago");}
+ else if (seconds<129600){timestring=Math.round(seconds/86400)+" "+qsTr("day") +" "+qsTr("ago");}
+ else if (seconds<3888000){timestring=Math.round(seconds/86400)+" "+qsTr("days") +" "+qsTr("ago");}
+ else if (seconds<5832000){timestring=Math.round(seconds/3888000)+" "+qsTr("month") +" "+qsTr("ago");}
+ else if (seconds<69984000){timestring=Math.round(seconds/3888000)+" "+qsTr("months") +" "+qsTr("ago");}
+ else {timestring=Math.round(seconds/69984000)+" "+qsTr("years") +" "+qsTr("ago");}
+ var data=({"newsitemobject": newsitemobject,"dateDiff":timestring,"friendica_activities":friendica_activities})}
+ //print("News:"+j+msg.news.length+JSON.stringify(data));
+ msg.model.append(data);}
+ if (j==msg.news.length){
+ msg.model.sync()
+ };
+}
diff --git a/v0.002/Release/source-linux/js/photoworker.js b/v0.002/Release/source-linux/js/photoworker.js
new file mode 100644
index 0000000..36646c7
--- /dev/null
+++ b/v0.002/Release/source-linux/js/photoworker.js
@@ -0,0 +1,15 @@
+WorkerScript.onMessage = function(msg) {
+ if (msg.firstalbum==0){msg.model.clear();}
+ var limit=0; if (msg.albums.length-msg.firstalbum<20){limit=msg.albums.length} else{limit=msg.firstalbum+20}
+ for (var j=msg.firstalbum;j0){
+ for(var i=0;i< AllStoredImages.length;i++){
+ var position=Helperjs.inArray(obj,"resourceID",AllStoredImages[i]);
+ if (position>-1){obj.splice(position,1)}
+ //obj.splice(obj.indexOf(AllStoredImages[i]),1);// list of objects instead of list!!!
+ }}
+ callback(obj)
+ })
+})}
+
+function dataRequest(login,photoID,database,rootwindow) {
+ // check if image exist and call download function
+ Helperjs.friendicaRequest(login,"/api/friendica/photo?photo_id="+photoID, rootwindow, function (image){
+ try{ if(image==""){currentimageno=currentimageno+1}else{
+ var obj = JSON.parse(image);
+ var helpfilename=obj.filename.substring(0,obj.filename.lastIndexOf("."));
+ var filesuffix="";
+ if (obj.type=="image/jpeg"){filesuffix=".jpg"}
+ else if (obj.type=="image/png"){filesuffix=".png"}
+ else {filesuffix=""}
+
+ if (helpfilename==""){// check if file has any filename
+ obj.filename=obj["id"]+filesuffix;
+ }
+ else{obj.filename=helpfilename+filesuffix}
+
+ var link="";
+ if(obj["link"][0]){link=obj["link"][0]} else{link=obj["link"]["4"]}
+ xhr.setUrl(Qt.resolvedUrl(link));
+ xhr.setFilename(login.imagestore+'albums/'+obj.album+"/"+obj["filename"]);
+ xhr.setDownloadtype("picture");
+ xhr.download();
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ db.transaction( function(tx) {
+ var result = tx.executeSql('SELECT * from imageData where id = "'+obj["id"]+'"');
+ if(result.rows.length === 1) {// use update
+ result = tx.executeSql('UPDATE imageData SET username ="' +login.username+ '",id="'+obj.id+'", created="'+obj.created+'", edited="'+obj.edited+'", profile="'+obj.profile+'", link="'+obj["link"]["4"]+'", filename="'+obj.filename+'",title="'+obj.title+'", desc="'+obj.desc+'", type="'+obj.type+'", width="'+obj.width+'", height="'+obj.height+'", album="'+obj.album+'", location="file://'+login.imagestore+'albums/'+obj.album+'/" where id="'+obj["id"]+'"');
+ } else {// use insert print('... does not exists, create it')
+ result = tx.executeSql('INSERT INTO imageData VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)', [login.username,obj.id,obj.created,obj.edited, obj.title, obj.desc, obj.album, obj.filename, obj.type, obj.height, obj.width,obj. profile,obj["link"]["4"],'file://'+login.imagestore+'albums/'+obj.album+"/"]);
+ }
+ })}}
+ catch (e){print("Data retrieval failure! "+ e+obj);}
+})}
+
+
+function deleteImageData(database,user,field,selection,callback) { // does nothing useful at the moment
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ //print(' delete Image Data() for ' + field +"="+selection)
+ db.transaction( function(tx) {
+ result = tx.executeSql('UPDATE imageData SET data="" where '+ field +'="'+selection+'"');
+ callback(result)})
+}
+
+function requestFriendsAlbumPictures(login,friend,rootwindow,callback){
+// screenscraping of albums page of contact without user and password
+ Helperjs.friendicaWebRequest(friend.url.replace("profile","photos"),rootwindow,function(photohtml){
+ //print(photohtml);
+ var photoarray=[];
+ var arr = photohtml.split("sidebar-photos-albums-li");
+ for (var i=2;i')-1);
+ var album={'link':albumlink,'name':albumname}
+ photoarray.push(album);
+ }
+ callback(photoarray)
+ })
+}
+
+function requestFriendsPictures(link,rootwindow,callback){
+// screenscraping of pictures page for given album
+ Helperjs.friendicaWebRequest(link,rootwindow,function(photohtml){
+ var photoarray=[];
+ var basehtml=photohtml.substring(photohtml.indexOf('',photohtml.indexOf('-1){ //theme 1
+ var arr = photohtml.split("photo-album-image-wrapper-end");}
+
+// other themes
+ if (photohtml.indexOf("photo-album-wrapper")>-1){ //theme 2
+ var photoarea=photohtml.substring(photohtml.indexOf("photo-album-wrapper"),photohtml.indexOf("photo-album-end"))
+ var arr = photoarea.split("");}
+
+ for (var i=0;i0){
+ for(var i = 0; i < rs.rows.length; i++) {
+ rsArray.push(rs.rows.item(i))
+ }
+ var rsObject={server:rsArray[0].server,username:rsArray[0].username, password:rsArray[0].password,imagestore:rsArray[0].imagestore,maxnews:rsArray[0].maxnews,isActive:rsArray[0].isActive,timerInterval:rsArray[0].timerInterval, newsViewType:rsArray[0].newsViewType,permissions:JSON.parse(rsArray[0].permissions),maxContactAge:rsArray[0].maxContactAge,APIVersion:rsArray[0].APIVersion};
+ } else {var rsObject=""}
+ callback(rsObject)}}
+ )
+}
+
+function readActiveConfig(database){
+ var obj;
+ readConfig(database,function(config){obj=config},"isActive", 0);
+ return obj;
+}
+
+function deleteConfig(database,userobj,callback) { // delete user data from DB
+ if (userobj){var where = " WHERE username='"+ userobj.username+"' and server='"+userobj.server+"'";} else { return "no user selected!";}
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ if(!db) { return; }
+ db.transaction( function(tx) {
+ var rs = tx.executeSql('delete from config'+where);
+ callback(rs);
+ })
+}
+
+function cleanNews(database,callback){
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ db.transaction( function(tx) {
+ var maxnewsrs = tx.executeSql("SELECT DISTINCT maxnews FROM config");
+ var maxnews=maxnewsrs.rows.item(0).maxnews;
+ var newscountrs = tx.executeSql('SELECT COUNT(*) from news');
+ var newscount = newscountrs.rows.item(0)["COUNT(*)"];
+ if (newscount>maxnews){var lastvalidtimers= tx.executeSql('select created_at from news ORDER BY created_at DESC LIMIT ' +(newscount-maxnews));
+ var lastvalidtime=lastvalidtimers.rows.item(maxnews).created_at;
+ var deleters = tx.executeSql('DELETE from news WHERE created_at<='+lastvalidtime)}
+ callback()
+ })
+ }
+
+function processNews(callback){
+ Newsjs.getCurrentContacts(login,db,function(contacts){
+ contactlist=contacts});
+
+ if (contactLoadType=="news"){
+ if(root.news.length==0){}
+ else{// show news
+ Newsjs.storeNews(login,db,news,root,function(dbnews){
+ root.newsSignal(dbnews);
+ newstab.newstabstatus=login.newsViewType
+ })}
+ }
+ else if (contactLoadType=="friends"){// show friends
+ root.friendsSignal(login.username);
+ Newsjs.getCurrentContacts(login,db,function(contacts){
+ contactlist=contacts;
+ })}
+
+ else if (contactLoadType=="conversation"){
+ var conversationid=news[0].statusnet_conversation_id
+ if (!isNaN(parseInt(conversationid))){//no directmessage conversation
+ Newsjs.storeNews(root.login,root.db,news,root,function(){
+ Newsjs.conversationfromdb(db,root.login.username,conversationid, function(newsarray){
+ newstab.conversation=newsarray;
+ })
+ })}
+ else {newstab.conversation=news}//only DM conversations from database
+ }
+ else if (contactLoadType=="favorites"){//show favorited news
+ Newsjs.storeNews(root.login,root.db,news,root,function(){
+ Newsjs.favoritesfromdb(db,login.username,function(newsarray){
+ root.newsSignal(newsarray);
+ newstab.newstabstatus="Favorites";
+ })
+ })}
+ callback()
+}
+
+function updateContactInDB(login,database,isFriend,contact){// for newstab and friendstab
+ var suffix=contact.profile_image_url.substring(contact.profile_image_url.lastIndexOf("."), contact.profile_image_url.length);
+ var imagename=login.imagestore+"contacts/"+contact.screen_name.trim()+suffix;
+ contacttimer.restart();
+ var currentTime=Date.now();
+ xhr.setUrl(Qt.resolvedUrl(contact.profile_image_url));
+ xhr.setFilename(imagename);
+ xhr.setDownloadtype("contact");
+ xhr.download();
+ var db=Sql.LocalStorage.openDatabaseSync(database[0],database[1],database[2],database[3]);
+ var result;
+ db.transaction( function(tx) {
+ result = tx.executeSql('SELECT * from contacts where username="'+root.login.username+'" AND id = '+contact.id); // check for news id
+ if(result.rows.length === 1) {// use update
+ result = tx.executeSql('UPDATE contacts SET username="'+login.username+'", id='+contact.id+', name="'+Qt.btoa(contact.name)+'", screen_name="'+contact.screen_name+'", location="'+contact.location+'",imageAge='+currentTime+', profile_image_url="'+contact.profile_image_url+'", description="'+Qt.btoa(contact.description)+'", profile_image="'+imagename+'", url="'+contact.url+'" , protected="'+contact.protected+'", followers_count='+contact.followers_count+', friends_count='+contact.friends_count+', created_at="'+ Date.parse(Newsjs.cleanDate(contact.created_at))+'", favourites_count="'+contact.favorites_count+'", utc_offset="'+contact.utc_offset+'", time_zone="'+contact.time_zone+'", statuses_count='+contact.statuses_count+', following="'+contact.following+'", verified ="'+contact.verified+'", statusnet_blocking="'+contact.statusnet_blocking+'", notifications="'+contact.notifictions+'", statusnet_profile_url="'+contact.statusnet_profile_url+'", cid='+contact.cid+', network="'+contact.network+'", isFriend='+isFriend+' where username="'+root.login.username+'" AND id='+contact.id);
+ } else {// use insert
+ result = tx.executeSql('INSERT INTO contacts VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', [login.username,contact.id,Qt.btoa(contact.name),contact.screen_name,contact.location,currentTime,contact.profile_image_url, Qt.btoa(contact.description),imagename,contact.url,contact.protected,contact.followers_count, contact.friends_count,Date.parse(Newsjs.cleanDate(contact.created_at)),contact.favorites_count,contact.utc_offset,contact.time_zone,contact.statuses_count,contact.following,contact.verfied,contact.statusnet_blocking,contact.notifications,contact.statusnet_profile_url,contact.cid,contact.network,isFriend]);}
+ });
+}
diff --git a/v0.002/Release/source-linux/qml/configqml/ConfigTab.qml b/v0.002/Release/source-linux/qml/configqml/ConfigTab.qml
new file mode 100644
index 0000000..c7dd2a3
--- /dev/null
+++ b/v0.002/Release/source-linux/qml/configqml/ConfigTab.qml
@@ -0,0 +1,314 @@
+import QtQuick 2.0
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls 1.2
+import "qrc:/js/service.js" as Service
+import "qrc:/js/layout.js" as Layoutjs
+import "qrc:/js/helper.js" as Helperjs
+import "qrc:/qml/configqml"
+import "qrc:/qml/genericqml"
+
+StackView{
+ id: configStack
+ anchors.fill:parent
+ initialItem: Flickable{
+ width:root.width-5*mm
+ height:root.height-12*mm
+ contentHeight: configBackground.height
+ boundsBehavior: Flickable.StopAtBounds
+ Rectangle{
+ id:configBackground
+ color: "grey"
+ width:parent.width
+ height:Math.max(90*mm,root.height-12*mm)
+ property var users:[]
+ BlueButton{
+ id:userButton
+ text:qsTr("User")
+ y:mm
+ width: root.width/2
+ onClicked:{
+ var useritems="";
+ for (var i=0;iFriendiqa v0.002
Licensed under GPL 3
"+
+ "Profile https://freunde.ma-nic.de/profile/friendiqa
"+
+ "Sourcecode: https://github.com/LubuWest/Friendica
"+
+ "C++ code by Fabio
"+
+ "QML and Javascript code by Marco"
+ onLinkActivated:{
+ Qt.openUrlExternally(link)}
+ }
+ BlueButton{
+ text:qsTr("Close")
+ onClicked:{configStack.pop()}
+ anchors.top:infoBoxText.bottom
+ }
+ }
diff --git a/v0.002/Release/source-linux/qml/contactqml/ContactComponent.qml b/v0.002/Release/source-linux/qml/contactqml/ContactComponent.qml
new file mode 100644
index 0000000..26432bc
--- /dev/null
+++ b/v0.002/Release/source-linux/qml/contactqml/ContactComponent.qml
@@ -0,0 +1,67 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.3
+import "qrc:/js/layout.js" as Layoutjs
+import "qrc:/qml/genericqml"
+
+Item {
+id: contactComponent
+property var createdAtDate: new Date(contact.created_at)
+property var linkUrl: contact.network!=="dfrn"?contact.url:contact.url.replace("profile","dfrn_request")
+Rectangle {
+ id: wrapper
+ width: 16*mm
+ height: 15*mm
+ border.color: "grey"
+ color:"white"
+ Image {
+ id: photoImage
+ x:1
+ y:1
+ width: 10*mm
+ height:10*mm
+ source:(contact.profile_image!="")? "file://"+contact.profile_image : contact.profile_image_url
+ onStatusChanged: if (photoImage.status == Image.Error) {source="qrc:/images/defaultcontact.jpg"}
+ }
+
+ Label {
+ id: namelabel
+ x: 1
+ width: wrapper.width-4
+ height: 3*mm
+ text: contact.screen_name
+ elide:Text.ElideRight
+ anchors.topMargin: 0
+ anchors.left: photoImage.left
+ color: "#303030"
+ font.pixelSize: 3*mm
+ anchors.top: photoImage.bottom
+ }
+ BlueButton{
+ id:infobutton
+ width: 5*mm
+ height: 5*mm
+ color:"transparent"
+ text:"?"
+ anchors.left: photoImage.right
+ anchors.leftMargin: 3
+ anchors.topMargin: 3
+ anchors.top: parent.top
+ onClicked:{
+ contactComponent.state="large";
+ var component = Qt.createComponent("qrc:/qml/contactqml/ContactDetailsComponent.qml");
+ if (component.status== Component.Ready){
+ var contactDetails = component.createObject(wrapper,{"contact": contact})}
+ }
+ }
+ }
+states: [
+ State {
+ name: "large"
+ PropertyChanges { target: namelabel; font.pixelSize: 4*mm; width:friendsTabView.width-4*mm; text:Qt.atob(contact.name)+" (@"+contact.screen_name+")"}
+ PropertyChanges { target: contactComponent; z: 2 }
+ PropertyChanges { target: wrapper; width:friendsTabView.width-3*mm;height:friendsTabView.height-10*mm}
+ PropertyChanges { target: photoImage; width:15*mm;height:15*mm }
+ PropertyChanges { target:contactComponent.GridView.view;contentY:contactComponent.y;contentX:contactComponent.x;interactive:false}
+ }
+]
+}
diff --git a/v0.002/Release/source-linux/qml/contactqml/ContactDetailsComponent.qml b/v0.002/Release/source-linux/qml/contactqml/ContactDetailsComponent.qml
new file mode 100644
index 0000000..da82738
--- /dev/null
+++ b/v0.002/Release/source-linux/qml/contactqml/ContactDetailsComponent.qml
@@ -0,0 +1,81 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.3
+import "qrc:/qml/genericqml"
+
+Rectangle{
+ id: detailsrectangle
+ anchors.top: namelabel.bottom
+ anchors.topMargin: 2*mm
+ //opacity: 0
+
+ ScrollView{
+ horizontalScrollBarPolicy:Qt.ScrollBarAlwaysOff
+ frameVisible: true
+ //Flickable{
+ id:namelabelflickable
+ width: root.width-10*mm
+ height:friendsTabView.height-45*mm
+ //boundsBehavior:Flickable.StopAtBounds
+ //flickableDirection:Flickable.VerticalFlick
+ //contentWidth:width
+ //contentHeight: namelabeltext.height
+ clip:true
+ Text{
+ id:namelabeltext
+ width: namelabelflickable.width
+ height: implicitHeight
+ font.pixelSize: 3*mm
+ textFormat:Text.RichText
+ wrapMode: Text.Wrap
+ text:""+qsTr("Description")+": "+Qt.atob(contact.description)+"
"+qsTr("Location")+": "+contact.location+"
"+qsTr("Posts")+": "+contact.statuses_count+
+ "
"+qsTr("URL")+": "+linkUrl+"
"+
+ qsTr("Created at")+": "+createdAtDate.toLocaleString(Qt.locale())
+ onLinkActivated: {
+ Qt.openUrlExternally(link)}
+ }
+ }
+
+ Row{
+ anchors.top: namelabelflickable.bottom
+ anchors.topMargin: 2*mm
+ spacing:4
+
+ BlueButton{
+ id:photobutton
+ text:"Photos"
+ visible:contact.network=="dfrn"? 1:0
+ onClicked:{contactComponent.state="";detailsrectangle.destroy();
+ root.currentIndex=2;
+ fotostab.active=true;
+ root.fotoSignal(contact) ;
+ }
+ }
+
+ BlueButton{
+ id:messagebutton
+ text:"Messages"
+ onClicked:{contactComponent.state="";detailsrectangle.destroy();
+ root.currentIndex=0;
+ newstab.active=true;
+ root.messageSignal(contact.id) ;
+ }
+ }
+
+ BlueButton{
+ id:dmbutton
+ visible: contact.following=="true"?true:false
+ text: "DM"
+ onClicked:{contactComponent.state="";detailsrectangle.destroy();
+ root.currentIndex=0;
+ newstab.active=true;
+ root.directmessageSignal(contact.screen_name);
+ }
+ }
+
+ BlueButton{
+ id: closeButton
+ text: "close"
+ onClicked:{contactComponent.state="";detailsrectangle.destroy()}
+ }
+ }
+ }
diff --git a/v0.002/Release/source-linux/qml/contactqml/FriendsTab.qml b/v0.002/Release/source-linux/qml/contactqml/FriendsTab.qml
new file mode 100644
index 0000000..c2e3521
--- /dev/null
+++ b/v0.002/Release/source-linux/qml/contactqml/FriendsTab.qml
@@ -0,0 +1,197 @@
+import QtQuick 2.0
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls 1.2
+import QtQuick.Controls.Styles 1.4
+import "qrc:/js/service.js" as Service
+import "qrc:/js/helper.js" as Helperjs
+import "qrc:/js/news.js" as Newsjs
+import "qrc:/qml/contactqml"
+import "qrc:/qml/genericqml"
+
+Rectangle {
+ y:1
+ color: "white"
+ TabView{
+ id:friendsTabView
+ tabPosition: Qt.TopEdge
+ x:mm
+ y:mm
+ width: root.width-2*mm
+ height: root.height-10*mm
+ currentIndex: 0
+
+ signal contactsSignal(var username)
+ signal groupsSignal(var username)
+ onCurrentIndexChanged:{
+ if (currentIndex==0){root.friendsSignal(root.login.username)}
+ else if (currentIndex==1){contactsSignal(root.login.username)}
+ else if (currentIndex==2){groupsSignal(root.login.username)}
+ }
+ style: TabViewStyle {
+ frameOverlap: 1
+ tab: Rectangle {
+ color: "white"
+ //border.color: "light grey"
+ implicitWidth: root.width/3-2*mm
+ implicitHeight: 4*mm
+ Text { id: text
+ anchors.centerIn: parent
+ text: styleData.title
+ color: "dark grey"
+ font.pixelSize:2.5*mm
+ font.bold: styleData.selected
+ }
+ }
+ frame: Rectangle { color: "light grey" }
+ tabsAlignment:Qt.AlignHCenter
+ }
+
+ Tab{
+ title: qsTr("Friends")
+ Rectangle{
+ id: friendsGridTab
+ function showFriends(username){
+ try {friendsModel.clear()} catch(e){print(e)};
+ Helperjs.readData(db,"contacts",username,function(friendsobject){
+ for (var i=0;i0){// download first contact image and update db
+ Service.updateContactInDB(login,db,newContacts[currentContact].isFriend,newContacts[currentContact])}
+ else if (contactLoadType!=""){
+ Service.processNews(function(){
+ root.contactLoadType="";
+ root.news=[];
+ })}
+ }
+
+ onCurrentContactChanged:{// download next contact image after photoplaceholder is finished saving and update db
+ if(currentContact0) {xhr.setParam("group_allow", Helperjs.cleanArray(group_allow))};
+ if (group_deny.length>0) {xhr.setParam("group_deny", Helperjs.cleanArray(group_deny))};
+ if (contact_allow.length>0) {xhr.setParam("contact_allow", Helperjs.cleanArray(contact_allow))};
+ if (contact_deny.length>0) {xhr.setParam("contact_deny", Helperjs.cleanArray(contact_deny))};
+ if (attachImageURL!=="") {xhr.setImageFileParam("media", attachImageURL )};
+ xhr.post();
+ }
+
+ function dmUpdate(title,text,replyto,screen_name,attachImageURL) {
+ xhr.url= login.server + "/api/direct_messages/new.xml";
+ xhr.setLogin(login.username+":"+Qt.atob(login.password));
+ xhr.clearParams();
+ xhr.setParam("text", text);
+ xhr.setParam("screen_name", screen_name);
+ if (parentId!="") {xhr.setParam("replyto", replyto)};
+ if (title!=="") {xhr.setParam("title", title)};
+ xhr.post();
+ }
+
+ Column {
+ id:messageColumn
+ spacing: 2
+ width: parent.width
+ TextField {
+ id: titleField
+ width: parent.width
+ placeholderText: qsTr("Title (optional)")
+ visible: messageSend.parentId === ""
+ }
+
+ TextArea {
+ id: bodyField
+ width: parent.width
+ height: 30*mm
+ wrapMode: TextEdit.Wrap
+ textFormat: TextEdit.PlainText
+ }
+
+ Row{
+ spacing: 2
+ CheckBox{
+ id:dmCheckbox
+ text:"DM"
+ enabled: false
+ checked: (directmessage==1)?true:false
+ onClicked:{
+ if(dmCheckbox.checkedState==Qt.Checked){directmessage=1}
+ else if(dmCheckbox.checkedState==Qt.Unchecked){directmessage=0}
+ }
+ }
+
+ BlueButton{
+ text:qsTr("Url")
+ onClicked: {
+ if(bodyField.selectedText==""){Helperjs.showMessage("Error","No text selected",messageSend)}
+ else{urlTextEdit.text="";
+ urlRectangle.visible=true}}
+ }
+ Rectangle{
+ id:urlRectangle
+ height:parent.height
+ width:37*mm
+ visible:false
+ TextField{
+ id:urlTextEdit
+ width:30*mm
+ height:parent.height
+ }
+ BlueButton{
+ anchors.left:urlTextEdit.right
+ anchors.leftMargin:mm
+ text:qsTr("\u2713")
+ onClicked: {if(urlTextEdit.text!=""){
+ var start = bodyField.selectionStart;
+ var end = bodyField.selectionEnd;
+ var text = bodyField.getText(start,end);
+ text = "[url="+urlTextEdit.text+"]" + text + "[/url]";
+ bodyField.remove(start,end);
+ bodyField.insert(start,text);}
+ urlRectangle.visible=false}
+ }
+ }
+ }
+
+
+ Row{
+ spacing:2
+ BlueButton{
+ visible: (directmessage==1)?false:true
+ text:qsTr("Permissions")
+ onClicked: {
+ var component = Qt.createComponent("qrc:/qml/newsqml/PermissionDialog.qml");
+ var permissions = component.createObject(messageColumn);
+ }}
+ BlueButton {
+ id: attachButton
+ text: qsTr("Attach")
+ visible:(directmessage==0)
+ onClicked: {
+ if (attachImageURL!=""){
+ Helperjs.showMessage( qsTr("Error"),qsTr("Only one attachment. Remove other attachment first!"), messageColumn)}
+ else{
+ try{imageAttachmentObject.destroy()}catch(e){print(e)}
+ imageAttachmentDialog.open()}
+ }
+ }
+ BlueButton{
+ id:contactButton
+ text:qsTr("cc")
+ visible:(directmessage==0)
+ onClicked:{
+ var contactitems="";
+ for (var i=0;i0){
+ print(Qt.atob(root.newContacts[root.currentContact].name))
+ }
+ }
+ }
+ function showNews(newsToShow){
+ if (newsStack.depth>1){newsStack.pop()}
+ newsBusy.running=false;
+ var currentTime= new Date();
+ var msg = {'currentTime': currentTime, 'model': newsModel,'news':newsToShow};
+ newsWorker.sendMessage(msg);
+ }
+
+ function onFriendsMessages(friend){
+ newstab.newstabstatus="Contact";
+ Newsjs.newsfromdb(db,root.login.username, function(dbnews){showNews(dbnews)},friend)
+ }
+
+ function onDirectMessage(friend){
+ newstab.newstabstatus="SendMessage"
+ newsStack.push({item:"qrc:/qml/newsqml/MessageSend.qml",properties:{"reply_to_user": friend,"directmessage":1,"login":root.login}});
+ }
+
+
+
+ StackView{
+ id: newsStack
+ anchors.fill:parent
+
+ initialItem:Rectangle {
+ y:1
+ color: "white"
+ width:root.width-2*mm
+ height:root.height-8*mm
+
+ BlueButton{
+ id:newstabstatusButton
+ anchors.top: parent.top
+ anchors.topMargin: 0.5*mm
+ text: qsTr(newstab.newstabstatus)
+ onClicked: {newstabmenu.popup()}
+ }
+
+ Row{
+ spacing: mm
+ anchors.top: parent.top
+ anchors.topMargin: 0.5*mm
+ anchors.right: parent.right
+
+ BlueButton {
+ id: newMessageButton
+ width:10*mm
+ text: qsTr("+")
+ onClicked: {
+ var groups=[];
+ Helperjs.readData(root.db,"groups",root.login.username,function(groupobject){
+ groups=groupobject});
+ newstab.newstabstatus="SendMessage"
+ Helperjs.readData(root.db,"contacts",root.login.username,function(friends){
+ newsStack.push({item:"qrc:/qml/newsqml/MessageSend.qml",properties:{"contacts": friends,"login":root.login}})
+ },"isFriend",1);
+ }
+ }
+ BlueButton {
+ id: quitButton
+ width:10*mm
+ text: qsTr("Quit")
+ onClicked: {Service.cleanNews(root.db,function(){Qt.quit() })}
+ }
+ BlueButton {
+ id: update
+ text: "Update"
+ onClicked: {
+ newsBusy.running=true;
+ newstab.newstabstatus=login.newsViewType;
+ root.contactLoadType="news";
+ var onlynew=true;
+ Newsjs.getFriendsTimeline(login,db,contactlist,onlynew,newstab,function(ns,nc){
+ root.news=ns;root.newContacts=nc;root.currentContact=0;
+ if (ns.length==0){// update last 20 existing news for changes and likes
+ onlynew=false;
+ Newsjs.getFriendsTimeline(login,db,contactlist,onlynew,newstab,function(rns,rnc){
+ root.contactLoadType="news";
+ root.news=rns;root.newContacts=rnc;root.currentContact=0})
+ }
+ })}
+ }
+
+ }
+ Component { id:footerComponent
+ Rectangle{
+ border.color: "#EEEEEE"
+ border.width: 1
+ width:parent.width
+ height:6*mm
+ Text{
+ font.pixelSize: 1.5*mm
+ anchors.centerIn: parent
+ text:qsTr("More")
+ }
+ MouseArea{anchors.fill:parent
+ onClicked:{
+ var currentTime= new Date();
+ if(newstab.newstabstatus=="Timeline"){
+ var lastnews_id=newsModel.get(newsModel.count-1).newsitemobject.created_at;
+ Newsjs.newsfromdb(root.db,root.login.username, function(news){
+ var msg = {'currentTime': currentTime, 'model': newsModel,'news':news,'appendnews':true};
+ newsWorker.sendMessage(msg);
+ },false,lastnews_id)}
+ if(newstab.newstabstatus=="Tree"){
+ var lastnews_id=newsModel.get(newsModel.count-1).newsitemobject.created_at;
+ Newsjs.chatsfromdb(root.db,root.login.username, function(news){
+ var msg = {'currentTime': currentTime, 'model': newsModel,'news':news,'appendnews':true};
+ newsWorker.sendMessage(msg);
+ },lastnews_id)}
+ else if(newstab.newstabstatus=="Contact"){
+ Newsjs.newsfromdb(root.db,root.login.username, function(news){
+ var msg = {'currentTime': currentTime, 'model': newsModel,'news':news,'appendnews':true};
+ newsWorker.sendMessage(msg);
+ },newsModel.get(newsModel.count-1).newsitemobject.uid,newsModel.get(newsModel.count-1).newsitemobject.created_at)}
+ }}
+ }
+ }
+ ListView {
+ id: newsView
+ anchors.fill: parent
+ anchors.topMargin: 8*root.mm
+ anchors.leftMargin: 3*root.mm; anchors.rightMargin: root.mm
+ anchors.bottomMargin: 1*root.mm
+ clip: true
+ spacing: 0
+ footer: footerComponent
+ model: newsModel
+ delegate: Newsitem{}
+ }
+
+ ListModel{id: newsModel}
+
+ WorkerScript {
+ id: newsWorker
+ source: "qrc:/js/newsworker.js"
+ }
+
+ BusyIndicator{
+ id: newsBusy
+ anchors.horizontalCenter: newsView.horizontalCenter
+ anchors.top:newsView.top
+ anchors.topMargin: 2*mm
+ width:10*mm
+ height: 10*mm
+ }
+
+ Menu {
+ id:newstabmenu
+ MenuItem {
+ text: qsTr("Timeline")
+ onTriggered: {
+ newstab.newstabstatus="Timeline";
+ newsModel.clear();
+ Newsjs.newsfromdb(root.db,root.login.username, function(dbnews){
+ showNews(dbnews)
+ })}
+ }
+ MenuItem {
+ text: qsTr("Favorites")
+ onTriggered:{
+ newstab.newstabstatus="Favorites";
+ root.contactLoadType="favorites";
+ newsBusy.running=true;
+ Newsjs.requestFavorites(root.login,db,root.contactlist,root,function(ns,nc){
+ root.news=ns; root.newContacts=nc;root.currentContact=0;
+ })
+ }
+ }
+
+ MenuItem {
+ text: qsTr("Tree")
+ onTriggered:{
+ newsModel.clear();
+ newstab.newstabstatus="Tree";
+ Newsjs.chatsfromdb(db,root.login.username,function(news){showNews(news)})
+ }
+ }
+ MenuItem {
+ text: qsTr("Notifications")
+ onTriggered:{
+ newstab.newstabstatus="Notifications";
+ newsBusy.running=true;
+ Newsjs.getNotifications(root.login,db,root,function(news){
+ showNews(news)}
+ )}
+ }
+ }
+ Component.onCompleted: {
+ root.messageSignal.connect(onFriendsMessages);
+ root.directmessageSignal.connect(onDirectMessage);
+ root.newsSignal.connect(showNews);
+ try{newsModel.clear()} catch(e){}
+ if(login.newsViewType=="Timeline"){Newsjs.newsfromdb(db,login.username,function(dbnews){showNews(dbnews)})}
+ else{Newsjs.chatsfromdb(db,login.username,function(dbnews){showNews(dbnews)})}
+ }
+ }
+ }
+}
diff --git a/v0.002/Release/source-linux/qml/newsqml/Newsitem.qml b/v0.002/Release/source-linux/qml/newsqml/Newsitem.qml
new file mode 100644
index 0000000..e6a85fa
--- /dev/null
+++ b/v0.002/Release/source-linux/qml/newsqml/Newsitem.qml
@@ -0,0 +1,343 @@
+import QtQuick 2.0
+import QtQuick.LocalStorage 2.0
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import "qrc:/js/news.js" as Newsjs
+
+
+Item {
+ id: newsitem
+ width: newsView.width
+ height:Math.max((itemMessage.height+topFlow.height+friendicaActivities.height+4*mm),profileImage.height+user_name.height+mm)
+
+ property string conversation_id: ""
+ property string attending: ""
+ onAttendingChanged: {attendLabel.visible=true;
+ attendLabel.text= qsTr("attending: ")+ qsTr(attending)}
+ signal replyto(string parent_id)
+ function showConversation(){
+ conversationsymbol.color="black";
+ newsBusy.running=true;
+ root.contactLoadType="conversation";
+ //newstabstatus="Conversation";
+
+ if(newsitemobject.messagetype==0){
+ Newsjs.requestConversation(root.login,db,newsitemobject.status_id,root.contactlist,root,function(ns,nc){
+ root.news=ns;root.newContacts=nc;root.currentContact=0;
+ })}
+ else{Newsjs.conversationfromdb(root.db,root.login.username,newsitemobject.statusnet_conversation_id, function(newsarray){
+ root.news=newsarray;root.newContacts=[];root.currentContact=1;
+ })}}
+
+ Rectangle{width:newsitem.width; height: 1; anchors.bottom: newsitem.bottom; color:"light grey"}
+ //MouseArea{
+ // anchors.fill: parent;
+ // enabled: (newstabstatus=="Chats")
+ // onClicked: {showConversation()}
+ //}
+ Rectangle{
+ width:newsitem.width
+ height:newsitem.height-1
+ color: (newsitemobject.messagetype==1)?"#ffe6e6" : "white"
+
+ Column {
+ id: authorcolumn
+ width: 8*mm
+
+ Image {
+ id:profileImage
+ source: (newsitemobject.user.profile_image!="")? "file://"+newsitemobject.user.profile_image : newsitemobject.user.profile_image_url
+ x:1
+ width: 7*mm
+ height: 7*mm
+ MouseArea{
+ anchors.fill: parent
+ onPressAndHold: { newsmenu.popup()}
+ }
+ onStatusChanged: if (profileImage.status == Image.Error) {source="qrc:/images/defaultcontact.jpg"}
+ }
+ Label {
+ id:user_name
+ color: "grey"
+ //height:3.5*mm
+ width:parent.width
+ font.pixelSize: 1.5*mm
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ text: Qt.atob(newsitemobject.user.name)
+ }
+ }
+ Column {
+ id:newscolumn
+ width: newsitem.width-8*mm
+ anchors.left: authorcolumn.right
+
+ Flow{
+ id:topFlow
+ spacing: mm
+ width:parent.width
+ Label {
+ id:messageTypeLabel
+ color: "grey"
+ text: if (newsitemobject.messagetype==0){qsTr("Source: ")+newsitemobject.source
+ } else if (newsitemobject.messagetype==1){ qsTr("Direct Message")} else {" Notification"}
+ font.pixelSize: 1.5*mm
+ }
+ Label {
+ id:createdAtLabel
+ color: "grey"
+ //height:3.5*mm
+ font.pixelSize: 1.5*mm
+ horizontalAlignment: Label.AlignRight
+ text: dateDiff
+ }
+ Label {
+ id:replytoLabel
+ color: "grey"
+ //height:3.5*mm
+ font.pixelSize: 1.5*mm
+ horizontalAlignment: Label.AlignRight
+ text: try {qsTr("In reply to ")+newsitemobject.reply_user.screen_name
+ }catch(e){" "}
+ }
+
+ Label {
+ id:newscountLabel
+ visible:((newstabstatus=="Tree")&&(newsitemobject.newscount>1))?true:false
+ color: "grey"
+ height:3.5*mm
+ font.pixelSize: 1.5*mm
+ font.bold: true
+ horizontalAlignment: Label.AlignRight
+ text: try {(newsitemobject.newscount-1)+qsTr(" comments") }catch(e){" "}
+ MouseArea{
+ anchors.fill:parent
+ onClicked: showConversation()
+ }
+ }
+ }
+
+ Text {
+ color: "#404040"
+ linkColor: "light green"
+ id: itemMessage
+ textFormat: Text.RichText
+ text: Qt.atob(newsitemobject.statusnet_html)
+ width: newsitem.width-8*mm-2
+ height: implicitHeight
+ wrapMode: Text.Wrap
+ onLinkActivated:{
+ Qt.openUrlExternally(link)}
+ }
+
+ Row{id:friendicaActivities
+ spacing:mm
+ Label{color: "grey"
+ font.pixelSize: 1.5*mm
+ text: friendica_activities.likeText
+ }
+ Label{color: "grey"
+ font.pixelSize: 1.5*mm
+ text: friendica_activities.dislikeText
+ }
+ Label{color: "grey"
+ font.pixelSize: 1.5*mm
+ text: friendica_activities.attendyesText
+ }
+ Label{color: "grey"
+ font.pixelSize: 1.5*mm
+ text: friendica_activities.attendnoText
+ }
+ Label{color: "grey"
+ font.pixelSize: 1.5*mm
+ text: friendica_activities.attendmaybeText
+ }
+ }
+ Row {
+ CheckBox{id:likeCheckbox
+ height:3*mm
+ width:8*mm
+ visible: (newsitemobject.messagetype==0)? true:false
+ checked:(friendica_activities.self.liked==1)?true:false
+ style: CheckBoxStyle {
+ background: Rectangle {
+ implicitWidth: 7*mm
+ implicitHeight: 3*mm
+ color:"white"
+ }
+ indicator:
+ Rectangle{
+ implicitWidth: 3*mm
+ implicitHeight:3*mm
+ color:control.checked?"yellow":"white"
+ x: 5*mm
+ Text{
+ font.pixelSize: 2*mm
+ color:"grey"
+ text:":-)"
+ }}
+ }
+ onClicked: {
+ if(likeCheckbox.checked==true){Newsjs.like(root.login,root.db,1,"like",newsitemobject.status_id,root);dislikeCheckbox.checked=false; model.friendica_activities.self.liked=0 }
+ else{Newsjs.like(root.login,root.db,0,"like",newsitemobject.status_id,root); model.friendica_activities.self.liked=1}}
+ }
+ CheckBox{id: dislikeCheckbox
+ height:3*mm
+ width:8*mm
+ visible: (newsitemobject.messagetype==0)? true:false
+ checked: (friendica_activities.self.disliked==1)?true:false
+ style: CheckBoxStyle {
+ background: Rectangle {
+ implicitWidth: 7*mm
+ implicitHeight:3*mm
+ color:"white"
+ }
+ indicator:
+ Rectangle{
+ implicitWidth: 3*mm
+ implicitHeight:3*mm
+ color:control.checked?"yellow":"white"
+ x:5*mm
+ Text{
+ font.pixelSize: 2*mm
+ color:"grey"
+ text:":-("
+ }}
+ }
+ onClicked: {
+ if (dislikeCheckbox.checked==true){Newsjs.like(root.login,root.db,1,"dislike",newsitemobject.status_id,root);likeCheckbox.checked=false; model.friendica_activities.self.disliked=0}
+ else {Newsjs.like(root.login,root.db,0,"dislike",newsitemobject.status_id,root); model.friendica_activities.self.disliked=1}}
+ }
+ CheckBox {
+ id:favoritedCheckbox
+ visible:(newsitemobject.messagetype==0)
+ style: CheckBoxStyle {
+ background: Rectangle {
+ implicitWidth: 6*mm
+ implicitHeight:3*mm
+ color:"transparent"
+ }
+ indicator:
+ Rectangle{x:3*mm
+ width: 3*mm
+ implicitHeight:3*mm
+ Text{
+ anchors.centerIn: parent
+ color:control.checked?"black":"grey"
+ text:"\u2605"
+ }}
+ }
+ checked:(newsitemobject.favorited>0)
+ onClicked:{
+ if(favoritedCheckbox.checkedState==Qt.Checked)
+ {Newsjs.favorite(login,true,newsitemobject.status_id,root); model.newsitemobject.favorited=1}
+ else if(favoritedCheckbox.checkedState==Qt.Unchecked)
+ {Newsjs.favorite(login,false,newsitemobject.status_id,root);model.newsitemobject.favorited=0}
+ }
+ }
+ Rectangle{
+ width: 4*mm
+ height: 3*mm
+ color:"transparent"
+ Text{
+ id:newsmenusymbol
+ color: "grey"
+ anchors.centerIn: parent
+ font.pixelSize: 2*mm
+ font.bold: true
+ text: "\u22EE"
+ }
+ MouseArea{
+ anchors.fill:parent
+ onClicked: {newsmenu.popup()}}
+
+ }
+
+ Rectangle{
+ width: 4*mm
+ height: 3*mm
+ color:"transparent"
+ //visible:(newsitemobject.in_reply_to_status_id!="")?true:false
+ Text{
+ id:conversationsymbol
+ color: "grey"
+ anchors.centerIn: parent
+ font.pixelSize: 2*mm
+ text: "\u21C4"
+ }
+ MouseArea{
+ anchors.fill:parent
+ onClicked: showConversation()
+ }
+ }
+ Label {
+ id:attendLabel
+ visible: false
+ color: "grey"
+ height:3.5*mm
+ font.pixelSize: 1.5*mm
+ horizontalAlignment: Label.AlignRight
+ text: (friendica_activities.self.attending)?qsTr("attending: ")+ qsTr(attending):""
+ }
+ }
+ }
+
+ Menu {
+ id:newsmenu
+ MenuItem {
+ text: qsTr("Reply")
+ onTriggered: {
+ var directmessage=0;
+ if (newsitemobject.messagetype==1){ directmessage=1}
+ newsStack.push({item:"qrc:/qml/newsqml/MessageSend.qml",properties:{"reply_to_user": newsitemobject.user.screen_name,"parentId":newsitemobject.status_id,"login":root.login,"directmessage":directmessage}});
+ }
+ }
+ MenuItem {
+ text: qsTr("DM")
+ onTriggered: {
+ root.directmessageSignal(newsitemobject.user.screen_name);
+ }
+ }
+ MenuItem {
+ text: qsTr("Repost")
+ onTriggered: {
+ Newsjs.retweetNews(root.login,db,newsitemobject.status_id,root,function(reply){
+ print(reply);
+ })
+ }
+ }
+ MenuItem {
+ text: qsTr("Conversation")
+ onTriggered: showConversation()
+ }
+
+ Menu{
+ title: qsTr("Attending")
+ MenuItem{text:qsTr("yes")
+ onTriggered: {Newsjs.attend(root.login,db,"yes",newsitemobject.status_id,root,function(){
+ newsitem.attending="yes";
+ attendLabel.visible=true})}
+ }
+
+ MenuItem{text:qsTr("maybe")
+ onTriggered: {Newsjs.attend(root.login,db,"maybe",newsitemobject.status_id,root,function(){
+ newsitem.attending="maybe"})}
+ }
+
+ MenuItem{text:qsTr("no")
+ onTriggered: {Newsjs.attend(root.login,db,"no",newsitemobject.status_id,root,function(){
+ newsitem.attending="no"})}
+ }
+ }
+
+ MenuItem {
+ text: qsTr("Delete")
+ onTriggered: {
+ Newsjs.deleteNews(root.login,root.db,newsitemobject.status_id,newsitemobject.messagetype,root,function(reply){
+ newsModel.remove(index);
+ })
+ }
+ }
+}
+}
+}
+
diff --git a/v0.002/Release/source-linux/qml/newsqml/PermissionDialog.qml b/v0.002/Release/source-linux/qml/newsqml/PermissionDialog.qml
new file mode 100644
index 0000000..d7e659a
--- /dev/null
+++ b/v0.002/Release/source-linux/qml/newsqml/PermissionDialog.qml
@@ -0,0 +1,191 @@
+import QtQuick 2.0
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls 1.4
+import QtQml.Models 2.1
+import "qrc:/js/service.js" as Service
+import "qrc:/js/helper.js" as Helperjs
+import "qrc:/qml/genericqml"
+
+Rectangle{
+ id:permissionDialog
+ x: mm
+ width: messageColumn.width-5*mm
+ height:root.height/3
+ function updatePerms(){
+ for (var i=0;i-1){contactstatus="positive";print(contacts[name].cid+" pos")}
+ else if (contact_deny.indexOf(contacts[name].cid)>-1){contactstatus="negative"}
+ contactModel.append({"contact":contacts[name],"contactstatus":contactstatus})
+ }},"isFriend",1);
+
+ Helperjs.readData(db,"groups",login.username,function(owngroups){
+ for (var number in owngroups){
+ var groupstatus= "neutral";
+ if (group_allow.indexOf(owngroups[number].gid)>-1){groupstatus="positive"}
+ else if (group_deny.indexOf(owngroups[number].gid)>-1){groupstatus="negative"}
+ groupModel.append({"group":owngroups[number],"groupstatus":groupstatus})
+ }});
+ }
+}
diff --git a/v0.002/Release/source-linux/qml/photoqml/PhotoComponent.qml b/v0.002/Release/source-linux/qml/photoqml/PhotoComponent.qml
new file mode 100644
index 0000000..9884d71
--- /dev/null
+++ b/v0.002/Release/source-linux/qml/photoqml/PhotoComponent.qml
@@ -0,0 +1,99 @@
+import QtQuick 2.0
+//import QtQuick.LocalStorage 2.0
+import QtQuick.Controls 1.2
+
+Package {
+ Item { id: stackItem; Package.name: 'stack'; z: stackItem.PathView.z;width:16.5*mm;height:16.5*mm}
+ Item { id: listItem; Package.name: 'list'; width: root.width-1*mm; height: root.height-8*mm; }
+ Item { id: gridItem; Package.name: 'grid';}
+
+ Item {
+ id: photoWrapper
+ width: 16.5*mm; height: 16.5*mm
+ z: stackItem.PathView.z
+ property string hqphotolink: photoLink
+
+ Rectangle {
+ id: placeHolder
+ color: 'lightblue'; antialiasing: true
+ anchors.fill:parent
+ }
+
+ BusyIndicator { anchors.centerIn: parent; running: realImage.status != Image.Ready }
+ Image {
+ id: realImage;
+ // property string hqphotolink: photoLink
+ width: photoWrapper.width; height: photoWrapper.height
+ antialiasing: true;
+ asynchronous: true
+ cache: false
+ fillMode: Image.PreserveAspectFit;
+ source: imageLocation
+ // onStatusChanged: if (realImage.status == Image.Ready) print(realImage.paintedHeight+"x"+realImage.paintedWidth)
+ }
+ Rectangle{
+ id:phototextRectangle
+ color:"black"
+ z:3
+ opacity: 0.5
+ width:phototext.contentWidth
+ height: phototext.contentHeight
+ anchors.bottom: photoWrapper.bottom
+ }
+ Text {
+ id:phototext
+ z:4
+ text: photoDescription.trim()
+ width:15*mm
+ anchors.bottom: photoWrapper.bottom
+ color: "white"
+ font.pixelSize: 2*mm
+ wrapMode:Text.Wrap
+ }
+ MouseArea {
+ width: realImage.paintedWidth; height: realImage.paintedHeight; anchors.centerIn: realImage
+ onClicked: {
+ if (albumWrapper.state == 'inGrid') {
+ gridItem.GridView.view.currentIndex = index;
+ //print("photoLink"+realImage.photoLink)
+ albumWrapper.state = 'fullscreen'
+ } else {
+ gridItem.GridView.view.currentIndex = index;
+ albumWrapper.state = 'inGrid'
+ }
+ }
+ }
+
+ states: [
+ State {
+ name: 'stacked'; when: albumWrapper.state == ''
+ ParentChange { target: photoWrapper; parent: stackItem; }//x: 1*mm; y: 1*mm }
+ PropertyChanges { target: photoWrapper; opacity: stackItem.PathView.onPath ? 1.0 : 0.0 }
+ PropertyChanges { target: phototext; opacity: 0.0 }
+ PropertyChanges { target: phototextRectangle; opacity: 0.0 }
+ },
+ State {
+ name: 'inGrid'; when: albumWrapper.state == 'inGrid'
+ ParentChange { target: photoWrapper; parent: gridItem; x: 1*mm; y: 1*mm;}
+ PropertyChanges { target: phototext; opacity: 1.0 }
+ PropertyChanges { target: phototextRectangle; opacity: 0.5 }
+ PropertyChanges { target: placeHolder; opacity: 1.0 }
+ },
+ State {
+ name: 'fullscreen'; when: albumWrapper.state == 'fullscreen'
+ ParentChange {
+ target: photoWrapper; parent: listItem; x: 1; y: 1;
+ width: root.width-mm; height: root.height-8*mm
+ }
+ PropertyChanges { target: placeHolder; opacity: 0.0 }
+ PropertyChanges { target: realImage; source: photoWrapper.hqphotolink}
+ PropertyChanges { target: phototext; anchors.bottom: realImage.bottom}
+ PropertyChanges { target: phototext; width:realImage.width }
+ PropertyChanges { target: phototextRectangle; anchors.bottom: realImage.bottom }
+ PropertyChanges { target: realImage; width: Math.min(listItem.width,sourceSize.width);height: Math.min(listItem.height,sourceSize.height) }
+ }
+ ]
+
+ }
+}
+
diff --git a/v0.002/Release/source-linux/qml/photoqml/PhotoTab.qml b/v0.002/Release/source-linux/qml/photoqml/PhotoTab.qml
new file mode 100644
index 0000000..edada91
--- /dev/null
+++ b/v0.002/Release/source-linux/qml/photoqml/PhotoTab.qml
@@ -0,0 +1,181 @@
+import QtQuick 2.0
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls 1.4
+import QtQml.Models 2.1
+import "qrc:/js/service.js" as Service
+import "qrc:/js/helper.js" as Helperjs
+import "qrc:/qml/photoqml"
+import "qrc:/qml/genericqml"
+
+Rectangle {
+ id:fotorectangle
+ property string phototabstatus:"Images"
+ onPhototabstatusChanged:{phototabstatusButton.text=qsTr(phototabstatus)}
+
+ y:1
+ width:root.width-mm
+ height:root.height-5*mm
+ color: '#fff'
+ property var newimages:[]
+ property int currentimageno: 0
+//onLoginChanged:{var msg = {'model': photogroupModel,'albums':[],'firstalbum':0,'foreignPicture':false};
+// photoWorker.sendMessage(msg);
+//}
+ onNewimagesChanged:{
+ if(newimages.length>0){
+ Helperjs.readField("album",root.db,"imageData",root.login.username,function(albums){
+ //print("albums"+JSON.stringify(albums)+JSON.stringify(newimages[currentimageno]));
+ for (var i=0;i