Implemented certificate link handling

Move content of qml directory to parent directory src
Message notification take care of plurar/singular
Fix compilation of service for non-android linux
AddTrustedNode.qml show node details for confirmation before adding
Added TrustedNodeDetails.qml to show nodes details
TrustedNodesView.qml show node details on click
Qml app added minimal infrastructure to delegate link handling
Removed unused icons
This commit is contained in:
Gioacchino Mazzurco 2017-04-13 16:47:27 +02:00
parent f6d44f1a46
commit d2598dd437
37 changed files with 326 additions and 115 deletions

View File

@ -1 +1 @@
../../retroshare-qml-app/src/qml/TokensManager.qml ../../retroshare-qml-app/src/TokensManager.qml

View File

@ -41,7 +41,10 @@ QtObject
"unread conversations") "unread conversations")
notificationsBridge.notify( notificationsBridge.notify(
qsTr("New message!"), qsTr("New message!"),
qsTr("Unread messages in %1 conversations").arg(convCnt) qsTr("Unread messages in %1 %2").arg(convCnt).arg(
convCnt > 1 ?
qsTr("conversations") : qsTr("conversation")
)
) )
} }
} }

View File

@ -17,12 +17,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QtAndroidExtras/QAndroidJniObject>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QtAndroid>
#include <QDebug> #include <QDebug>
#ifdef __ANDROID__
#include <QtAndroid>
#include <QtAndroidExtras/QAndroidJniObject>
#endif // __ANDROID__
struct NotificationsBridge : QObject struct NotificationsBridge : QObject
{ {
Q_OBJECT Q_OBJECT
@ -33,6 +36,7 @@ public slots:
{ {
qDebug() << __PRETTY_FUNCTION__ << title << text << uri; qDebug() << __PRETTY_FUNCTION__ << title << text << uri;
#ifdef __ANDROID__
QtAndroid::androidService().callMethod<void>( QtAndroid::androidService().callMethod<void>(
"notify", "notify",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
@ -40,5 +44,6 @@ public slots:
QAndroidJniObject::fromString(text).object(), QAndroidJniObject::fromString(text).object(),
QAndroidJniObject::fromString(uri).object() QAndroidJniObject::fromString(uri).object()
); );
#endif // __ANDROID__
} }
}; };

View File

@ -12,14 +12,12 @@ RESOURCES += qml.qrc
TEMPLATE = app TEMPLATE = app
android-g++ android-g++ {
{
TEMPLATE = lib TEMPLATE = lib
QT += androidextras QT += androidextras
HEADERS += notificationsbridge.h
} }
HEADERS += libresapilocalclient.h HEADERS += libresapilocalclient.h notificationsbridge.h
SOURCES += libresapilocalclient.cpp main.cpp SOURCES += libresapilocalclient.cpp main.cpp
DEPENDPATH *= ../../libretroshare/src DEPENDPATH *= ../../libretroshare/src

View File

@ -35,20 +35,25 @@ Item
text: "Add trusted node" text: "Add trusted node"
onClicked: onClicked:
{ {
console.log("retroshare addtrusted: ", otherKeyField.text) rsApi.request(
var jsonData = "/peers/examine_cert/",
{ JSON.stringify({cert_string: otherKeyField.text}),
cert_string: otherKeyField.text, function(par)
flags: {
{ console.log("/peers/examine_cert/ CB", par)
allow_direct_download: true, var jData = JSON.parse(par.response).data
allow_push: false, stackView.push(
require_whitelist: false, "qrc:/TrustedNodeDetails.qml",
} {
} nodeCert: otherKeyField.text,
console.log("retroshare addtrusted jsonData: ", JSON.stringify(jsonData)) pgpName: jData.name,
//rsApi.request("/peers/examine_cert/", JSON.stringify({ cert_string: otherKeyField.text })) pgpId: jData.pgp_id,
rsApi.request("PUT /peers", JSON.stringify(jsonData)) locationName: jData.location,
sslIdTxt: jData.peer_id
}
)
}
)
} }
} }

View File

@ -37,7 +37,7 @@ Item
WorkerScript WorkerScript
{ {
id: contactsSortWorker id: contactsSortWorker
source: "qrc:/qml/ContactSort.js" source: "qrc:/ContactSort.js"
onMessage: contactsListModel.json = JSON.stringify(messageObject) onMessage: contactsListModel.json = JSON.stringify(messageObject)
} }
@ -110,7 +110,7 @@ Item
function startChatCallback(par) function startChatCallback(par)
{ {
var chId = JSON.parse(par.response).data.chat_id var chId = JSON.parse(par.response).data.chat_id
stackView.push("qrc:/qml/ChatView.qml", {'chatId': chId}) stackView.push("qrc:/ChatView.qml", {'chatId': chId})
} }
@ -154,7 +154,7 @@ Item
height: searchText.height - 4 height: searchText.height - 4
width: searchText.height - 4 width: searchText.height - 4
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
source: "qrc:/qml/icons/edit-find.png" source: "qrc:/icons/edit-find.png"
} }
TextField TextField

View File

@ -54,7 +54,7 @@ Item
Image Image
{ {
source: "qrc:/qml/icons/edit-image-face-detect.png" source: "qrc:/icons/edit-image-face-detect.png"
anchors.fill: parent anchors.fill: parent
} }

View File

@ -47,7 +47,7 @@ Item
target: loginView target: loginView
visible: true visible: true
buttonText: qsTr("Save") buttonText: qsTr("Save")
iconUrl: "qrc:/qml/icons/edit-image-face-detect.png" iconUrl: "qrc:/icons/edit-image-face-detect.png"
suggestionText: qsTr("Create your profile") suggestionText: qsTr("Create your profile")
onSubmit: onSubmit:
{ {

View File

@ -24,7 +24,7 @@ Item
{ {
id: loginView id: loginView
property string buttonText: qsTr("Unlock") property string buttonText: qsTr("Unlock")
property string iconUrl: "qrc:/qml/icons/emblem-locked.png" property string iconUrl: "qrc:/icons/emblem-locked.png"
property string login property string login
property bool loginPreset: false property bool loginPreset: false
property bool advancedMode: false property bool advancedMode: false

View File

@ -0,0 +1,132 @@
/*
* RetroShare Android QML App
* Copyright (C) 2017 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import QtQuick.Controls 2.0
Item
{
id: nodeDetailsRoot
property string pgpName
property alias pgpId: pgpIdTxt.text
property string nodeCert
property var locations
Column
{
id: pgpColumn
anchors.top: parent.top
Text { text: nodeDetailsRoot.pgpName.replace(" (Generated by RetroShare) <>", "") }
Text { id: pgpIdTxt }
}
JSONListModel
{
id: jsonModel
json: JSON.stringify(nodeDetailsRoot.locations)
}
ListView
{
width: parent.width
anchors.top: pgpColumn.bottom
anchors.bottom: buttonsRow.top
model: jsonModel.model
delegate: Column
{
height: 60
width: parent.width
leftPadding: 4
spacing: 4
Row
{
height: 30
spacing: 10
Image
{
id: statusImage
source: model.is_online ?
"icons/state-ok.png" :
"icons/state-offline.png"
height: parent.height - 4
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
}
Text
{
id: locNameText
text: model.location
anchors.verticalCenter: parent.verticalCenter
}
}
Text { text: model.peer_id }
}
}
Row
{
id: buttonsRow
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 6
Button
{
text: qsTr("Revoke")
onClicked:
rsApi.request(
"/peers/"+nodeDetailsRoot.pgpId+"/delete", "",
function()
{ stackView.push("qrc:/TrustedNodesView.qml") })
}
Button
{
text: qsTr("Entrust")
visible: nodeDetailsRoot.nodeCert.length > 0
onClicked:
{
var jsonData =
{
cert_string: nodeCert,
flags:
{
allow_direct_download: true,
allow_push: false,
require_whitelist: false,
}
}
rsApi.request(
"PUT /peers", JSON.stringify(jsonData),
function()
{ stackView.push("qrc:/TrustedNodesView.qml") })
}
}
}
}

View File

@ -16,9 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.0 import QtQuick 2.7
import QtQuick.Controls 2.0 import QtQuick.Controls 2.0
import QtQuick.Dialogs 1.2
import "jsonpath.js" as JSONPath import "jsonpath.js" as JSONPath
import "." //Needed for TokensManager singleton import "." //Needed for TokensManager singleton
@ -28,7 +27,7 @@ Item
property int token: 0 property int token: 0
Component.onCompleted: refreshData() Component.onCompleted: refreshData()
onFocusChanged: focus && refreshData() onVisibleChanged: visible && refreshData()
function refreshDataCallback(par) function refreshDataCallback(par)
{ {
@ -52,6 +51,12 @@ Item
return locOn.reduce(function(cur,acc){return cur || acc}, false) return locOn.reduce(function(cur,acc){return cur || acc}, false)
return Boolean(locOn) return Boolean(locOn)
} }
function getLocations(pgpId)
{
var qr = "$.data[?(@.pgp_id=='"+pgpId+"')].locations"
return JSONPath.jsonPath(JSON.parse(jsonModel.json), qr)
}
} }
ListView ListView
@ -85,62 +90,31 @@ Item
anchors.left: statusImage.right anchors.left: statusImage.right
anchors.leftMargin: 10 anchors.leftMargin: 10
} }
Image MouseArea
{ {
source: "icons/remove-link.png" anchors.fill: parent
onClicked:
height: parent.height - 6
fillMode: Image.PreserveAspectFit
anchors.right: parent.right
anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter
MouseArea
{ {
height: parent.height stackView.push(
width: parent.width "qrc:/TrustedNodeDetails.qml",
onClicked: {
{ pgpName: model.name,
deleteDialog.nodeName = model.name pgpId: model.pgp_id,
deleteDialog.nodeId = model.pgp_id locations: jsonModel.getLocations(
deleteDialog.visible = true model.pgp_id)
} }
)
} }
} }
} }
} }
Dialog
{
id: deleteDialog
property string nodeName
property string nodeId
standardButtons: StandardButton.Yes | StandardButton.No
visible: false
onYes:
{
rsApi.request("/peers/"+nodeId+"/delete")
trustedNodesView.refreshData()
trustedNodesView.forceActiveFocus()
}
onNo: trustedNodesView.forceActiveFocus()
Text
{
text: "Are you sure to delete " + deleteDialog.nodeName + " ("+
deleteDialog.nodeId +") ?"
width: parent.width - 2
wrapMode: Text.Wrap
}
}
Button Button
{ {
id: bottomButton id: bottomButton
text: "Add Trusted Node" text: qsTr("Add Trusted Node")
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
onClicked: stackView.push("qrc:/qml/AddTrustedNode.qml") onClicked: stackView.push("qrc:/AddTrustedNode.qml")
width: parent.width width: parent.width
} }
} }

View File

Before

Width:  |  Height:  |  Size: 387 B

After

Width:  |  Height:  |  Size: 387 B

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1 @@
../../../data/128x128/apps/retroshare06.png

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -53,7 +53,7 @@ int main(int argc, char *argv[])
engine.rootContext()->setContextProperty("apiSocketPath", sockPath); engine.rootContext()->setContextProperty("apiSocketPath", sockPath);
engine.rootContext()->setContextProperty("rsApi", &rsApi); engine.rootContext()->setContextProperty("rsApi", &rsApi);
engine.load(QUrl(QLatin1String("qrc:/qml/main.qml"))); engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec(); return app.exec();
} }

View File

@ -19,7 +19,7 @@
import QtQuick 2.2 import QtQuick 2.2
import QtQuick.Controls 2.0 import QtQuick.Controls 2.0
import org.retroshare.qml_components.LibresapiLocalClient 1.0 import org.retroshare.qml_components.LibresapiLocalClient 1.0
import "URI.js" as URI import "URI.js" as UriJs
import "." //Needed for TokensManager singleton import "." //Needed for TokensManager singleton
ApplicationWindow ApplicationWindow
@ -32,10 +32,14 @@ ApplicationWindow
property string pgp_name property string pgp_name
function handleIntentUri(uriStr) property bool coreReady: stackView.state === "running_ok" ||
{ stackView.state === "running_ok_no_full_control"
console.log("handleIntentUri", JSON.stringify(URI.parse(uriStr), null, 1))
} Component.onCompleted: addUriHandler("/certificate", certificateLinkHandler)
property var uriHandlersRegister: ({})
function addUriHandler(path, fun) { uriHandlersRegister[path] = fun }
function delUriHandler(path, fun) { delete uriHandlersRegister[path] }
header: ToolBar header: ToolBar
{ {
@ -68,7 +72,7 @@ ApplicationWindow
Image Image
{ {
source: "qrc:/qml/icons/application-menu.png" source: "qrc:/icons/application-menu.png"
height: parent.height - 10 height: parent.height - 10
width: parent.height - 10 width: parent.height - 10
anchors.centerIn: parent anchors.centerIn: parent
@ -82,15 +86,30 @@ ApplicationWindow
MenuItem MenuItem
{ {
text: qsTr("Trusted Nodes") text: qsTr("Trusted Nodes")
//iconSource: "qrc:/qml/icons/document-share.png" //iconSource: "qrc:/icons/document-share.png"
onTriggered: stackView.push("qrc:/qml/TrustedNodesView.qml") onTriggered: stackView.push("qrc:/TrustedNodesView.qml")
enabled: mainWindow.coreReady
} }
MenuItem MenuItem
{ {
text: qsTr("Search Contacts") text: qsTr("Search Contacts")
onTriggered: onTriggered:
stackView.push("qrc:/qml/Contacts.qml", stackView.push("qrc:/Contacts.qml",
{'searching': true} ) {'searching': true} )
enabled: mainWindow.coreReady
}
MenuItem
{
text: "Paste Link"
onTriggered:
{
clipboardWrap.selectAll()
clipboardWrap.paste()
handleIntentUri(clipboardWrap.text)
}
enabled: mainWindow.coreReady
TextField { id: clipboardWrap; visible: false }
} }
MenuItem MenuItem
{ {
@ -131,7 +150,7 @@ ApplicationWindow
{ {
console.log("StateChangeScript waiting_account_select") console.log("StateChangeScript waiting_account_select")
stackView.clear() stackView.clear()
stackView.push("qrc:/qml/Locations.qml") stackView.push("qrc:/Locations.qml")
} }
} }
}, },
@ -145,7 +164,7 @@ ApplicationWindow
{ {
console.log("StateChangeScript waiting_startup") console.log("StateChangeScript waiting_startup")
stackView.clear() stackView.clear()
stackView.push("qrc:/qml/BusyOverlay.qml", stackView.push("qrc:/BusyOverlay.qml",
{ message: "Core initializing..."}) { message: "Core initializing..."})
} }
} }
@ -161,7 +180,7 @@ ApplicationWindow
console.log("StateChangeScript running_ok") console.log("StateChangeScript running_ok")
coreStateCheckTimer.stop() coreStateCheckTimer.stop()
stackView.clear() stackView.clear()
stackView.push("qrc:/qml/Contacts.qml") stackView.push("qrc:/Contacts.qml")
} }
} }
}, },
@ -202,4 +221,80 @@ ApplicationWindow
} }
} }
} }
function handleIntentUri(uriStr)
{
console.log("handleIntentUri(uriStr)")
if(!Array.isArray(uriStr.match(/:\/\/[a-zA-Z.-]*\//g)))
{
/* RetroShare GUI produces links without hostname and only two
* slashes after scheme causing the first piece of the path part
* being interpreted as host, this is awckard and should be fixed in
* the GUI, in the meantime we add a slash for easier parsing, in
* case there is no hostname and just two slashes, we might consider
* to use +hostname+ part for some trick in the future, for example
* it could help other application to recognize retroshare link by
* putting a domain name there that has no meaning for retroshare
*/
uriStr = uriStr.replace("://", ":///")
}
var uri = new UriJs.URI(uriStr)
var hPath = uri.path() // no nesting ATM segmentCoded()
console.log(hPath)
if(typeof uriHandlersRegister[hPath] == "function")
{
console.log("handleIntentUri(uriStr)", "found handler for path",
hPath, uriHandlersRegister[hPath])
uriHandlersRegister[hPath](uriStr)
}
}
function certificateLinkHandler(uriStr)
{
console.log("certificateLinkHandler(uriStr)", coreReady)
if(!coreReady) return
var uri = new UriJs.URI(uriStr)
var uQuery = uri.search(true)
if(uQuery.radix)
{
console.log("/peers/examine_cert/")
console.log("uriStr", uriStr)
var certStr = UriJs.URI.decode(uQuery.radix)
// Workaround https://github.com/RetroShare/RetroShare/issues/772
certStr = certStr.replace(/ /g, "+")
console.log("certStr", certStr)
console.log("JSON.stringify(..)",
JSON.stringify({cert_string: certStr}, null, 1))
rsApi.request(
"/peers/examine_cert/",
JSON.stringify({cert_string: certStr}),
function(par)
{
console.log("/peers/examine_cert/ CB", par)
var jData = JSON.parse(par.response).data
stackView.push(
"qrc:/TrustedNodeDetails.qml",
{
nodeCert: certStr,
pgpName: jData.name,
pgpId: jData.pgp_id,
locations:
[{
location: jData.location,
peer_id: jData.peer_id
}]
}
)
}
)
}
}
} }

View File

@ -1,28 +1,27 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>qml/main.qml</file> <file>main.qml</file>
<file>qml/Locations.qml</file> <file>Locations.qml</file>
<file>qml/jsonpath.js</file> <file>jsonpath.js</file>
<file>qml/JSONListModel.qml</file> <file>JSONListModel.qml</file>
<file>qml/Contacts.qml</file> <file>Contacts.qml</file>
<file>qml/AddTrustedNode.qml</file> <file>AddTrustedNode.qml</file>
<file>qml/RsLoginPassView.qml</file> <file>RsLoginPassView.qml</file>
<file>qml/TrustedNodesView.qml</file> <file>TrustedNodesView.qml</file>
<file>qml/ChatView.qml</file> <file>ChatView.qml</file>
<file>qml/icons/retroshare06.png</file> <file>icons/retroshare06.png</file>
<file>qml/icons/remove-link.png</file> <file>icons/state-offline.png</file>
<file>qml/icons/state-offline.png</file> <file>icons/state-ok.png</file>
<file>qml/icons/state-ok.png</file> <file>GxsIdentityDelegate.qml</file>
<file>qml/GxsIdentityDelegate.qml</file> <file>icons/edit-find.png</file>
<file>qml/icons/edit-find.png</file> <file>icons/edit-image-face-detect.png</file>
<file>qml/icons/edit-image-face-detect.png</file> <file>icons/application-menu.png</file>
<file>qml/icons/document-share.png</file> <file>ContactSort.js</file>
<file>qml/icons/application-menu.png</file> <file>icons/emblem-locked.png</file>
<file>qml/ContactSort.js</file> <file>BusyOverlay.qml</file>
<file>qml/icons/emblem-locked.png</file> <file>URI.js</file>
<file>qml/BusyOverlay.qml</file> <file>TokensManager.qml</file>
<file>qml/URI.js</file> <file>qmldir</file>
<file>qml/TokensManager.qml</file> <file>TrustedNodeDetails.qml</file>
<file>qml/qmldir</file>
</qresource> </qresource>
</RCC> </RCC>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1 +0,0 @@
../../../../data/128x128/apps/retroshare06.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -30,7 +30,7 @@ DISTFILES += \
android/build.gradle \ android/build.gradle \
android/gradle/wrapper/gradle-wrapper.properties \ android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew.bat \ android/gradlew.bat \
qml/icons/retroshare06.png icons/retroshare06.png
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android