Pocket integration

This commit is contained in:
Muki 2017-03-18 14:56:28 +01:00
parent 842bb08b85
commit 869e12a0fd
67 changed files with 1633 additions and 225 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -1 +1,4 @@
#include <QString>
static const quint64 KEY = Q_UINT64_C(0x00000000000000000);
static const QString pocket_consumer_key = "";

View file

@ -0,0 +1,59 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import Sailfish.Silica 1.0
Item {
id: root
property alias button: _button
property alias description: desc.text
opacity: enabled ? 1.0 : 0.4
height: col.height + 5*Theme.paddingSmall
anchors {left: parent.left; right: parent.right}
Column {
id: col
spacing: Theme.paddingSmall
anchors {
top: parent.top; topMargin: 3*Theme.paddingSmall
left: parent.left; right: parent.right
leftMargin: Theme.horizontalPageMargin; rightMargin: Theme.horizontalPageMargin
}
Button {
id: _button
enabled: root.enabled
anchors.horizontalCenter: parent.horizontalCenter
}
Label {
id: desc
anchors { left: parent.left; right: parent.right }
wrapMode: Text.Wrap
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
}
}
}

View file

@ -38,17 +38,58 @@ Page {
ActiveDetector {}
SilicaListView {
anchors { top: parent.top; left: parent.left; right: parent.right }
clip: true
height: app.flickHeight
header: PageHeader {
title: qsTr("Changelog")
SilicaFlickable {
id: flick
anchors {
left: parent.left
right: parent.right
top: parent.top
}
height: app.flickHeight
clip: true
contentHeight: content.height
model: VisualItemModel {
Column {
id: content
anchors {
left: parent.left
right: parent.right
}
spacing: Theme.paddingMedium
PageHeader {
title: qsTr("Changelog")
}
SectionHeader {
text: qsTr("Version %1").arg("2.6.0")
}
LogItem {
title: "Pocket integration"
description: "Pocket is an Internet tool for saving articles to read later. Integration implemented in Kaktus provides \"Add to Pocket\" button in the articles list and in the web viewer.";
}
LogItem {
title: "Share link"
description: "\"Share link\" button has been added. Due to Jolla Store restrictions it will be enabled only in OpenRepos package.";
}
LogItem {
title: "Improved app icon"
description: "Kaktus icon has a new fresh look!"
}
LogItem {
title: "Delete web viewer cookies"
description: "Option in the settings that allows you to clear cache and cookies of the web viewer."
}
LogItem {
title: "Spanish translation update"
description: "Spanish translations has been updated."
}
SectionHeader {
text: qsTr("Version %1").arg("2.5.3")
@ -282,13 +323,11 @@ Page {
}*/
Item {
height: Theme.paddingMedium
}
Spacer {}
}
VerticalScrollDecorator {}
}
VerticalScrollDecorator {
flickable: flick
}
}

View file

@ -56,7 +56,6 @@ Item {
}
onBusyChanged: {
//console.log("onBusyChanged:", busy)
open = busy
}

View file

@ -58,6 +58,8 @@ ListItem {
readonly property alias expandable: box.expandable
property bool expandedMode: settings.expandedMode
property int evaluation: 0
signal markedAsRead
signal markedAsUnread
signal markedReadlater
@ -70,6 +72,9 @@ ListItem {
signal openInViewer
signal openInBrowser
signal showFeedContent
signal evaluated(int evaluation)
signal share
signal pocketAdd
enabled: !last && !daterow
@ -80,7 +85,7 @@ ListItem {
onMenuOpenChanged: { if(menuOpen) app.hideBar() }
menu: last ? null : settings.iconContextMenu ? iconContextMenu : contextMenu
menu: last ? null : iconContextMenu
onHiddenChanged: {
if (hidden && expanded) {
@ -134,7 +139,7 @@ ListItem {
}
}*/
BackgroundItem {
/*BackgroundItem {
id: star
anchors.right: parent.right; anchors.top: parent.top
height: Theme.iconSizeSmall + 2*Theme.paddingMedium
@ -164,6 +169,22 @@ ListItem {
return "image://theme/icon-m-favorite?"+Theme.primaryColor;
}
}
}*/
SmallIconButton {
id: star
anchors.right: parent.right; anchors.top: parent.top
marginV: 2*Theme.paddingMedium
visible: !last && !daterow
icon.source: root.readlater ? "image://theme/icon-m-favorite-selected":
"image://theme/icon-m-favorite"
onClicked: {
if (root.readlater)
root.unmarkedReadlater()
else
root.markedReadlater()
}
}
Item {
@ -326,7 +347,7 @@ ListItem {
Image {
id: expanderIcon
anchors.bottom: parent.bottom
anchors.verticalCenter: expanderLabel.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Theme.paddingMedium
source: "image://theme/icon-lock-more"
@ -382,7 +403,8 @@ ListItem {
Label {
anchors.left: parent.left; anchors.right: parent.right
id: authorLabel
anchors {left: parent.left; right: parent.right}
font.pixelSize: Theme.fontSizeExtraSmall
color: root.down ? Theme.secondaryHighlightColor : Theme.secondaryColor
truncationMode: TruncationMode.Fade
@ -390,6 +412,53 @@ ListItem {
? utils.getHumanFriendlyTimeString(date)+" • "+root.author
: utils.getHumanFriendlyTimeString(date)
}
/*Item {
anchors.left: parent.left; anchors.right: parent.right
height: evaluationButtons.height
Label {
id: authorLabel
anchors {left: parent.left; right: evaluationButtons.left; verticalCenter: parent.verticalCenter}
font.pixelSize: Theme.fontSizeExtraSmall
color: root.down ? Theme.secondaryHighlightColor : Theme.secondaryColor
truncationMode: TruncationMode.Fade
text: root.author!=""
? utils.getHumanFriendlyTimeString(date)+" • "+root.author
: utils.getHumanFriendlyTimeString(date)
}
Row {
id: evaluationButtons
anchors {right: parent.right; verticalCenter: parent.verticalCenter}
SmallIconButton {
icon.source: root.evaluation === 1 ?
"image://icons/icon-m-good-selected" :
"image://icons/icon-m-good"
onClicked: {
if (root.evaluation === 1)
root.evaluation = 0
else
root.evaluation = 1
evaluated(root.evaluation)
}
}
SmallIconButton {
icon.source: root.evaluation === -1 ?
"image://icons/icon-m-bad-selected" :
"image://icons/icon-m-bad"
onClicked: {
if (root.evaluation === -1)
root.evaluation = 0
else
root.evaluation = -1
evaluated(root.evaluation)
}
}
}
}*/
}
onClicked: {
@ -476,143 +545,56 @@ ListItem {
visible: enabled
//enabled: settings.clickBehavior !== 2
onClicked: {
root.showFeedContent();
root.expanded = false;
menu.hide();
root.showFeedContent()
root.expanded = false
menu.hide()
}
}
IconMenuItem {
text: qsTr("Add to Pocket")
visible: settings.pocketEnabled
enabled: settings.pocketEnabled && dm.online
icon.source: "image://icons/icon-m-pocket"
busy: pocket.busy
onClicked: root.pocketAdd()
}
IconMenuItem {
text: qsTr("Share link")
icon.source: "image://theme/icon-m-share"
onClicked: root.share()
}
IconMenuItem {
id: likeItem
text: qsTr("Toggle Like")
icon.source: root.liked ? 'image://icons/icon-m-like-selected' : 'image://icons/icon-m-like'
icon.source: root.liked ? "image://icons/icon-m-like-selected" : "image://icons/icon-m-like"
enabled: settings.showBroadcast && app.isOldReader
visible: enabled
onClicked: {
if (root.liked) {
root.unmarkedLike();
root.unmarkedLike()
} else {
root.markedLike();
root.markedLike()
}
menu.hide();
menu.hide()
}
}
IconMenuItem {
text: qsTr("Toggle Share")
icon.source: root.broadcast ? 'image://icons/icon-m-share-selected' : 'image://icons/icon-m-share'
icon.source: root.broadcast ? "image://icons/icon-m-share-selected" : "image://icons/icon-m-share"
enabled: settings.showBroadcast && app.isOldReader &&
!root.friendStream
visible: enabled
onClicked: {
if (root.broadcast) {
root.unmarkedBroadcast();
root.unmarkedBroadcast()
} else {
root.markedBroadcast();
root.markedBroadcast()
}
menu.hide();
}
}
}
}
Component {
id: contextMenu
ContextMenu {
MenuItem {
text: read ? qsTr("Mark as unread") : qsTr("Mark as read")
visible: enabled
enabled: root.showMarkedAsRead
onClicked: {
if (read) {
root.markedAsUnread();
} else {
root.markedAsRead();
root.expanded = false;
}
}
}
MenuItem {
text: qsTr("Mark above as read")
visible: enabled
enabled: root.showMarkedAsRead && index > 1
onClicked: {
root.markedAboveAsRead();
root.expanded = false;
}
}
MenuItem {
text: app.isNetvibes || app.isFeedly ?
readlater ? qsTr("Unsave") : qsTr("Save") :
readlater ? qsTr("Unstar") : qsTr("Star")
onClicked: {
if (readlater) {
root.unmarkedReadlater();
} else {
root.markedReadlater();
}
}
}
MenuItem {
text: liked ? qsTr("Unlike") : qsTr("Like")
enabled: settings.showBroadcast && app.isOldReader
visible: enabled
onClicked: {
if (liked) {
root.unmarkedLike();
} else {
root.markedLike();
}
}
}
MenuItem {
text: broadcast ? qsTr("Unshare") : qsTr("Share with followers")
enabled: settings.showBroadcast && app.isOldReader &&
!root.friendStream
visible: enabled
onClicked: {
if (broadcast) {
root.unmarkedBroadcast();
} else {
root.markedBroadcast();
}
}
}
MenuItem {
text: qsTr("Open in viewer")
visible: enabled
//enabled: settings.clickBehavior !== 0
onClicked: {
root.openInViewer();
root.expanded = false;
}
}
MenuItem {
text: qsTr("Open in browser")
visible: enabled
//enabled: settings.clickBehavior !== 1
onClicked: {
root.openInBrowser();
root.expanded = false;
}
}
MenuItem {
text: qsTr("Show feed content")
visible: enabled
//enabled: settings.clickBehavior !== 2
onClicked: {
root.showFeedContent();
root.expanded = false;
menu.hide()
}
}
}

View file

@ -248,6 +248,7 @@ Page {
landscapeMode: root.landscapeMode
onlineurl: model.link
offlineurl: cache.getUrlbyId(model.uid)
evaluation: ai.evaluation(model.uid)
signal singleEntryClicked
signal doubleEntryClicked
@ -431,6 +432,10 @@ Page {
onMarkedReadlater: {
entryModel.setData(index, "readlater", 1, "");
if (evaluation !== -1) {
evaluation = 1
ai.addEvaluation(model.uid, model.title, evaluation)
}
}
onUnmarkedReadlater: {
@ -464,18 +469,30 @@ Page {
onOpenInBrowser: {
if (!check()) {
return;
return
}
openInExaternalBrowser(model.index, model.link, model.uid);
openInExaternalBrowser(model.index, model.link, model.uid)
}
onOpenInViewer: {
if (!check()) {
return;
return
}
openEntryInViewer();
openEntryInViewer()
}
onEvaluated: {
ai.addEvaluation(model.uid, model.title, evaluation)
}
onShare: {
pageStack.push(Qt.resolvedUrl("ShareLinkPage.qml"),{"link": model.link, "linkTitle": model.title})
}
onPocketAdd: {
pocket.add(model.link, model.title)
}
}

View file

@ -80,8 +80,15 @@ Page {
Connections {
target: Qt.application
onActiveChanged: {
if(!Qt.application.active && settings.powerSaveMode) {
pageStack.pop();
if(!Qt.application.active) {
if (settings.powerSaveMode && root.status === PageStatus.Active) {
pageStack.pop()
return
}
if (root.status !== PageStatus.Active) {
pageStack.pop(pageStack.previousPage(root), PageStackAction.Immediate)
return
}
}
}
}
@ -95,6 +102,10 @@ Page {
}
}
function share() {
pageStack.push(Qt.resolvedUrl("ShareLinkPage.qml"),{"link": root.onlineUrl, "linkTitle": root.title});
}
function check() {
// Not allowed while Syncing
if (dm.busy || fetcher.busy || dm.removerBusy) {
@ -409,6 +420,23 @@ Page {
}
}
IconMenuItem {
text: qsTr("Add to Pocket")
visible: settings.pocketEnabled
enabled: settings.pocketEnabled && dm.online
icon.source: "image://icons/icon-m-pocket"
busy: pocket.busy
onClicked: {
pocket.add(root.onlineUrl, root.title)
}
}
IconMenuItem {
text: qsTr("Share link")
icon.source: "image://theme/icon-m-share"
onClicked: root.share()
}
IconBarItem {
text: qsTr("Toggle Like")
icon: root.liked ? "image://icons/icon-m-like-selected" : "image://icons/icon-m-like"

View file

@ -25,6 +25,7 @@ Item {
property alias icon: iconButton.icon
property alias enabled: iconButton.enabled
property alias text: lbl.text
property bool busy: false
width: iconButton.width
height: iconButton.height
@ -50,6 +51,21 @@ Item {
horizontalAlignment: Text.AlignHCenter
}
Image {
anchors.fill: parent
opacity: root.busy ? 1.0 : 0.0
visible: opacity > 0.0
Behavior on opacity { FadeAnimation {} }
source: "image://theme/graphic-busyindicator-medium"
RotationAnimation on rotation {
loops: Animation.Infinite
from: 0
to: 360
duration: 1200
running: root.busy && Qt.application.active
}
}
IconButton {
id: iconButton
onClicked: root.clicked();

194
sailfish/qml/Pocket.qml Normal file
View file

@ -0,0 +1,194 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
Item {
id: root
property bool busy: false
readonly property string consumer_key: settings.pocketConsumerKey()
readonly property string redirect_uri: "kaktus:authorizationFinished"
readonly property string request_url: "https://getpocket.com/v3/oauth/request"
readonly property string authorize_url: "https://getpocket.com/auth/authorize"
readonly property string oauth_authorize_url: "https://getpocket.com/v3/oauth/authorize"
readonly property string add_url: "https://getpocket.com/v3/add"
property string request_token
property string access_token: settings.pocketToken
function _split(tags) {
var _tags = tags.split(",")
var tagsArr = []
for (var i = 0; i < _tags.length; i++) {
var tag = _tags[i].trim().toLowerCase()
if (tag !== "") {
if (tagsArr.indexOf(tag) === -1) {
tagsArr.push(tag)
}
}
}
return tagsArr
}
function fixTags(tags) {
return _split(tags).join(", ")
}
function enable() {
busy = true
settings.pocketToken = ""
_requestToken(function() {
console.log("request token: " + root.request_token)
pageStack.push(Qt.resolvedUrl("PocketAuthWebViewPage.qml"))
}, function(code) {
console.log("X-Error:" + xhr.getResponseHeader("X-Error"))
console.log("Error while requesting Pocket token, X-Error-Code:" + code)
notification.show(qsTr("Pocket authorization has failed."))
busy = false
})
}
function check() {
_accessToken(function() {
console.log("access token: " + root.access_token)
settings.pocketToken = root.access_token
settings.pocketEnabled = true
notification.show(qsTr("Pocket authorization was successful."))
busy = false
}, function(code) {
console.log("X-Error:" + xhr.getResponseHeader("X-Error"))
console.log("Error while requesting Pocket access token, X-Error-Code:" + code)
notification.show(qsTr("Pocket authorization has failed."))
busy = false
})
}
function cancel() {
busy = false
}
function getAuthUrl() {
return authorize_url + "?request_token=" + request_token + "&redirect_uri=" + redirect_uri
}
function add(url, title) {
if (settings.pocketQuickAdd)
quickAdd(url, title, settings.pocketTags)
else
pageStack.push(Qt.resolvedUrl("PocketDialog.qml"),{"url": url, "title": title})
}
function quickAdd(url, title, tags) {
tags = tags == null ? "" : fixTags(tags)
title = title == null ? "" : title.trim()
var req = {
consumer_key: consumer_key,
access_token: access_token,
url: url,
title: title,
tags: tags
}
var xhr = new XMLHttpRequest()
xhr.open("POST", add_url)
xhr.setRequestHeader("Content-Type", "application/json; charset=UTF8")
xhr.setRequestHeader("X-Accept", "application/json")
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
busy = false
if (xhr.status === 200) {
notification.show(qsTr("Article has been successfully added to Pocket."))
settings.pocketTagsHistory = _split(settings.pocketTagsHistory + "," + tags).sort().join(",")
} else {
console.log("X-Error:" + xhr.getResponseHeader("X-Error"))
var code = xhr.getResponseHeader("X-Error-Code")
console.log("Error while adding article to Pocket, X-Error-Code:" + code)
notification.show(qsTr("Error while adding article to Pocket."))
}
}
}
busy = true
xhr.send(JSON.stringify(req))
}
// Private
function _requestToken(ok, error) {
var req = {
consumer_key: consumer_key,
redirect_uri: redirect_uri
}
var xhr = new XMLHttpRequest()
xhr.open("POST", request_url)
xhr.setRequestHeader("Content-Type", "application/json; charset=UTF8")
xhr.setRequestHeader("X-Accept", "application/json")
xhr.onreadystatechange = function () {
/*console.log("xhr.onreadystatechange")
console.log(" xhr.readyState: " + xhr.readyState)
console.log(" xhr.status: " + xhr.status)
console.log(" xhr.responseType: " + xhr.responseType)
console.log(" xhr.responseURL : " + xhr.responseURL )
console.log(" xhr.statusText: " + xhr.statusText)
console.log(" xhr.responseText: " + xhr.responseText)
console.log(" X-Error-Code:" + xhr.getResponseHeader("X-Error-Code"))*/
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var res = JSON.parse(xhr.responseText)
root.request_token = res.code
ok()
} else {
error(xhr.getResponseHeader("X-Error-Code"))
}
}
}
xhr.send(JSON.stringify(req))
}
function _accessToken(ok, error) {
var req = {
consumer_key: consumer_key,
code: request_token
}
var xhr = new XMLHttpRequest()
xhr.open("POST", oauth_authorize_url)
xhr.setRequestHeader("Content-Type", "application/json; charset=UTF8")
xhr.setRequestHeader("X-Accept", "application/json")
xhr.onreadystatechange = function () {
/*console.log("xhr.onreadystatechange")
console.log(" xhr.readyState: " + xhr.readyState)
console.log(" xhr.status: " + xhr.status)
console.log(" xhr.responseType: " + xhr.responseType)
console.log(" xhr.responseURL : " + xhr.responseURL )
console.log(" xhr.statusText: " + xhr.statusText)
console.log(" xhr.responseText: " + xhr.responseText)
console.log(" X-Error-Code:" + xhr.getResponseHeader("X-Error-Code"))*/
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var res = JSON.parse(xhr.responseText)
root.access_token = res.access_token
ok()
} else {
error(xhr.getResponseHeader("X-Error-Code"))
}
}
}
xhr.send(JSON.stringify(req))
}
}

View file

@ -0,0 +1,153 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.1
import Sailfish.Silica 1.0
import QtWebKit 3.0
Page {
id: root
property bool showBar: false
property bool doPop: false
property bool done: false
function init() {
view.url = pocket.getAuthUrl()
}
Component.onCompleted: {
init()
}
onForwardNavigationChanged: {
if (forwardNavigation)
forwardNavigation = false
}
showNavigationIndicator: false
allowedOrientations: {
switch (settings.allowedOrientations) {
case 1:
return Orientation.Portrait;
case 2:
return Orientation.Landscape;
}
return Orientation.Landscape | Orientation.Portrait;
}
onStatusChanged: {
if (status === PageStatus.Active && doPop) {
pageStack.pop()
}
}
SilicaWebView {
id: view
anchors {left: parent.left; right: parent.right}
height: controlbar.open ? parent.height - controlbar.height : parent.height
clip: true
_cookiesEnabled: true
experimental.preferences.offlineWebApplicationCacheEnabled: false
experimental.preferences.localStorageEnabled: true
//experimental.preferences.privateBrowsingEnabled: true
onLoadingChanged: {
switch (loadRequest.status) {
case WebView.LoadStartedStatus:
proggressPanel.text = qsTr("Loading page content...");
proggressPanel.open = true
break;
case WebView.LoadSucceededStatus:
proggressPanel.open = false
break;
case WebView.LoadFailedStatus:
proggressPanel.open = false
break;
default:
proggressPanel.open = false
}
}
onNavigationRequested: {
if (!Qt.application.active) {
request.action = WebView.IgnoreRequest
}
}
onUrlChanged: {
console.log("Url changed:", url)
var surl = url.toString()
if (surl === "https://getpocket.com/a/") {
init()
return
}
if (surl === "kaktus:authorizationFinished") {
done = true
pocket.check()
if (status === PageStatus.Active) {
pageStack.pop()
} else {
doPop = true
}
}
}
}
IconBar {
id: controlbar
flickable: view
theme: "black"
IconBarItem {
text: qsTr("Back")
icon: "image://theme/icon-m-back"
onClicked: view.canGoBack ? view.goBack() : pageStack.pop()
}
}
ProgressPanel {
id: proggressPanel
transparent: false
anchors.left: parent.left
anchors.bottom: parent.bottom
cancelable: true
onCloseClicked: view.stop()
}
// Workaround for 'High Power Consumption' webkit bug
Connections {
target: Qt.application
onActiveChanged: {
if(!Qt.application.active && settings.powerSaveMode) {
pageStack.pop()
}
}
}
Component.onDestruction: {
if (!done) {
pocket.cancel()
}
}
}

View file

@ -0,0 +1,162 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import Sailfish.Silica 1.0
Dialog {
id: root
property bool showBar: false
property string url
property string title
property var selectedTags: []
function checkTags() {
selectedTags = pocket._split(tags.text)
tags.text = selectedTags.join(", ")
tags.cursorPosition = tags.text.length
}
function addTag(tag) {
tags.text += tags.text === "" ? tag : ", " + tag
checkTags()
}
allowedOrientations: {
switch (settings.allowedOrientations) {
case 1:
return Orientation.Portrait;
case 2:
return Orientation.Landscape;
}
return Orientation.Landscape | Orientation.Portrait;
}
onAccepted: {
pocket.quickAdd(url, title, tags.text)
}
SilicaFlickable {
id: flick
anchors {
left: parent.left
right: parent.right
top: parent.top
}
height: app.flickHeight
contentHeight: content.height
Column {
id: content
anchors {
left: parent.left
right: parent.right
}
spacing: Theme.paddingMedium
DialogHeader {
acceptText : qsTr("Add to Pocket")
}
TextArea {
id: tags
anchors.left: parent.left; anchors.right: parent.right
inputMethodHints: Qt.ImhNoAutoUppercase
wrapMode: TextEdit.WordWrap
placeholderText: qsTr("Insert comma seperated tags")
label: qsTr("Tags")
text: settings.pocketTags
EnterKey.iconSource: "image://theme/icon-m-enter-close"
EnterKey.onClicked: parent.focus = true
onTextChanged: timer.restart()
onFocusChanged: {
if (!focus) {
timer.stop()
root.checkTags()
}
}
Timer {
id: timer
interval: 1000
onTriggered: {
timer.stop()
root.checkTags()
}
}
}
SectionHeader {
text: qsTr("Previously used tags")
visible: settings.pocketTagsHistory !== ""
}
Flow {
anchors {
left: parent.left
right: parent.right
leftMargin: Theme.horizontalPageMargin
rightMargin: Theme.horizontalPageMargin
}
spacing: Theme.paddingMedium
Repeater {
model: settings.pocketTagsHistory.split(",")
Item {
width: tagLabel.width + 2 * Theme.paddingSmall
height: tagLabel.height
enabled: root.selectedTags.indexOf(modelData) ===-1
Label {
id: tagLabel
anchors.centerIn: parent
text: modelData
color: parent.enabled ?
mouse.pressed ? Theme.highlightColor : Theme.primaryColor :
Theme.secondaryColor
}
MouseArea {
id: mouse
anchors.fill: parent
onClicked: {
root.addTag(modelData)
}
}
}
}
}
}
}
VerticalScrollDecorator {
flickable: flick
}
}

View file

@ -37,25 +37,41 @@ Page {
ActiveDetector {}
SilicaListView {
anchors { top: parent.top; left: parent.left; right: parent.right }
clip: true
height: app.flickHeight
header: PageHeader {
title: qsTr("Settings")
SilicaFlickable {
id: flick
anchors {
left: parent.left
right: parent.right
top: parent.top
}
height: app.flickHeight
clip: true
contentHeight: content.height
Column {
id: content
anchors {
left: parent.left
right: parent.right
}
spacing: Theme.paddingMedium
PageHeader {
title: qsTr("Settings")
}
model: VisualItemModel {
id: model
Item {
anchors { left: parent.left; right: parent.right}
height: Math.max(icon.height, label.height)
Image {
id: icon
anchors { right: label.left; rightMargin: Theme.paddingMedium }
anchors {
right: label.left
rightMargin: Theme.paddingMedium
verticalCenter: parent.verticalCenter
}
source: app.isNetvibes ? "nv.png" :
app.isOldReader ? "oldreader.png" : "feedly.png"
width: Theme.iconSizeMedium
@ -65,14 +81,17 @@ Page {
Label {
id: label
anchors { right: parent.right; rightMargin: Theme.paddingLarge}
anchors {
right: parent.right
rightMargin: Theme.paddingLarge
verticalCenter: parent.verticalCenter
}
text: app.isNetvibes ? "Netvibes":
app.isOldReader ? "Old Reader" : "Feedly"
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignRight
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeSmall
y: Theme.paddingSmall/2
}
}
@ -420,6 +439,15 @@ Page {
}
}
ButtonItem {
button.text: qsTr("Delete cookies")
description: qsTr("Clear web viewer cache and cookies. Changes will take effect after restart.")
button.onClicked: {
utils.resetQtWebKit()
notification.show(qsTr("Cache and cookies have been deleted."))
}
}
SectionHeader {
text: qsTr("UI")
}
@ -692,22 +720,6 @@ Page {
settings.showOldestFirst = true; break;
}
}
}
ComboBox {
width: root.width
label: qsTr("Context menu style")
currentIndex: settings.iconContextMenu ? 0 : 1
menu: ContextMenu {
MenuItem { text: qsTr("Icons") }
MenuItem { text: qsTr("Text") }
}
onCurrentIndexChanged: {
settings.iconContextMenu = (currentIndex == 0 ? true : false);
}
}
ComboBox {
@ -760,7 +772,17 @@ Page {
qsTr("List of articles can be filtered to display all articles, unread and starred or only unread.")
}
TextSwitch {
text: qsTr("Social features")
enabled: app.isOldReader
description: qsTr("Following Old Reader's social features will be enabled: Following folder, Sharing article with followers, Like option, Liked articles view mode.")
onCheckedChanged: {
settings.showBroadcast = checked;
}
Component.onCompleted: {
checked = settings.showBroadcast;
}
}
TextSwitch {
text: qsTr("Expanded items")
@ -786,18 +808,6 @@ Page {
}
}
TextSwitch {
text: qsTr("Social features")
enabled: app.isOldReader
description: qsTr("Following Old Reader's social features will be enabled: Following folder, Sharing article with followers, Like option, Liked articles view mode.")
onCheckedChanged: {
settings.showBroadcast = checked;
}
Component.onCompleted: {
checked = settings.showBroadcast;
}
}
TextSwitch {
text: qsTr("Power save mode")
description: qsTr("When the phone or app goes to the idle state, "+
@ -824,10 +834,127 @@ Page {
onCurrentIndexChanged: settings.allowedOrientations = currentIndex
}
SectionHeader {
text: qsTr("Pocket")
}
TextSwitchWithIcon {
text: qsTr("Pocket integration")
description: qsTr("Pocket is an Internet tool for saving articles to read later. Integration implemented in Kaktus provides \"Add to Pocket\" button in the articles list and in the web viewer.")
iconSource: "image://icons/icon-m-pocket"
checked: settings.pocketEnabled
busy: pocket.busy
enabled: dm.online
automaticCheck: false
onClicked: {
if (checked) {
settings.pocketEnabled = false
} else {
pocket.enable()
}
}
}
TextFieldItem {
id: pocketTagsField
enabled: settings.pocketEnabled
textField.placeholderText: qsTr("Default tags")
textField.label: qsTr("Default tags")
textField.labelVisible: false
description: qsTr("List of comma seperated tags that will be automatically inserted when you add article to Pocket.")
textField.inputMethodHints: Qt.ImhNoAutoUppercase
textField.onTextChanged: timer.restart()
Component.onCompleted: textField.text = settings.pocketTags
EnterKey.iconSource: "image://theme/icon-m-enter-close"
EnterKey.onClicked: {
checkTags()
parent.focus = true
}
onFocusChanged: {
if (!focus)
checkTags()
}
function checkTags() {
timer.stop()
pocketTagsField.textField.text = pocket.fixTags(pocketTagsField.textField.text)
settings.pocketTags = pocketTagsField.textField.text
}
Timer {
id: timer
interval: 1000
onTriggered: pocketTagsField.checkTags()
}
}
TextSwitch {
text: qsTr("Quick adding")
description: qsTr("If enabled, article will be send to Pocket immediately after you click on \"Add to Pocket\" button, so without any confirmation dialog. All tags from \"Default tags\" field will be automatically added.")
checked: settings.pocketQuickAdd
enabled: settings.pocketEnabled
onCheckedChanged: {
settings.pocketQuickAdd = checked
}
}
ButtonItem {
enabled: settings.pocketEnabled
button.text: qsTr("Delete saved tags")
button.onClicked: {
settings.pocketTagsHistory = ""
notification.show(qsTr("Saved tags have been deleted."))
}
}
/*SectionHeader {
text: qsTr("Experimental")
}
Column {
x: Theme.horizontalPageMargin
spacing: Theme.paddingMedium
Row {
spacing: Theme.paddingMedium
Image {
source: "image://icons/icon-m-good"
height: Theme.iconSizeSmall
width: Theme.iconSizeSmall
anchors.verticalCenter: parent.verticalCenter
}
Label {
text: ai.evaluationCount(1)
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
spacing: Theme.paddingMedium
Image {
source: "image://icons/icon-m-bad"
height: Theme.iconSizeSmall
width: Theme.iconSizeSmall
anchors.verticalCenter: parent.verticalCenter
}
Label {
text: ai.evaluationCount(-1)
anchors.verticalCenter: parent.verticalCenter
}
}
}*/
/*SectionHeader {
text: qsTr("Other")
}
Button {
text: qsTr("Show User Guide")
anchors.horizontalCenter: parent.horizontalCenter
@ -836,13 +963,12 @@ Page {
}
}*/
Item {
height: Theme.paddingMedium
width: height
}
Spacer {}
}
}
VerticalScrollDecorator {}
VerticalScrollDecorator {
flickable: flick
}
}

View file

@ -0,0 +1,55 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import Sailfish.Silica 1.0
import Sailfish.TransferEngine 1.0
Page {
id: root
property string link
property string linkTitle
readonly property bool showBar: false
ShareMethodList {
id: shareMethodList
anchors { top: parent.top; left: parent.left; right: parent.right }
clip: true
height: app.flickHeight
header: PageHeader {
title: qsTr("Share link")
}
filter: "text/x-url"
content: {
"type": "text/x-url",
"status": root.link,
"linkTitle": root.linkTitle
}
ViewPlaceholder {
enabled: shareMethodList.model.count === 0
text: qsTr("No sharing accounts available. You can add accounts in settings")
}
}
}

View file

@ -0,0 +1,34 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import Sailfish.Silica 1.0
IconButton {
id: root
property int iconSize: Theme.iconSizeSmall
property int marginV: Theme.paddingMedium
property int marginH: Theme.paddingMedium
icon.height: iconSize
icon.width: iconSize
width: Theme.iconSizeSmall + marginH
height: Theme.iconSizeSmall + marginV
}

26
sailfish/qml/Spacer.qml Normal file
View file

@ -0,0 +1,26 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import Sailfish.Silica 1.0
Item {
width: Theme.itemSizeSmall
height: Theme.itemSizeSmall
}

View file

@ -0,0 +1,58 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import Sailfish.Silica 1.0
Item {
id: root
property alias textArea: _textArea
property alias description: desc.text
opacity: enabled ? 1.0 : 0.4
height: col.height + 4*Theme.paddingSmall
anchors {left: parent.left; right: parent.right}
Column {
id: col
anchors {
top: parent.top; topMargin: 2*Theme.paddingSmall
left: parent.left; right: parent.right
}
TextArea {
id: _textArea
enabled: root.enabled
anchors {left: parent.left; right: parent.right}
}
Label {
id: desc
anchors {
left: parent.left; right: parent.right;
leftMargin: Theme.horizontalPageMargin; rightMargin: Theme.horizontalPageMargin
}
wrapMode: Text.Wrap
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
}
}
}

View file

@ -0,0 +1,58 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import Sailfish.Silica 1.0
Item {
id: root
property alias textField: _textField
property alias description: desc.text
opacity: enabled ? 1.0 : 0.4
height: col.height + 4*Theme.paddingSmall
anchors {left: parent.left; right: parent.right}
Column {
id: col
anchors {
top: parent.top; topMargin: 2*Theme.paddingSmall
left: parent.left; right: parent.right
}
TextField{
id: _textField
enabled: root.enabled
anchors {left: parent.left; right: parent.right}
}
Label {
id: desc
anchors {
left: parent.left; right: parent.right;
leftMargin: Theme.horizontalPageMargin; rightMargin: Theme.horizontalPageMargin
}
wrapMode: Text.Wrap
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
}
}
}

View file

@ -27,6 +27,9 @@ Item {
property alias description: textswitch.description
property alias checked: textswitch.checked
property alias iconSource: icon.source
property alias automaticCheck: textswitch.automaticCheck
property alias busy: textswitch.busy
signal clicked
anchors.left: parent.left; anchors.right: parent.right
height: Math.max(textswitch.height, icon.height)
@ -35,6 +38,7 @@ Item {
id: textswitch
anchors.verticalCenter: parent.verticalCenter
width: parent.width - icon.width - icon.anchors.rightMargin
onClicked: root.clicked()
}
Image {

View file

@ -50,6 +50,10 @@ Page {
property bool nightModePossible: true
property bool autoReaderMode: settings.readerMode
function share() {
pageStack.push(Qt.resolvedUrl("ShareLinkPage.qml"),{"link": root.onlineUrl, "linkTitle": root.title});
}
function openUrlEntryInBrowser(url) {
notification.show(qsTr("Launching an external browser..."))
Qt.openUrlExternally(url)
@ -138,6 +142,7 @@ Page {
}
function messageReceivedHandler(message) {
//console.log("view.url: " + view.url)
if (message.type === "inited") {
// NightMode
root.nightModePossible = true
@ -179,8 +184,6 @@ Page {
view.experimental.postMessage(JSON.stringify({ "type": message, "data": data }));
}
ActiveDetector {}
showNavigationIndicator: false
allowedOrientations: {
@ -199,8 +202,15 @@ Page {
Connections {
target: Qt.application
onActiveChanged: {
if(!Qt.application.active && settings.powerSaveMode) {
pageStack.pop();
if(!Qt.application.active) {
if (settings.powerSaveMode && root.status === PageStatus.Active) {
pageStack.pop()
return
}
if (root.status !== PageStatus.Active) {
pageStack.pop(pageStack.previousPage(root), PageStackAction.Immediate)
return
}
}
}
}
@ -403,6 +413,23 @@ Page {
}
}
IconMenuItem {
text: qsTr("Add to Pocket")
visible: settings.pocketEnabled
enabled: settings.pocketEnabled && dm.online
icon.source: "image://icons/icon-m-pocket"
busy: pocket.busy
onClicked: {
pocket.add(root.onlineUrl, root.title)
}
}
IconMenuItem {
text: qsTr("Share link")
icon.source: "image://theme/icon-m-share"
onClicked: root.share()
}
IconBarItem {
text: qsTr("Toggle Like")
icon: root.liked ? "image://icons/icon-m-like-selected" : "image://icons/icon-m-like"

View file

@ -451,5 +451,8 @@ ApplicationWindow {
x: app.orientation==Orientation.Portrait ? 0 : app.width
}
Pocket {
id: pocket
}
}

View file

@ -8,6 +8,14 @@
# * date Author's Name <author's email> version-release
# - Summary of changes
* Wed Mar 15 2017 Michal Kosciesza 2.6.0-2
- Pocket integration
- New app icon
* Sun Feb 19 2017 Michal Kosciesza 2.6.0-1
- Share link option (only in openrepos package)
- ES translation update
* Tue Feb 09 2017 Michal Kosciesza 2.5.3-1
- FIX: Sync process did not download all saved articles in Netvibes

View file

@ -13,8 +13,8 @@ Name: harbour-kaktus
%{!?qtc_make:%define qtc_make make}
%{?qtc_builddir:%define _builddir %qtc_builddir}
Summary: Kaktus
Version: 2.5.3
Release: 1
Version: 2.6.0
Release: 2
Group: Qt/Qt
License: LICENSE
URL: https://github.com/mkiol/kaktus

View file

@ -1,7 +1,7 @@
Name: harbour-kaktus
Summary: Kaktus
Version: 2.5.3
Release: 1
Version: 2.6.0
Release: 2
# The contents of the Group field should be one of the groups listed here:
# http://gitorious.org/meego-developer-tools/spectacle/blobs/master/data/GROUPS
Group: Qt/Qt

180
sailfish/src/ai.cpp Normal file
View file

@ -0,0 +1,180 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QSqlQuery>
#include "ai.h"
#include "settings.h"
Ai::Ai(QObject *parent) : QObject(parent)
{
}
Ai::~Ai()
{
db.close();
QSqlDatabase::removeDatabase("qt_sql_kaktusai_connection");
}
void Ai::init()
{
if (!openDB()) {
qWarning() << "Error when trying to open AI-DB.";
return;
}
int ver = version();
if (Ai::VERSION != ver) {
if (ver != 0) {
qWarning() << "AI-DB version mismatch. Exising version is"
<< ver << ", but required is" << Ai::VERSION;
deleteDB();
init();
if (!openDB()) {
qWarning() << "Error when trying to open AI-DB.";
return;
}
} else {
qWarning() << "AI-DB file doesn't exist.";
}
createDB();
}
}
bool Ai::openDB()
{
Settings *s = Settings::instance();
db = QSqlDatabase::addDatabase("QSQLITE","qt_sql_kaktusai_connection");
dbFilePath = s->getSettingsDir();
dbFilePath.append(QDir::separator()).append("ai.db");
dbFilePath = QDir::toNativeSeparators(dbFilePath);
db.setDatabaseName(dbFilePath);
return db.open();
}
void Ai::deleteDB()
{
db.close();
QSqlDatabase::removeDatabase("qt_sql_kaktusai_connection");
if (!QFile::exists(dbFilePath)) {
qWarning() << "AI-DB file doesn't exist.";
return;
}
QFile::remove(dbFilePath);
}
int Ai::version()
{
QSqlQuery query(db);
query.exec("PRAGMA user_version");
query.first();
return query.value(0).toInt();
}
void Ai::createDB()
{
QSqlQuery query(db);
query.exec("PRAGMA journal_mode = MEMORY");
query.exec("PRAGMA synchronous = OFF");
query.exec(QString("PRAGMA user_version = %1").arg(Ai::VERSION));
query.exec("CREATE TABLE IF NOT EXISTS entries ("
"id VARCHAR(50) PRIMARY KEY, "
"title TEXT, "
"evaluation INTEGER DEFAULT 0, "
"status INTEGER DEFAULT 1, "
"last_update TIMESTAMP "
");");
query.exec("CREATE INDEX IF NOT EXISTS entries_evaluation "
"ON entries(id, evaluation);");
}
void Ai::addEvaluation(const QString &id, const QString &title, int evaluation)
{
if (!db.isOpen()) {
qWarning() << "AI-DB is not open.";
return;
}
QSqlQuery query(db);
query.prepare("INSERT OR REPLACE INTO entries (id, title, evaluation, last_update) VALUES (?,?,?,?)");
query.addBindValue(id);
query.addBindValue(title);
query.addBindValue(evaluation);
query.addBindValue(QDateTime::currentDateTimeUtc().toTime_t());
if (!query.exec())
qWarning() << "SQL error:" << query.lastQuery();
}
int Ai::evaluationCount(const int evaluation)
{
if (!db.isOpen()) {
qWarning() << "AI-DB is not open.";
return 0;
}
QSqlQuery query(db);
query.prepare("SELECT count(*) FROM entries WHERE evaluation = ?");
query.addBindValue(evaluation);
if (!query.exec()) {
qWarning() << "SQL error:" << query.lastQuery();
return 0;
}
while (query.next()) {
return query.value(0).toInt();
}
return 0;
}
int Ai::evaluation(const QString &id)
{
if (!db.isOpen()) {
qWarning() << "AI-DB is not open.";
return 0;
}
QSqlQuery query(db);
query.prepare("SELECT evaluation FROM entries WHERE id = ? LIMIT 1");
query.addBindValue(id);
if (!query.exec()) {
qWarning() << "SQL error:" << query.lastQuery();
return 0;
}
while (query.next()) {
return query.value(0).toInt();
}
return 0;
}

49
sailfish/src/ai.h Normal file
View file

@ -0,0 +1,49 @@
/*
Copyright (C) 2017 Michal Kosciesza <michal@mkiol.net>
This file is part of Kaktus.
Kaktus 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.
Kaktus 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 Kaktus. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AI_H
#define AI_H
#include <QObject>
#include <QSqlDatabase>
#include <QString>
class Ai : public QObject
{
Q_OBJECT
public:
explicit Ai(QObject *parent = 0);
~Ai();
Q_INVOKABLE void init();
Q_INVOKABLE void addEvaluation(const QString &id, const QString &title, int evaluation);
Q_INVOKABLE int evaluation(const QString &id);
Q_INVOKABLE int evaluationCount(const int evaluation);
private:
const static int VERSION = 1;
QSqlDatabase db;
QString dbFilePath;
bool openDB();
int version();
void deleteDB();
void createDB();
};
#endif // AI_H

View file

@ -27,6 +27,12 @@ DatabaseManager::DatabaseManager(QObject *parent) :
QObject(parent)
{}
DatabaseManager::~DatabaseManager()
{
db.close();
QSqlDatabase::removeDatabase("qt_sql_kaktus_connection");
}
bool DatabaseManager::isSynced()
{
if (db.isOpen()) {

View file

@ -173,6 +173,7 @@ public:
};
explicit DatabaseManager(QObject *parent = 0);
~DatabaseManager();
Q_INVOKABLE void init();
Q_INVOKABLE void newInit();

View file

@ -28,6 +28,7 @@
#include <sailfishapp.h>
#include <QFile>
#include <QDir>
#include <QStandardPaths>
#endif
#ifdef ANDROID
#include <QQmlApplicationEngine>
@ -43,14 +44,15 @@
#include "utils.h"
#include "settings.h"
#include "networkaccessmanagerfactory.h"
#include "ai.h"
static const char *APP_NAME = "Kaktus";
static const char *AUTHOR = "Michal Kosciesza <michal@mkiol.net>";
static const char *PAGE = "https://github.com/mkiol/kaktus";
#ifdef KAKTUS_LIGHT
static const char *VERSION = "2.5.3 (light edition)";
static const char *VERSION = "2.6.0 (light edition)";
#else
static const char *VERSION = "2.5.3";
static const char *VERSION = "2.6.0";
#endif
@ -94,24 +96,26 @@ int main(int argc, char *argv[])
#ifdef SAILFISH
//-- temp fix --
// config file
if (QFile::exists("/home/nemo/.config/harbour-kaktus/Kaktus.conf")) {
QString path = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)).path();
if (QFile::exists(path + "/harbour-kaktus/Kaktus.conf")) {
qWarning() << "Old config file exists -> doing migration";
QFile::remove("/home/nemo/.config/harbour-kaktus/harbour-kaktus.conf");
if (QFile::copy("/home/nemo/.config/harbour-kaktus/Kaktus.conf",
"/home/nemo/.config/harbour-kaktus/harbour-kaktus.conf")) {
QFile::remove("/home/nemo/.config/harbour-kaktus/Kaktus.conf");
QFile::remove(path + "/harbour-kaktus/harbour-kaktus.conf");
if (QFile::copy(path + "/harbour-kaktus/Kaktus.conf",
path + "/harbour-kaktus/harbour-kaktus.conf")) {
QFile::remove(path + "/harbour-kaktus/Kaktus.conf");
}
}
// cache file
QDir newDir("/home/nemo/.cache/harbour-kaktus/Kaktus/");
path = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation)).path();
QDir newDir(path + "/harbour-kaktus/Kaktus/");
if (newDir.exists()) {
qWarning() << "Old cache dir exists -> doing migration";
QDir oldDir("/home/nemo/.cache/harbour-kaktus/harbour-kaktus/");
QDir oldDir(path + "/harbour-kaktus/harbour-kaktus/");
if (oldDir.exists()) {
oldDir.removeRecursively();
}
qDebug() << newDir.rename("/home/nemo/.cache/harbour-kaktus/Kaktus/",
"/home/nemo/.cache/harbour-kaktus/harbour-kaktus/");
qDebug() << newDir.rename(path + "/harbour-kaktus/Kaktus/",
path + "/harbour-kaktus/harbour-kaktus/");
}
//--
#endif
@ -129,6 +133,7 @@ int main(int argc, char *argv[])
DatabaseManager db; settings->db = &db;
DownloadManager dm; settings->dm = &dm;
CacheServer cache(&db); settings->cache = &cache;
Ai ai; ai.init();
QObject::connect(engine.data(), SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));
@ -139,6 +144,7 @@ int main(int argc, char *argv[])
context->setContextProperty("utils", &utils);
context->setContextProperty("dm", &dm);
context->setContextProperty("cache", &cache);
context->setContextProperty("ai", &ai);
context->setContextProperty("settings", settings);
#ifdef SAILFISH

View file

@ -62,6 +62,11 @@ Settings* Settings::instance()
return Settings::inst;
}
QString Settings::pocketConsumerKey()
{
return pocket_consumer_key;
}
const QList<QVariant> Settings::viewModeHistory()
{
return settings.value("viewmodehistory").toList();
@ -119,19 +124,6 @@ bool Settings::getShowOldestFirst()
return settings.value("showoldestfirst", false).toBool();
}
void Settings::setIconContextMenu(bool value)
{
if (getIconContextMenu() != value) {
settings.setValue("iconcontextmenu", value);
emit showOldestFirstChanged();
}
}
bool Settings::getIconContextMenu()
{
return settings.value("iconcontextmenu", true).toBool();
}
void Settings::setShowBroadcast(bool value)
{
if (getShowBroadcast() != value) {
@ -192,6 +184,96 @@ bool Settings::getReaderMode()
return settings.value("readermode", false).toBool();
}
void Settings::setPocketEnabled(bool value)
{
if (getPocketEnabled() != value) {
settings.setValue("pocketenabled", value);
emit pocketEnabledChanged();
}
}
bool Settings::getPocketEnabled()
{
return settings.value("pocketenabled", false).toBool();
}
void Settings::setPocketQuickAdd(bool value)
{
if (getPocketQuickAdd() != value) {
settings.setValue("pocketquickadd", value);
emit pocketQuickAddChanged();
}
}
bool Settings::getPocketQuickAdd()
{
return settings.value("pocketquickadd", false).toBool();
}
void Settings::setPocketFavorite(bool value)
{
if (getPocketFavorite() != value) {
settings.setValue("pocketfavorite", value);
emit pocketFavoriteChanged();
}
}
bool Settings::getPocketFavorite()
{
return settings.value("pocketfavorite", false).toBool();
}
void Settings::setPocketToken(const QString &value)
{
if (getPocketToken() != value) {
SimpleCrypt crypto(KEY);
QString encryptedToken = crypto.encryptToString(value);
if (!crypto.lastError() == SimpleCrypt::ErrorNoError) {
emit error(532);
return;
}
settings.setValue("pockettoken", encryptedToken);
emit pocketTokenChanged();
}
}
QString Settings::getPocketToken()
{
SimpleCrypt crypto(KEY);
QString plainToken = crypto.decryptToString(settings.value("pockettoken", "").toString());
if (!crypto.lastError() == SimpleCrypt::ErrorNoError) {
emit error(531);
return "";
}
return plainToken;
}
void Settings::setPocketTags(const QString &value)
{
if (getPocketTags() != value) {
settings.setValue("pockettags", value);
emit pocketTagsChanged();
}
}
QString Settings::getPocketTags()
{
return settings.value("pockettags", "").toString();
}
void Settings::setPocketTagsHistory(const QString &value)
{
if (getPocketTagsHistory() != value) {
settings.setValue("pockettagshistory", value);
emit pocketTagsHistoryChanged();
}
}
QString Settings::getPocketTagsHistory()
{
return settings.value("pockettagshistory", "").toString();
}
void Settings::setNightMode(bool value)
{
if (getNightMode() != value) {

View file

@ -72,12 +72,17 @@ class Settings: public QObject
Q_PROPERTY (bool showBroadcast READ getShowBroadcast WRITE setShowBroadcast NOTIFY showBroadcastChanged)
Q_PROPERTY (bool showOldestFirst READ getShowOldestFirst WRITE setShowOldestFirst NOTIFY showOldestFirstChanged)
Q_PROPERTY (bool syncRead READ getSyncRead WRITE setSyncRead NOTIFY syncReadChanged)
Q_PROPERTY (bool iconContextMenu READ getIconContextMenu WRITE setIconContextMenu NOTIFY iconContextMenuChanged)
Q_PROPERTY (bool doublePane READ getDoublePane WRITE setDoublePane NOTIFY doublePaneChanged)
Q_PROPERTY (int clickBehavior READ getClickBehavior WRITE setClickBehavior NOTIFY clickBehaviorChanged)
Q_PROPERTY (bool expandedMode READ getExpandedMode WRITE setExpandedMode NOTIFY expandedModeChanged)
Q_PROPERTY (int webviewNavigation READ getWebviewNavigation WRITE setWebviewNavigation NOTIFY webviewNavigationChanged)
Q_PROPERTY (bool nightMode READ getNightMode WRITE setNightMode NOTIFY nightModeChanged)
Q_PROPERTY (bool pocketEnabled READ getPocketEnabled WRITE setPocketEnabled NOTIFY pocketEnabledChanged)
Q_PROPERTY (bool pocketQuickAdd READ getPocketQuickAdd WRITE setPocketQuickAdd NOTIFY pocketQuickAddChanged)
Q_PROPERTY (bool pocketFavorite READ getPocketFavorite WRITE setPocketFavorite NOTIFY pocketFavoriteChanged)
Q_PROPERTY (QString pocketToken READ getPocketToken WRITE setPocketToken NOTIFY pocketTokenChanged)
Q_PROPERTY (QString pocketTags READ getPocketTags WRITE setPocketTags NOTIFY pocketTagsChanged)
Q_PROPERTY (QString pocketTagsHistory READ getPocketTagsHistory WRITE setPocketTagsHistory NOTIFY pocketTagsHistoryChanged)
public:
static Settings* instance();
@ -128,8 +133,23 @@ public:
bool getShowBroadcast();
void setShowBroadcast(bool value);
bool getIconContextMenu();
void setIconContextMenu(bool value);
bool getPocketEnabled();
void setPocketEnabled(bool value);
bool getPocketQuickAdd();
void setPocketQuickAdd(bool value);
bool getPocketFavorite();
void setPocketFavorite(bool value);
void setPocketToken(const QString &value);
QString getPocketToken();
void setPocketTags(const QString &value);
QString getPocketTags();
void setPocketTagsHistory(const QString &value);
QString getPocketTagsHistory();
void setDashboardInUse(const QString &value);
QString getDashboardInUse();
@ -254,6 +274,8 @@ public:
Q_INVOKABLE const QList<QVariant> viewModeHistory();
Q_INVOKABLE QString pocketConsumerKey();
signals:
void offlineModeChanged();
void autoOfflineChanged();
@ -281,11 +303,16 @@ signals:
void showBroadcastChanged();
void showOldestFirstChanged();
void syncReadChanged();
void iconContextMenuChanged();
void doublePaneChanged();
void clickBehaviorChanged();
void expandedModeChanged();
void webviewNavigationChanged();
void pocketEnabledChanged();
void pocketTokenChanged();
void pocketTagsChanged();
void pocketTagsHistoryChanged();
void pocketFavoriteChanged();
void pocketQuickAddChanged();
/*
501 - Unable create settings dir

View file

@ -188,12 +188,18 @@ void Utils::copyToClipboard(const QString &text)
void Utils::resetQtWebKit()
{
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
QString value = QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)).path();
QStringList dataDirs = QStandardPaths::standardLocations(QStandardPaths::DataLocation);
if(dataDirs.size() > 0) {
QDir dir(QDir(dataDirs.at(0)).filePath(".QtWebKit"));
qDebug() << dir.path();
if (dir.exists())
dir.removeRecursively();
}
#else
QString value = QDir(QDesktopServices::storageLocation(QDesktopServices::DataLocation)).path();
#endif
value = value + "/.QtWebKit";
removeDir(value);
#endif
}
void Utils::log(const QString & data)