diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fcc6e8d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +language: cpp +compiler: gcc +sudo: require +dist: trusty +before_install: +- sudo add-apt-repository ppa:beineri/opt-qt58-trusty -y +- sudo apt-get update -qq +install: +- sudo apt-get -y install qt58base qt58webengine qt58quickcontrols +- source /opt/qt58/bin/qt58-env.sh +script: +- qmake PREFIX=/usr +- make -j4 +- sudo make INSTALL_ROOT=appdir install ; sudo chown -R $USER appdir ; find appdir/ +- wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" +- chmod a+x linuxdeployqt*.AppImage +- unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH +- "./linuxdeployqt*.AppImage ./appdir/usr/share/applications/*.desktop -qmldir=./qml/ + -bundle-non-qt-libs" +- "./linuxdeployqt*.AppImage ./appdir/usr/share/applications/*.desktop -qmldir=./qml/ + -appimage" +- find ./appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " + -f 2-3 | sort | uniq +- mv FeedTheMonkey*.AppImage FeedTheMonkey.AppImage +deploy: + provider: releases + api_key: + secure: d+hHwOnmeLPVvuue6VDCs2LwLS+BFzJF/BB5iObtkCYBwQ8ybnVzUcgnjJKOt37SHI0T9kLegI+Lq/843ECYiGiDjQg4PvCF69V8ODgHv3v1qiN5oG/eroBXd83a0+xhi4BuJt0SwcV9mcv4uD9bCPhj944rmMLH+3qD4ysgImBmbYSbbLecE9+QAs7bfrCwQRfdCePBORX3FHa/p12NEtln7xv6ZRyku9LdJSzAcdgm4zc95ggTAVC1+aQB6J0q2QzWPlQcOkLx+ZYmOqClhbSMFpIyPXP8UpXjYyvUlTAd0+wH8BGf0O3lpOqACc7IKIbj9d5oPmghVZo55SyW+RR77G+az+IbGJ7iXZsMfQZsMvtB7hNYhNvUUxQrAau7Y/ve+6sMQmvA7aMHV8kDUvnNW/c2r2jAWwk+N8QzGcP/rclDCKeOWZqZABmrzTViXZVAeXh4hJ8r6mbq8iwagBUPCsVYhVuerQt/KIoWxyn6/1GmMfKGi3dA/v3u1qU61vzrz3yLlJBmUAVPxZdVmqfRweh4BXjImxFMFmf5PYm5FnDg1gmw8rWsgii7+IPYw7DjTAHpjYbtXvDwDgG1nRXiRp2TGtPPgKW1/Uk8r/j5vfB5WcEZ7exLUgsPPjny5MGvzjqOxeLvwK1Pg9jFBFXIx7l1tNMJQxQU0r3DmBg= + file: FeedTheMonkey.AppImage + on: + repo: jeena/FeedTheMonkey + skip_cleanup: true + draft: true diff --git a/README.md b/README.md index d26e967..155358a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # FeedTheMonkey -Icon +Icon FeedTheMonkey is a desktop client for [TinyTinyRSS](http://tt-rss.org). That means that it doesn't work as a standalone feed reader but only as a client for the TinyTinyRSS API @@ -11,10 +11,12 @@ to have Qt 5.6 installed to be able to compile and have a account on a TinyTinyR ## Installation -Download the latest release code from: https://github.com/jeena/FeedTheMonkey/releases/latest +If you run Linux then there is an AppImage on the [Latest release](https://github.com/jeena/FeedTheMonkey/releases/latest) page. You download it, make executable and are able to run, it should work on most of the distributions out there. + +For ArchLinux I package it and it's available on https://aur.archlinux.org/packages/feedthemonkey/ You can compile and install it everywhere Qt is suported, this means on macOS, Windows -and Linux. For ArchLinux I package it and it's available on https://aur.archlinux.org/packages/feedthemonkey/ +and Linux. ## Keyboard shortcuts @@ -41,13 +43,13 @@ the use on a desktop computer but I'd like to see it on a mobile device too. ## Screenshot -![Feed the Monkey screenshot](http://jabs.nu/feedthemonkey/screenshot.png) +![Feed the Monkey screenshot](http://jeena.net/feedthemonkey/feedthemonkey-dark.png) ## License This file is part of FeedTheMonkey. -Copyright 2015 Jeena +Copyright 2015-2017 Jeena FeedTheMonkey is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/html/content.css b/html/content.css index 1b3eda8..2b1d0b3 100644 --- a/html/content.css +++ b/html/content.css @@ -25,13 +25,12 @@ html, body { body { background: #eee; font-family: sans-serif; - padding: 2em; - font-weight: lighter; + word-wrap: break-word; } .nightmode { - background: #111; - color: #aaa; + background: #353535; + color: #ddd; } .nightmode::-webkit-scrollbar { @@ -50,32 +49,28 @@ body { } h1 { - font-weight: lighter; font-size: 1.4em; margin: 0; padding: 0; } -#date { - border-bottom: 1px solid #aaa; - margin-bottom: 1em; - padding-bottom: 1em; - display: block; -} - -.nightmode #date { - border-bottom-color: #333; -} - .starred:after { content: "*"; } +header { + padding: 2em; + border-bottom: 1px solid #aaa; +} + +.nightmode header { + border-bottom-color: #222; +} + header p { - color: #aaa; + color: #666; margin: 0; padding: 0; - font-size: 0.8em; } .nightmode header p { @@ -89,6 +84,7 @@ a { article { line-height: 1.6; + margin: 2em; } article a { diff --git a/html/content.html b/html/content.html index bbcd5f5..87d53aa 100644 --- a/html/content.html +++ b/html/content.html @@ -4,7 +4,96 @@ TTRSS - + diff --git a/html/content.js b/html/content.js deleted file mode 100644 index cbc29b2..0000000 --- a/html/content.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * This file is part of FeedTheMonkey. - * - * Copyright 2015 Jeena - * - * FeedTheMonkey is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * FeedTheMonkey is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with FeedTheMonkey. If not, see . - */ - -function $(id) { - return document.getElementById(id); -} - -function setArticle(article) { - window.scrollTo(0, 0); - - $("date").innerHTML = ""; - $("title").innerHTML = ""; - $("title").href = ""; - $("title").title = ""; - $("feed_title").innerHTML = ""; - $("author").innerHTML = ""; - $("article").innerHTML = ""; - - if(article === "empty") { - - $("article").innerHTML = "No unread articles to display."; - - } else if(article === "loading") { - - $("article").innerHTML = "Loading "; - - } else if (article === "logout") { - - } else if(article) { - - $("date").innerHTML = (new Date(parseInt(article.updated, 10) * 1000)); - $("title").innerHTML = article.title; - $("title").href = article.link; - $("title").title = article.link; - $("feed_title").innerHTML = article.feed_title; - $("title").className = article.marked ? "starred" : ""; - $("author").innerHTML = ""; - if(article.author && article.author.length > 0) - $("author").innerHTML = "– " + article.author - $("article").innerHTML = article.content; - - var as = $("article").getElementsByTagName("a"); - for(var i = 0; i < as.length; i++) { - as[i].target = ""; - } - } -} - -function setFont(font, size) { - document.body.style.fontFamily = font; - document.body.style.fontSize = size + "pt"; -} - -function setNightmode(nightmode) { - if(nightmode) document.body.className = "nightmode"; - else document.body.className = ""; -} diff --git a/html/html.qrc b/html/html.qrc index 90d8b19..c6cf166 100644 --- a/html/html.qrc +++ b/html/html.qrc @@ -2,6 +2,5 @@ content.css content.html - content.js diff --git a/misc/feedthemonkey.desktop b/misc/feedthemonkey.desktop index a9d950f..c302345 100644 --- a/misc/feedthemonkey.desktop +++ b/misc/feedthemonkey.desktop @@ -1,5 +1,4 @@ [Desktop Entry] -Version=2.0.0 Comment=A desktop client for the TinyTinyRSS feed reader. Exec=feedthemonkey GenericName=Feed Reader @@ -9,4 +8,4 @@ NoDisplay=false StartupNotify=true Terminal=false Type=Application -Categories=Network;Qt +Categories=Network;Qt; diff --git a/qml/Content.qml b/qml/Content.qml index b00764a..07440be 100644 --- a/qml/Content.qml +++ b/qml/Content.qml @@ -17,12 +17,11 @@ * along with FeedTheMonkey. If not, see . */ -import QtWebEngine 1.0 +import QtWebEngine 1.8 import QtQuick 2.0 import QtQuick.Controls 1.3 import QtQuick.Layouts 1.1 import QtQuick.Controls.Styles 1.3 -import QtQuick.Controls 1.3 import TTRSS 1.0 Item { @@ -85,9 +84,17 @@ Item { webView.runJavaScript("if(typeof setNightmode == \"function\") setNightmode(" + (content.nightmode ? "true" : "false") + ")") } - onNavigationRequested: { - if (request.navigationType != WebEngineView.LinkClickedNavigation) { + if (request.url == "feedthemonkey:previous") { + request.action = WebEngineView.IgnoreRequest; + app.showPreviousPost(); + } else if (request.url == "feedthemonkey:next") { + request.action = WebEngineView.IgnoreRequest; + app.showNextPost(); + } else if (request.url == "feedthemonkey:open") { + request.action = WebEngineView.IgnoreRequest; + Qt.openUrlExternally(post.link) + } else if (request.navigationType !== WebEngineNavigationRequest.LinkClickedNavigation) { request.action = WebEngineView.AcceptRequest; } else { request.action = WebEngineView.IgnoreRequest; diff --git a/qml/PostListItem.qml b/qml/PostListItem.qml index e0c3d20..63bf813 100644 --- a/qml/PostListItem.qml +++ b/qml/PostListItem.qml @@ -33,7 +33,7 @@ Item { } id: item - height: d.height + t.height + e.height + 20 + height: d.height + t.height + e.height + 2 Item { anchors.fill: parent @@ -81,7 +81,7 @@ Item { Label { id: t text: title - color: nightmode ? (read ? "#555" : "#aaa") : (read ? "gray" : "black") + color: nightmode ? (read ? "#888" : "#ddd") : (read ? "gray" : "black") font.pointSize: textFontSize textFormat: Text.PlainText wrapMode: Text.WrapAnywhere diff --git a/qml/Sidebar.qml b/qml/Sidebar.qml index bcb70ee..84de4cb 100644 --- a/qml/Sidebar.qml +++ b/qml/Sidebar.qml @@ -48,6 +48,21 @@ ScrollView { } } + onWidthChanged: { + // Hide sidebar if smaller than 200px wide + if(width < 200) { + width = 0; + } + } + + Rectangle { + width: 1 + color: app.nightmode ? "#111" : "lightgray" + anchors.right: parent.right + anchors.top: parent.top + height: parent.height + } + ListView { id: listView @@ -67,12 +82,12 @@ ScrollView { highlightFollowsCurrentItem: false highlight: Component { Rectangle { - width: listView.currentItem.width + width: listView.currentItem.width -1 height: listView.currentItem.height - color: nightmode ? "#444" : "lightblue" - opacity: 0.5 + color: nightmode ? "#15539e" : "lightblue" y: listView.currentItem.y } + } onCurrentItemChanged: { @@ -85,8 +100,6 @@ ScrollView { } item.content.post = server.posts[currentIndex] - //content.flickableItem.contentY = 0 - previousPost = item.content.post } } diff --git a/qml/TheMenuBar.qml b/qml/TheMenuBar.qml index ae2eb70..34c0310 100644 --- a/qml/TheMenuBar.qml +++ b/qml/TheMenuBar.qml @@ -35,6 +35,11 @@ MenuBar { Menu { visible: menuBar.visible title: qsTr("File") + MenuItem { + text: qsTr("Close &Window") + shortcut: "Ctrl+W" + onTriggered: Qt.quit() + } MenuItem { text: qsTr("Exit") shortcut: "Ctrl+Q" @@ -132,7 +137,7 @@ MenuBar { title: qsTr("Help") MenuItem { text: qsTr("About") - onTriggered: Qt.openUrlExternally("http://jabs.nu/feedthemonkey"); + onTriggered: Qt.openUrlExternally("http://jeena.net/feedthemonkey/index.html"); } } diff --git a/qml/main.qml b/qml/main.qml index 0a6288a..0b6074b 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -21,6 +21,7 @@ import QtQuick 2.3 import QtQuick.Controls 1.3 import QtQuick.Window 2.0 import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 import Qt.labs.settings 1.0 import TTRSS 1.0 @@ -28,7 +29,7 @@ ApplicationWindow { id: app title: "FeedTheMonkey" visible: true - color: nightmode ? "#111" : "#eee" + color: nightmode ? "#2d2d2d" : "#eee" minimumWidth: 480 minimumHeight: 320 @@ -47,6 +48,17 @@ ApplicationWindow { property int textFontSizeIndex: defaultTextFontSizeIndex property int textFontSize: fontSizes[textFontSizeIndex] property bool nightmode: false + property bool showMenuBar: false + + menuBar: TheMenuBar { + id: menu + serverLogin: serverLogin + server: server + sidebar: sidebar + content: content + visible: app.showMenuBar + __contentItem.visible: visible + } Settings { id: settings @@ -60,14 +72,6 @@ ApplicationWindow { property alias nightmode: app.nightmode } - property TheMenuBar menu: TheMenuBar { - id: menu - serverLogin: serverLogin - server: server - sidebar: sidebar - content: content - } - function loggedIn() { if(serverLogin.loggedIn()) { menu.loggedIn = true; @@ -109,6 +113,14 @@ ApplicationWindow { return forEscapingHTML.getText(0, forEscapingHTML.length) } + function showNextPost() { + sidebar.next() + } + + function showPreviousPost() { + sidebar.previous() + } + function keyPressed(event) { switch (event.key) { case Qt.Key_Right: @@ -147,10 +159,6 @@ ApplicationWindow { case Qt.Key_Return: Qt.openUrlExternally(content.post.link) break - case Qt.Key_S: { - console.log(Qt.openUrlExternally("speaker:"+ removeHTML(content.post.content))) - break - } default: break } @@ -172,7 +180,6 @@ ApplicationWindow { content: content server: server - Layout.minimumWidth: 200 implicitWidth: 300 textFontSize: app.textFontSize nightmode: app.nightmode @@ -192,7 +199,7 @@ ApplicationWindow { Keys.onReleased: { switch (event.key) { case Qt.Key_Alt: - app.menuBar = menu + app.showMenuBar = !app.showMenuBar break default: break @@ -206,14 +213,27 @@ ApplicationWindow { visible: !serverLogin.loggedIn() function login() { - console.log("FOO") serverLogin.login(serverUrl, userName, password) } + + } + + MessageDialog { + id: loginErrorAlert + title: "A login error occured" + text: serverLogin.loginError + onAccepted: visible = false } ServerLogin { id: serverLogin onSessionIdChanged: app.loggedIn() + onLoginErrorChanged: { + console.log("loginError:", loginError) + if(loginError.length > 0) { + loginErrorAlert.visible = true + } + } } Server { diff --git a/src/main.cpp b/src/main.cpp index 48475bb..95f351e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,6 +31,7 @@ int main(int argc, char *argv[]) { + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); app.setOrganizationName("Jeena"); app.setOrganizationDomain("jeena.net"); diff --git a/src/tinytinyrsslogin.cpp b/src/tinytinyrsslogin.cpp index f0536f2..2648e7a 100644 --- a/src/tinytinyrsslogin.cpp +++ b/src/tinytinyrsslogin.cpp @@ -88,23 +88,47 @@ void TinyTinyRSSLogin::reply() QNetworkReply *reply = qobject_cast(sender()); if (reply) { + if (reply->error() == QNetworkReply::NoError) { QString jsonString = QString(reply->readAll()); QJsonDocument json = QJsonDocument::fromJson(jsonString.toUtf8()); - mSessionId = json.object().value("content").toObject().value("session_id").toString(); + if(json.object().value("content").toObject().value("error").toString().length() > 0) { - emit sessionIdChanged(mSessionId); + mLoginError = json.object().value("content").toObject().value("error").toString(); + qWarning() << mLoginError; + emit loginErrorChanged(mLoginError); - QSettings settings; - settings.setValue("sessionId", mSessionId); - settings.setValue("serverUrl", mServerUrl); - settings.sync(); + if(mLoginError == "NOT_LOGGED_IN") { + mSessionId = nullptr; + mServerUrl = nullptr; + QSettings settings; + settings.remove("sessionId"); + settings.remove("serverUrl"); + settings.sync(); + + emit sessionIdChanged(mSessionId); + } + + } else { + mSessionId = json.object().value("content").toObject().value("session_id").toString(); + + emit sessionIdChanged(mSessionId); + + QSettings settings; + settings.setValue("sessionId", mSessionId); + settings.setValue("serverUrl", mServerUrl); + settings.sync(); + } } else { - int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - //do some error management - qWarning() << "HTTP error: " << httpStatus << " :: " << reply->error(); + mLoginError = "HTTP error: " + + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString() + + " :: " + + reply->errorString(); + qWarning() << mLoginError; + + emit loginErrorChanged(mLoginError); } reply->deleteLater(); } diff --git a/src/tinytinyrsslogin.h b/src/tinytinyrsslogin.h index 8971f2a..5c21887 100644 --- a/src/tinytinyrsslogin.h +++ b/src/tinytinyrsslogin.h @@ -30,12 +30,14 @@ class TinyTinyRSSLogin : public QObject Q_OBJECT Q_PROPERTY(QString sessionId READ sessionId NOTIFY sessionIdChanged) Q_PROPERTY(QUrl serverUrl READ serverUrl) + Q_PROPERTY(QString loginError READ loginError NOTIFY loginErrorChanged) public: TinyTinyRSSLogin(QObject *parent = 0); ~TinyTinyRSSLogin(); QString sessionId() const { return mSessionId; } QUrl serverUrl() const { return mServerUrl; } + QString loginError() const { return mLoginError; } Q_INVOKABLE bool loggedIn(); Q_INVOKABLE void login(const QString serverUrl, const QString user, const QString password); @@ -43,6 +45,7 @@ public: signals: void sessionIdChanged(QString); + void loginErrorChanged(QString); private slots: void reply(); @@ -50,6 +53,7 @@ private slots: private: QString mSessionId; QUrl mServerUrl; + QString mLoginError; QNetworkAccessManager *mNetworkManager; };