Multiple Qml app improvements

LibresapiLocalClient::request(...) return a meaningful value
ChatView.qml uses a TextField for message input
TrustedNodesView.qml improve aestetic
main.qml use stack based navigation and tool/menubar
This commit is contained in:
Gioacchino Mazzurco 2017-03-17 17:22:58 +01:00
parent 5a219b4d10
commit 17a41dc96a
12 changed files with 266 additions and 124 deletions

View File

@ -1,6 +1,6 @@
/*
* RetroShare Android QML App
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2016-2017 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2016 Manu Pineda <manu@cooperativa.cat>
*
* This program is free software: you can redistribute it and/or modify
@ -38,9 +38,7 @@ int LibresapiLocalClient::request( const QString& path, const QString& jsonData,
data.append(path); data.append('\n');
data.append(jsonData); data.append('\n');
callbackQueue.enqueue(callback);
mLocalSocket.write(data);
return 1;
return mLocalSocket.write(data);
}
void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError)

View File

@ -1,6 +1,6 @@
/*
* RetroShare Android QML App
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2016-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
@ -39,7 +39,8 @@ int main(int argc, char *argv[])
QQmlApplicationEngine engine;
/// @deprecated
/** When possible it is better to use +rsApi+ object directly instead of
* multiple instances of +LibresapiLocalClient+ in Qml */
qmlRegisterType<LibresapiLocalClient>(
"org.retroshare.qml_components.LibresapiLocalClient", 1, 0,
"LibresapiLocalClient");
@ -55,7 +56,8 @@ int main(int argc, char *argv[])
engine.load(QUrl(QLatin1String("qrc:/qml/main.qml")));
QFileInfo fileInfo(sockPath);
qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString();
qDebug() << "QML APP:" << sockPath << fileInfo.exists()
<< fileInfo.lastModified().toString();
return app.exec();
}

View File

@ -1,10 +1,6 @@
<RCC>
<qresource prefix="/">
<file>qml/main.qml</file>
<file>qml/icons/star-2-128.png</file>
<file>qml/icons/settings-4-128.png</file>
<file>qml/icons/email-128.png</file>
<file>qml/icons/contacts-128.png</file>
<file>qml/Locations.qml</file>
<file>qml/jsonpath.js</file>
<file>qml/JSONListModel.qml</file>
@ -13,5 +9,9 @@
<file>qml/RsLoginPassView.qml</file>
<file>qml/TrustedNodesView.qml</file>
<file>qml/ChatView.qml</file>
<file>qml/icons/retroshare06.png</file>
<file>qml/icons/remove-link.png</file>
<file>qml/icons/state-offline.png</file>
<file>qml/icons/state-ok.png</file>
</qresource>
</RCC>

View File

@ -1,3 +1,21 @@
/*
* RetroShare Android QML App
* Copyright (C) 2016-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.0
import QtQuick.Controls 1.4
import org.retroshare.qml_components.LibresapiLocalClient 1.0
@ -43,16 +61,7 @@ Item
delegate: chatMessageDelegate
}
Rectangle
{
color: "green"
anchors.bottom: parent.bottom
anchors.left: parent.left
width: chatView.width - sendButton.width
height: Math.max(20, msgComposer.height)
}
TextEdit
TextField
{
id: msgComposer
anchors.bottom: parent.bottom

View File

@ -1,6 +1,6 @@
/*
* RetroShare Android QML App
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2016-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
@ -29,7 +29,15 @@ Item
Component.onCompleted: refreshOwn()
function refreshData() { rsApi.request("/identity/*/", "", function(par) { locationsModel.json = par.response; if(contactsView.own_gxs_id == "") refreshOwn() }) }
function refreshData()
{
function refreshCallback(par)
{
locationsModel.json = par.response
if(contactsView.own_gxs_id == "") refreshOwn()
}
rsApi.request("/identity/*/", "", refreshCallback)
}
function refreshOwn()
{
rsApi.request("/identity/own", "", function(par)
@ -43,6 +51,14 @@ Item
else createIdentityDialog.visible = true
})
}
function startChatCallback(par)
{
var chId = JSON.parse(par.response).data.chat_id
stackView.push({
item:"qrc:/qml/ChatView.qml",
properties: {chatId: chId}
})
}
onFocusChanged: focus && refreshData()
@ -68,12 +84,16 @@ Item
anchors.fill: parent
onClicked:
{
console.log("Contacts view onclicked:", model.name, model.gxs_id)
console.log("Contacts view onclicked:", model.name,
model.gxs_id)
if(model.own) contactsView.own_gxs_id = model.gxs_id
else
{
var jsonData = { "own_gxs_hex": contactsView.own_gxs_id, "remote_gxs_hex": model.gxs_id }
rsApi.request("/chat/initiate_distant_chat", JSON.stringify(jsonData), function (par) { mainWindow.activeChatId = JSON.parse(par.response).data.chat_id })
var jsonData = { "own_gxs_hex": contactsView.own_gxs_id,
"remote_gxs_hex": model.gxs_id }
rsApi.request("/chat/initiate_distant_chat",
JSON.stringify(jsonData),
contactsView.startChatCallback)
}
}
Text

View File

@ -1,6 +1,6 @@
/*
* RetroShare Android QML App
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2016-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
@ -18,17 +18,31 @@
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import "jsonpath.js" as JSONPath
Item
{
function refreshData() { rsApi.request("/peers/*", "", function(par) { jsonModel.json = par.response }) }
id: trustedNodesView
function refreshData()
{
rsApi.request("/peers/*", "",
function(par) { jsonModel.json = par.response })
}
onFocusChanged: focus && refreshData()
JSONListModel
{
id: jsonModel
query: "$.data[*]"
function isOnline(pgpId)
{
var qr = "$.data[?(@.pgp_id=='"+pgpId+"')].locations[*].is_online"
var locArr = JSONPath.jsonPath(JSON.parse(jsonModel.json), qr)
return locArr.reduce(function(cur,acc){return cur || acc}, false)
}
}
ListView
@ -38,39 +52,93 @@ Item
anchors.bottom: bottomButton.top
model: jsonModel.model
delegate: Item
{
height: 50
Row
{
height: 30
width: parent.width
Image
{
id: statusImage
source: jsonModel.isOnline(model.pgp_id) ?
"icons/state-ok.png" :
"icons/state-offline.png"
height: parent.height - 4
fillMode: Image.PreserveAspectFit
anchors.leftMargin: 6
anchors.verticalCenter: parent.verticalCenter
}
Text
{
text: model.name
onTextChanged: color = JSONPath.jsonPath(JSON.parse(jsonModel.json), "$.data[?(@.pgp_id=='"+model.pgp_id+"')].locations[*].is_online").reduce(function(cur,acc){return cur || acc}, false) ? "lime" : "darkslategray"
anchors.verticalCenter: parent.verticalCenter
anchors.left: statusImage.right
anchors.leftMargin: 10
}
Rectangle
Image
{
height: parent.height
width: parent.height
color: "red"
source: "icons/remove-link.png"
height: parent.height - 6
fillMode: Image.PreserveAspectFit
anchors.right: parent.right
anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter
MouseArea
{
height: parent.height
width: parent.height
onClicked: rsApi.request("/peers/"+model.pgp_id+"/delete")
width: parent.width
onClicked:
{
deleteDialog.nodeName = model.name
deleteDialog.nodeId = model.pgp_id
deleteDialog.visible = true
}
}
}
}
}
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
{
id: bottomButton
text: "Add Trusted Node"
anchors.bottom: parent.bottom
onClicked: swipeView.currentIndex = 3
onClicked: stackView.push({item:"qrc:/qml/AddTrustedNode.qml"})
width: parent.width
}
Timer
{
interval: 800
repeat: true
onTriggered: if(trustedNodesView.visible) trustedNodesView.refreshData()
Component.onCompleted: start()
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,6 +1,6 @@
/*
* RetroShare Android QML App
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2016-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
@ -24,94 +24,137 @@ ApplicationWindow
{
id: mainWindow
visible: true
title: qsTr("RSChat")
title: "RetroShare"
width: 400
height: 400
property string activeChatId;
Rectangle
toolBar: ToolBar
{
id: mainView
Image
{
id: rsIcon
fillMode: Image.PreserveAspectFit
height: Math.max(30, parent.height - 4)
anchors.verticalCenter: parent.verticalCenter
source: "icons/retroshare06.png"
}
Label
{
text: "RetroShare"
anchors.verticalCenter: parent.verticalCenter
anchors.left: rsIcon.right
anchors.leftMargin: 20
}
}
menuBar: MenuBar
{
Menu
{
MenuItem
{
text: "Trusted Nodes"
onTriggered:
stackView.push({item:"qrc:/qml/TrustedNodesView.qml"})
}
MenuItem
{
text: "StackView State"
onTriggered: console.log(stackView.state, stackView.enabled)
}
}
}
StackView
{
id: stackView
anchors.fill: parent
states:
[
Keys.onReleased:
if (event.key === Qt.Key_Back && stackView.depth > 1)
{
stackView.pop();
event.accepted = true;
}
function checkCoreStatus()
{
function runStateCallback(par)
{
var jsonReponse = JSON.parse(par.response)
var runState = jsonReponse.data.runstate
if(typeof(runState) === 'string') stackView.state = runState
else
{
stackView.state = "core_down"
console.log("runStateCallback(...) core is down")
}
}
var ret = rsApi.request("/control/runstate/", "", runStateCallback)
if ( ret < 1 )
{
console.log("checkCoreStatus() core is down")
stackView.state = "core_down"
}
}
Timer
{
id: refreshTimer
interval: 800
repeat: true
onTriggered: if(stackView.visible) stackView.checkCoreStatus()
Component.onCompleted: start()
}
state: "core_down"
states: [
State
{
name: "core_down"
PropertyChanges { target: stackView; enabled: false }
},
State
{
name: "waiting_account_select"
PropertyChanges { target: swipeView; currentIndex: 0 }
PropertyChanges { target: locationsTab; enabled: true }
PropertyChanges { target: stackView; enabled: true }
StateChangeScript
{
script:
{
console.log("StateChangeScript waiting_account_select")
stackView.clear()
stackView.push({item:"qrc:/qml/Locations.qml"})
}
}
},
State
{
name: "running_ok"
PropertyChanges { target: swipeView; currentIndex: 1 }
PropertyChanges { target: locationsTab; enabled: false }
PropertyChanges { target: stackView; enabled: true }
StateChangeScript
{
script:
{
console.log("StateChangeScript waiting_account_select")
stackView.clear()
stackView.push({item: "qrc:/qml/Contacts.qml"})
}
}
},
State
{
name: "running_ok_no_full_control"
PropertyChanges { target: swipeView; currentIndex: 1 }
PropertyChanges { target: locationsTab; enabled: false }
PropertyChanges { target: stackView; state: "running_ok" }
}
]
LibresapiLocalClient
initialItem: Rectangle
{
onGoodResponseReceived:
{
var jsonReponse = JSON.parse(msg)
mainView.state = jsonReponse.data.runstate
}
Component.onCompleted:
{
openConnection(apiSocketPath)
request("/control/runstate/", "")
}
}
TabView
{
id: swipeView
anchors.fill: parent
visible: true
currentIndex: 0
color: "green"
border.color: "black"
Tab
{
title:"Locations"
id: locationsTab
Locations { onVisibleChanged: focus = visible }
}
Tab
{
title: "Trusted Nodes"
TrustedNodesView { onVisibleChanged: focus = visible }
}
Tab
{
title: "Contacts"
Contacts { onVisibleChanged: focus = visible }
}
Tab
{
title: "Add Node"
AddTrustedNode { onVisibleChanged: focus = visible }
}
Tab
{
title: "Chat"
ChatView
{
id: chatView
chatId: mainWindow.activeChatId
onVisibleChanged: focus = visible
}
}
Text { text: "Connecting to core..." }
}
}
}

View File

@ -23,7 +23,8 @@ DISTFILES += \
android/res/values/libs.xml \
android/build.gradle \
android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew.bat
android/gradlew.bat \
qml/icons/retroshare06.png
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android