Merge branch 'GSoC2017-evaluation-final'

This commit is contained in:
Gioacchino Mazzurco 2017-08-18 01:37:39 +02:00
commit 32f43b999e
1431 changed files with 6666 additions and 445 deletions

View File

@ -1189,7 +1189,7 @@ int RsServer::StartupRetroShare()
#ifdef __APPLE__
plugins_directories.push_back(rsAccounts->PathDataDirectory()) ;
#endif
#ifndef WINDOWS_SYS
#if !defined(WINDOWS_SYS) && defined(PLUGIN_DIR)
plugins_directories.push_back(std::string(PLUGIN_DIR)) ;
#endif
std::string extensions_dir = rsAccounts->PathBaseDirectory() + "/extensions6/" ;

View File

@ -31,7 +31,7 @@ QtObject
property int loginAttemptCount: 0
property bool attemptingLogin: false
property int loginNotificationTime: 0
property var loginNotificationTime: 0
function delay(msecs, func)
{
@ -58,7 +58,11 @@ QtObject
var runState = jsonReponse.data.runstate
if(typeof(runState) !== 'string')
{
console.log("runStateCallback(par)", "Core hanged!", par.response)
coreReady = false
console.log("runStateCallback(par)",
"Core hanged!",
"typeof(runState):", typeof(runState),
"par.response:", par.response)
return
}
@ -70,7 +74,7 @@ QtObject
break
case "fatal_error":
coreReady = false
console.log("Core hanged")
console.log("Core hanged! runState:", runState)
break
case "waiting_account_select":
coreReady = false
@ -145,7 +149,11 @@ QtObject
interval: 700
repeat: true
triggeredOnStart: true
onTriggered: rsApi.request("/control/password/", "", attemptPasswordCB)
onTriggered:
{
if(am.coreReady) attemptPasswordCBCB()
else rsApi.request("/control/password/", "", attemptPasswordCB)
}
function attemptPasswordCB(par)
{

View File

@ -41,10 +41,17 @@ QtObject
"unread conversations")
notificationsBridge.notify(
qsTr("New message!"),
qsTr("Unread messages in %1 %2").arg(convCnt).arg(
convCnt > 1 ?
qsTr("conversations") : qsTr("conversation")
)
(convCnt > 1) ?
qsTr("Unread messages in %1 conversations").arg(convCnt):
qsTr("%1 Unread %2 from %3")
.arg(json.data[0].unread_count)
.arg(json.data[0].unread_count > 1 ? "messages" : "message")
.arg(json.data[0].remote_author_name),
qsTr("/contacts%1").arg(
convCnt == 1?
"?gxsId="+json.data[0].remote_author_id +
"&name="+json.data[0].remote_author_name
: "")
)
}
}

View File

@ -21,109 +21,266 @@ import QtQuick.Controls 2.0
import org.retroshare.qml_components.LibresapiLocalClient 1.0
import "." //Needed for ClipboardWrapper singleton
import "URI.js" as UriJs
import "components/."
Item
{
property ApplicationWindow mW
property int infoIconHeight: 20
Column
{
anchors.fill: parent
spacing: 7
Text
{
text: qsTr("Import/export node from/to clipboard")
text: qsTr("Import node from clipboard")
font.bold: true
wrapMode: Text.Wrap
}
anchors.horizontalCenter: parent.horizontalCenter
Button
{
text: qsTr("Export own certificate link")
onClicked:
font.pixelSize: 13
Rectangle
{
console.log("onClicked", text)
rsApi.request(
"/peers/self/certificate/", "",
function(par)
{
var radix = JSON.parse(par.response).data.cert_string
var name = mainWindow.user_name
var encodedName = UriJs.URI.encode(name)
var nodeUrl = (
"retroshare://certificate?" +
"name=" + encodedName +
"&radix=" + UriJs.URI.encode(radix) +
"&location=" + encodedName )
ClipboardWrapper.postToClipBoard(nodeUrl)
linkCopiedPopup.itemName = name
linkCopiedPopup.open()
platformGW.shareUrl(nodeUrl);
})
id: backgroundRectangle
anchors.fill: parent.fill
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width + 20
height: parent.height + 5
color:"transparent"
Rectangle
{
id: borderBottom
width: parent.width
height: 1
anchors.bottom: parent.bottom
anchors.right: parent.right
color: "lightgrey"
}
}
}
Button
Row
{
text: qsTr("Import trusted node")
anchors.horizontalCenter: parent.horizontalCenter
spacing: 5
onClicked:
ButtonIcon
{
var cptext = ClipboardWrapper.getFromClipBoard()
height: infoIconHeight
anchors.verticalCenter: importButton.verticalCenter
width: height
imgUrl: "/icons/info.svg"
onClicked:
{
tooltipSpace.show(qsTr("Import a friend certificate from your clipboard. <br>"+
"This will add him as trusted node."))
console.log("typeof(cptext)", typeof(cptext))
if(cptext.search("://") > 0)
mainWindow.handleIntentUri(cptext)
else
rsApi.request(
"/peers/examine_cert/",
JSON.stringify({cert_string: cptext}),
function(par)
{
console.log("/peers/examine_cert/ CB",
par.response)
var resp = JSON.parse(par.response)
if(resp.returncode === "fail")
}
}
ButtonText
{
id: importButton
text: qsTr("Import trusted node")
fontSize: 14
onClicked:
{
var cptext = ClipboardWrapper.getFromClipBoard()
console.log("typeof(cptext)", typeof(cptext))
if(cptext.search("://") > 0)
mainWindow.handleIntentUri(cptext)
else
rsApi.request(
"/peers/examine_cert/",
JSON.stringify({cert_string: cptext}),
function(par)
{
importErrorPop.text = resp.debug_msg
importErrorPop.open()
return
}
console.log("/peers/examine_cert/ CB",
par.response)
var resp = JSON.parse(par.response)
if(resp.returncode === "fail")
{
importErrorPop.text = resp.debug_msg
importErrorPop.open()
return
}
var jData = resp.data
stackView.push(
"qrc:/TrustedNodeDetails.qml",
{
nodeCert: cptext,
pgpName: jData.name,
pgpId: jData.pgp_id,
locationName: jData.location,
sslIdTxt: jData.peer_id
}
)
}
)
var jData = resp.data
stackView.push(
"qrc:/TrustedNodeDetails.qml",
{
nodeCert: cptext,
pgpName: jData.name,
pgpId: jData.pgp_id,
locationName: jData.location,
sslIdTxt: jData.peer_id
}
)
}
)
}
}
}
Button
Text
{
text: qsTr("Export own plain certificate")
onClicked:
{
rsApi.request(
"/peers/self/certificate/", "",
function(par)
{
var radix = JSON.parse(par.response).data.cert_string
var name = mainWindow.user_name
ClipboardWrapper.postToClipBoard(radix)
text: qsTr("Export node")
font.bold: true
wrapMode: Text.Wrap
anchors.horizontalCenter: parent.horizontalCenter
linkCopiedPopup.itemName = name
linkCopiedPopup.open()
})
font.pixelSize: 13
Rectangle
{
anchors.fill: parent.fill
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width + 20
height: parent.height + 5
color:"transparent"
Rectangle
{
width: parent.width
height: 1
anchors.bottom: parent.bottom
anchors.right: parent.right
color: "lightgrey"
}
}
}
Row
{
anchors.horizontalCenter: parent.horizontalCenter
spacing: 5
ButtonIcon
{
height: infoIconHeight
anchors.verticalCenter: btRsCert.verticalCenter
width: height
imgUrl: "/icons/info.svg"
onClicked:
{
tooltipSpace.show(qsTr("Share your RetroShare link! <br>"+
"Send it to a friend and start talk!"))
}
}
ButtonText
{
id: btRsCert
text: qsTr("Export own certificate link")
fontSize: 14
onClicked:
{
console.log("onClicked", text)
rsApi.request(
"/peers/self/certificate/", "",
function(par)
{
var radix = JSON.parse(par.response).data.cert_string
var name = mainWindow.user_name
var encodedName = UriJs.URI.encode(name)
var nodeUrl = (
"retroshare://certificate?" +
"name=" + encodedName +
"&radix=" + UriJs.URI.encode(radix) +
"&location=" + encodedName )
ClipboardWrapper.postToClipBoard(nodeUrl)
linkCopiedPopup.itemName = name
linkCopiedPopup.open()
platformGW.shareUrl(nodeUrl);
})
}
}
}
ButtonText
{
onClicked: plainCertificateRow.visible = !plainCertificateRow.visible
iconUrl: "/icons/options.svg"
anchors.horizontalCenter: parent.horizontalCenter
color: "white"
borderWidth: 1
text: qsTr("Advanced")
}
Row
{
id: plainCertificateRow
anchors.horizontalCenter: parent.horizontalCenter
spacing: 5
visible: false
ButtonIcon
{
height: infoIconHeight
anchors.verticalCenter: btPlainCert.verticalCenter
width: height
imgUrl: "/icons/info.svg"
onClicked:
{
tooltipSpace.show(qsTr("This will copy your RetroShare plain certificate.<br>"+
"Add it manually to your friend client."))
}
}
ButtonText
{
id: btPlainCert
text: qsTr("Export own plain certificate")
fontSize: 14
onClicked:
{
rsApi.request(
"/peers/self/certificate/", "",
function(par)
{
var radix = JSON.parse(par.response).data.cert_string
var name = mainWindow.user_name
ClipboardWrapper.postToClipBoard(radix)
linkCopiedPopup.itemName = name
linkCopiedPopup.open()
})
}
}
}
}
Rectangle
{
id: tooltipSpace
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
ToolTip
{
id: infoTooltip
timeout: 5000
Component.onCompleted: show("afafsaf",0)
}
function show (infoText)
{
infoTooltip.text = infoText
infoTooltip.open()
}
}
TimedPopup

View File

@ -1,76 +0,0 @@
/*
* 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
Item
{
id: compRoot
property string gxs_id
height: 130
width: height
////////////// The following should be considered privates /////////////////////
property bool has_avatar: false
property int avatarAttemptCnt: 0
function getDetails()
{
++compRoot.avatarAttemptCnt
rsApi.request(
"/identity/get_identity_details",
JSON.stringify({ gxs_id: compRoot.gxs_id }),
function(par)
{
var jData = JSON.parse(par.response).data
setDetails(jData)
if(!compRoot.has_avatar &&
compRoot.avatarAttemptCnt < 3) getDetails()
})
}
function setDetails(data)
{
compRoot.has_avatar = data.avatar.length > 0
if(compRoot.has_avatar)
{
contactAvatar.source =
"data:image/png;base64," + data.avatar
}
}
Component.onCompleted: if(visible && !has_avatar) getDetails()
onVisibleChanged: if(visible && !has_avatar) getDetails()
Image
{
id: contactAvatar
anchors.fill: parent
visible: compRoot.has_avatar
}
ColorHash
{
anchors.fill: parent
visible: !compRoot.has_avatar
hash: compRoot.gxs_id
}
}

View File

@ -0,0 +1,128 @@
import QtQuick 2.7
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0
import "." // To load styles
import "./components"
import "URI.js" as UriJs
Item
{
id: chatBubbleDelegate
height: bubble.height
width: mainWindow.width - (styles.aditionalBubbleWidth - 10)
property var styles: StyleChat.bubble
Rectangle
{
id: rootBubble
anchors.fill: parent
width: parent.width
height: parent.height
color: "transparent"
Rectangle
{
id: bubble
width: Math.min (
rootBubble.width * styles.bubbleMaxWidth,
Math.max(mesageText.implicitWidth, sendersName.implicitWidth )
) + timeText.implicitWidth + styles.aditionalBubbleWidth
height: mesageText.height + sendersName.height + styles.aditionalBubbleHeight
anchors.left: (model.incoming)? parent.left : undefined
anchors.right: (!model.incoming)? parent.right : undefined
color: (!model.incoming)? styles.colorOutgoing : styles.colorIncoming
radius: styles.radius
Text
{
id: sendersName
visible: model.incoming
text: (model.incoming)? model.author_name + ":" : ""
color: styles.colorSenderName
font.bold: true
anchors.leftMargin: styles.lMarginBubble
anchors.rightMargin: styles.rMarginBubble
anchors.topMargin: styles.tMarginBubble
anchors.top: bubble.top
anchors.left: (model.incoming)? parent.left : undefined
anchors.right:(!model.incoming)? parent.right : undefined
// Used for give minimum heigh to time when the message is bigger than the bubble in sended messages
height: (model.incoming || !model.incoming &&
mesageText.implicitWidth >= (rootBubble.width * styles.bubbleMaxWidth) )? implicitHeight : 0
}
Text
{
id: timeText
text: getMessageTime()
color: styles.colorMessageTime
anchors.left: (!model.incoming)? parent.left : undefined
anchors.right:(model.incoming)? parent.right : undefined
anchors.top: bubble.top
anchors.leftMargin: styles.lMarginBubble
anchors.rightMargin: styles.rMarginBubble
anchors.topMargin: styles.tMarginBubble
}
Text
{
id: mesageText
text: UriJs.URI.withinString(model.msg, function(url)
{
return "<a href=\""+ url + "\">" + url + "</a>";
})
width: rootBubble.width * styles.bubbleMaxWidth + timeText.width
anchors.left: (model.incoming)? parent.left : undefined
anchors.right:(!model.incoming)? parent.right : undefined
anchors.top: sendersName.bottom
anchors.leftMargin: styles.lMarginBubble
anchors.rightMargin: styles.rMarginBubble
textFormat: Text.RichText
onLinkActivated: Qt.openUrlExternally(link)
// Used for the correct alineation when the message must be on right
horizontalAlignment:(!model.incoming &&
mesageText.implicitWidth <= (rootBubble.width * styles.bubbleMaxWidth)
)? Text.AlignRight : Text.AlignLeft
wrapMode: Text.Wrap
font.pixelSize: styles.messageTextSize
}
}
}
function getMessageTime()
{
var timeFormat = "hh:mm";
var recvDate = new Date(model.recv_time*1000)
var timeString = Qt.formatDateTime(recvDate, timeFormat)
return timeString
}
}

View File

@ -0,0 +1,185 @@
pragma Singleton
import QtQml 2.3
import QtQuick.Controls 2.0
import org.retroshare.qml_components.LibresapiLocalClient 1.0
import Qt.labs.settings 1.0
QtObject
{
id: chatCache
property QtObject lastMessageCache: QtObject
{
id: lastMessageCache
property var lastMessageList: ({})
signal lastMessageChanged(var chatI, var newLastMessage)
function updateLastMessageCache (chatId, chatModel)
{
console.log("updateLastMessageCache (chatId, chatModel)", chatId)
// First creates the chat id object for don't wait to work with the object if is needed to call RS api
if (!lastMessageList[chatId]) {
lastMessageList[chatId] = {}
console.log("Last message cache created!")
}
if (!chatModel) {
rsApi.request( "/chat/messages/"+chatId, "", function (par){
updateLastMessage(chatId, par.response)
})
} else {
updateLastMessage (chatId, chatModel)
}
}
function updateLastMessage (chatId, chatModel)
{
console.log("updateLastMessage (chatId, chatModel)")
var lastMessage = findChatLastMessage (chatModel)
lastMessageList[chatId].lastMessage = lastMessage
lastMessageChanged(chatId, lastMessage)
}
function findChatLastMessage (chatModel)
{
var messagesData = JSON.parse(chatModel).data
return messagesData.slice(-1)[0]
}
function findChatFirstMessage (chatModel)
{
var messagesData = JSON.parse(chatModel).data
return messagesData.slice[0]
}
function setRemoteGXS (chatId, remoteGXS)
{
if (!lastMessageList[chatId]) {
lastMessageList[chatId] = {}
console.log("Last message cache created!")
}
if (lastMessageList[chatId] && !lastMessageList[chatId].remoteGXS){
lastMessageList[chatId].remoteGXS = remoteGXS
return true
}
else {
return false
}
}
function getChatIdFromGxs (gxs)
{
for (var key in lastMessageList) {
if ( lastMessageList[key].remoteGXS &&
lastMessageList[key].remoteGXS.gxs === gxs ) {
return key
}
}
return ""
}
function getGxsFromChatId (chatId)
{
if (lastMessageList[chatId]) return lastMessageList[chatId].remoteGXS
return undefined
}
function getChatLastMessage (chatId)
{
if (lastMessageList[chatId]) {
return lastMessageList[chatId].lastMessage
}
return ""
}
}
property QtObject contactsCache: QtObject
{
id: contactsCache
property var contactsList
property var own
property var identityDetails: ({})
function getContactFromGxsId (gxsId)
{
console.log("getContactFromGxsId (gxsId)", gxsId)
for(var i in contactsList)
{
if (contactsList[i].gxs_id == gxsId) return contactsList[i]
}
}
function getIdentityDetails (gxsId)
{
if (identityDetails[gxsId]) return identityDetails[gxsId]
return ""
}
function setIdentityDetails (jData)
{
identityDetails[jData.gxs_id] = jData
}
function getIdentityAvatar (gxsId)
{
if (identityDetails[gxsId] && identityDetails[gxsId].avatar !== undefined)
{
return identityDetails[gxsId].avatar
}
return ""
}
function delIdentityAvatar (gxsId)
{
if (identityDetails[gxsId] && identityDetails[gxsId].avatar !== undefined)
{
identityDetails[gxsId].avatar = ""
}
}
}
property QtObject facesCache: QtObject
{
id: facesCache
property var iconCache: ({})
property var callbackCache: ({})
}
property QtObject chatHelper: QtObject
{
id: chatHelper
property var gxs_id
property var name
property var cb
function startDistantChat (own_gxs_id, gxs_id, name, cb)
{
console.log("startDistantChat()")
chatHelper.gxs_id = gxs_id
chatHelper.name = name
chatHelper.cb = cb
var jsonData = { "own_gxs_hex": own_gxs_id,
"remote_gxs_hex": gxs_id }
rsApi.request("/chat/initiate_distant_chat",
JSON.stringify(jsonData),
startDistantChatCB)
}
function startDistantChatCB (par)
{
var chatId = JSON.parse(par.response).data.chat_id
lastMessageCache.setRemoteGXS(chatId, { gxs: gxs_id, name: name})
cb(chatId)
}
}
}

View File

@ -18,15 +18,22 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.2
import org.retroshare.qml_components.LibresapiLocalClient 1.0
import "." //Needed for TokensManager singleton
import "./components"
import "./components/emoji"
Item
{
id: chatView
property string chatId
property var gxsInfo: ""
property int token: 0
property string objectName:"chatView"
function refreshData()
{
console.log("chatView.refreshData()", visible)
@ -38,66 +45,343 @@ Item
token = JSON.parse(par.response).statetoken
TokensManager.registerToken(token, refreshData)
ChatCache.lastMessageCache.updateLastMessageCache(chatId, chatModel.json)
if(chatListView.visible)
{
chatListView.positionViewAtEnd()
rsApi.request("/chat/mark_chat_as_read/"+chatId)
}
} )
})
}
Component.onCompleted: refreshData()
Component.onCompleted:
{
refreshData()
}
onFocusChanged: focus && refreshData()
function changeState ()
{
toolBar.state = "CHATVIEW"
gxsInfo= ChatCache.lastMessageCache.getGxsFromChatId(chatView.chatId)
toolBar.gxsSource = gxsInfo.gxs
toolBar.titleText = gxsInfo.name
}
JSONListModel
{
id: chatModel
query: "$.data[*]"
}
Component
{
id: chatMessageDelegate
Item
{
height: 20
Row
{
Text { text: author_name }
Text { text: ": " + msg }
}
}
}
ListView
{
property var styles: StyleChat.chat
id: chatListView
width: parent.width
height: 300
width: parent.width - styles.bubbleMargin
height: parent.height - inferiorPanel.height
anchors.horizontalCenter: parent.horizontalCenter
model: chatModel.model
delegate: chatMessageDelegate
}
delegate: ChatBubbleDelegate {}
spacing: styles.bubbleSpacing
preferredHighlightBegin: 1
TextField
{
id: msgComposer
anchors.bottom: parent.bottom
anchors.left: parent.left
width: chatView.width - sendButton.width
}
Button
{
id: sendButton
text: "Send"
anchors.bottom: parent.bottom
anchors.right: parent.right
onClicked:
onHeightChanged:
{
var jsonData = {"chat_id":chatView.chatId, "msg":msgComposer.text}
rsApi.request( "/chat/send_message", JSON.stringify(jsonData),
function(par) { msgComposer.text = ""; } )
chatListView.currentIndex = count - 1
}
}
EmojiPicker {
id: emojiPicker
anchors.fill: parent
anchors.topMargin: parent.height / 2
anchors.bottomMargin: categorySelectorHeight
property int categorySelectorHeight: 50
color: "white"
buttonWidth: 40
textArea: inferiorPanel.textMessageArea //the TextArea in which EmojiPicker is pasting the Emoji into
state: "EMOJI_HIDDEN"
states: [
State {
name: "EMOJI_HIDDEN"
PropertyChanges { target: emojiPicker; anchors.topMargin: parent.height }
PropertyChanges { target: emojiPicker; anchors.bottomMargin: -1 }
},
State {
name: "EMOJI_SHOWN"
PropertyChanges { target: emojiPicker; anchors.topMargin: parent.height / 2 }
PropertyChanges { target: emojiPicker; anchors.bottomMargin: categorySelectorHeight }
}
]
}
Item
{
property var styles: StyleChat.inferiorPanel
property alias textMessageArea: msgComposer.textMessageArea
id: inferiorPanel
height: ( msgComposer.height > styles.height)? msgComposer.height: styles.height
width: parent.width
anchors.bottom: parent.bottom
Rectangle
{
id: backgroundRectangle
anchors.fill: parent.fill
width: parent.width
height: parent.height
color:inferiorPanel.styles.backgroundColor
border.color: inferiorPanel.styles.borderColor
}
ButtonIcon
{
id: attachButton
property var styles: StyleChat.inferiorPanel.btnIcon
height: styles.height
width: styles.width
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: styles.margin
imgUrl: styles.attachIconUrl
}
RowLayout
{
id: msgComposer
property var styles: StyleChat.inferiorPanel.msgComposer
property alias textMessageArea: flickable.msgField
anchors.verticalCenter: parent.verticalCenter
anchors.left: attachButton.right
width: chatView.width -
(sendButton.width + sendButton.anchors.margins) -
(attachButton.width + attachButton.anchors.margins) -
(emojiButton.width + emojiButton.anchors.margins)
height: (flickable.contentHeight < styles.maxHeight)? flickable.contentHeight : styles.maxHeight
Flickable
{
id: flickable
property alias msgField: msgField
anchors.fill: parent
flickableDirection: Flickable.VerticalFlick
width: parent.width
contentWidth: msgField.width
contentHeight: msgField.height
contentY: contentHeight - height
ScrollBar.vertical: ScrollBar {}
clip: true
TextArea
{
property var styles: StyleChat.inferiorPanel.msgComposer
id: msgField
height: contentHeight + font.pixelSize
width: parent.width
placeholderText: styles.placeHolder
background: styles.background
wrapMode: TextEdit.Wrap
focus: true
inputMethodHints: Qt.ImhMultiLine
font.pixelSize: styles.messageBoxTextSize
onTextChanged:
{
var msgLenght = (msgField.preeditText)? msgField.preeditText.length : msgField.length
if (msgLenght == 0)
{
sendButton.state = ""
}
else if (msgLenght > 0 )
{
sendButton.state = "SENDBTN"
}
}
property bool shiftPressed: false
Keys.onPressed:
{
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter)
&& !shiftPressed)
{
if (sendButton.state == "SENDBTN" )
{
chatView.sendMessage ()
}
}
else if (event.key === Qt.Key_Shift)
{
shiftPressed = true
}
}
Keys.onReleased:
{
if (event.key === Qt.Key_Shift)
{
shiftPressed = false
}
}
function reset ()
{
msgField.text = ""
Qt.inputMethod.reset()
}
}
}
}
ButtonIcon
{
id: emojiButton
property var styles: StyleChat.inferiorPanel.btnIcon
height: styles.height
width: styles.width
anchors.right: sendButton.left
anchors.bottom: parent.bottom
anchors.margins: styles.margin
imgUrl: styles.emojiIconUrl
onClicked: {
if (emojiPicker.state == "EMOJI_HIDDEN") {
emojiPicker.state = "EMOJI_SHOWN"
} else {
emojiPicker.state = "EMOJI_HIDDEN"
}
}
}
ButtonIcon
{
id: sendButton
property var styles: StyleChat.inferiorPanel.btnIcon
property alias icon: sendButton.imgUrl
height: styles.height
width: styles.width
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: styles.margin
imgUrl: styles.microIconUrl
onClicked:
{
if (sendButton.state == "SENDBTN" )
{
chatView.sendMessage ()
}
}
onPressed:
{
if (sendButton.state == "RECORDING" )
{
sendButton.state = ""
}
else if (sendButton.state == "" )
{
sendButton.state = "RECORDING"
}
}
onReleased:
{
if (sendButton.state == "RECORDING" )
{
sendButton.state = ""
}
}
states:
[
State
{
name: ""
PropertyChanges { target: sendButton; icon: styles.microIconUrl}
},
State
{
name: "RECORDING"
PropertyChanges { target: sendButton; icon: styles.microMuteIconUrl}
},
State
{
name: "SENDBTN"
PropertyChanges { target: sendButton; icon: styles.sendIconUrl}
}
]
}
}
function sendMessage ()
{
if (emojiPicker.state == "EMOJI_SHOWN") emojiPicker.state = "EMOJI_HIDDEN"
msgField.text = getCompleteMessageText () + " " // Needed to prevent pre edit text problems
var msgText = msgField.text
var jsonData = {"chat_id":chatView.chatId, "msg":msgText}
rsApi.request( "/chat/send_message", JSON.stringify(jsonData),
function(par)
{
msgField.reset();
})
}
// This function is needed for the compatibility with auto predictive keyboards
function getCompleteMessageText (){
var completeMsg
if (msgField.preeditText) completeMsg = msgField.text + msgField.preeditText
else completeMsg = msgField.text
return completeMsg
}
}

View File

@ -19,6 +19,7 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import "." //Needed for ClipboardWrapper singleton
import "./components"
import "URI.js" as UriJs
Item
@ -26,6 +27,61 @@ Item
id: cntDt
property var md
property bool is_contact: cntDt.md.is_contact
property bool isOwn: cntDt.md.own
property string objectName: "contactDetails"
ButtonText
{
id: avatarPicker
text: (isOwn)? qsTr("Change your Avatar") : qsTr("Start Chat!")
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
buttonTextPixelSize: 14
iconUrl: (isOwn)? "/icons/attach-image.svg": "/icons/chat-bubble.svg"
borderRadius: 0
onClicked:
{
if (isOwn) fileChooser.open()
else startDistantChat ()
}
function startDistantChat ()
{
ChatCache.chatHelper.startDistantChat(ChatCache.contactsCache.own.gxs_id,
cntDt.md.gxs_id,
cntDt.md.name,
function (chatId)
{
stackView.push("qrc:/ChatView.qml", {'chatId': chatId})
})
}
CustomFileChooser
{
id: fileChooser
onResultFileChanged:
{
console.log("Result file changed! " , resultFile)
var base64Image = androidImagePicker.imageToBase64(resultFile)
rsApi.request("/identity/set_avatar", JSON.stringify({"gxs_id": cntDt.md.gxs_id, "avatar": base64Image }),
function (par)
{
var jP = JSON.parse(par.response)
if (jP.returncode === "ok")
{
console.log("Avatar changed! ")
topFace.refresh()
}
})
}
}
}
AvatarOrColorHash
{
@ -33,7 +89,7 @@ Item
gxs_id: cntDt.md.gxs_id
anchors.top: parent.top
anchors.top: avatarPicker.bottom
anchors.topMargin: 6
anchors.horizontalCenter: parent.horizontalCenter
}
@ -68,10 +124,11 @@ Item
Image
{
source: cntDt.is_contact ?
"qrc:/icons/rating.png" :
"qrc:/icons/rating-unrated.png"
height: parent.height - 4
"qrc:/icons/rating.svg" :
"qrc:/icons/rating-unrated.svg"
height: parent.height -4
fillMode: Image.PreserveAspectFit
sourceSize.height: height
anchors.verticalCenter: parent.verticalCenter
MouseArea
@ -112,9 +169,11 @@ Item
spacing: 6
Button
ButtonText
{
text: qsTr("Contact full link")
borderRadius: 0
buttonTextPixelSize: 14
onClicked:
{
rsApi.request(
@ -140,10 +199,12 @@ Item
}
}
Button
ButtonText
{
text: qsTr("Contact short link")
enabled: false
borderRadius: 0
buttonTextPixelSize: 14
}
}
}

View File

@ -30,9 +30,20 @@ Item
property bool searching: false
onSearchingChanged: !searching && contactsSortWorker.sendMessage({})
Component.onCompleted: refreshAll()
property string objectName:"contactsView"
Component.onCompleted:
{
refreshAll()
}
onFocusChanged: focus && refreshAll()
function changeState ()
{
toolBar.state = "CONTACTSVIEW"
toolBar.searchBtnCb = toggleSearchBox
}
WorkerScript
{
id: contactsSortWorker
@ -40,6 +51,11 @@ Item
onMessage: contactsListModel.json = JSON.stringify(messageObject)
}
function toggleSearchBox (){
if (searching) searching = false
else searching = true
}
function refreshAll()
{
refreshOwn()
@ -51,6 +67,7 @@ Item
{
console.log("contactsView.refreshContactsCB()", visible)
var token = JSON.parse(par.response).statetoken
ChatCache.contactsCache.contactsList = JSON.parse(par.response).data
TokensManager.registerToken(token, refreshContacts)
contactsSortWorker.sendMessage(
{'action': 'refreshContacts', 'response': par.response})
@ -71,6 +88,7 @@ Item
if(json.data.length > 0)
{
ChatCache.contactsCache.own = json.data[0]
contactsView.own_gxs_id = json.data[0].gxs_id
contactsView.own_nick = json.data[0].name
if(mainWindow.user_name.length === 0)
@ -101,6 +119,11 @@ Item
TokensManager.registerToken(json.statetoken, refreshUnread)
contactsSortWorker.sendMessage(
{'action': 'refreshUnread', 'response': par.response})
json.data.forEach (function (chat)
{
ChatCache.lastMessageCache.updateLastMessageCache(chat.chat_id)
ChatCache.lastMessageCache.setRemoteGXS (chat.chat_id, { gxs: chat.remote_author_id, name: chat.remote_author_name})
})
}
function refreshUnread()
{
@ -109,13 +132,6 @@ Item
rsApi.request("/chat/unread_msgs", "", refreshUnreadCallback)
}
function startChatCallback(par)
{
var chId = JSON.parse(par.response).data.chat_id
stackView.push("qrc:/ChatView.qml", {'chatId': chId})
}
/** This must be equivalent to
p3GxsTunnelService::makeGxsTunnelId(...) */
function getChatId(from_gxs, to_gxs)
@ -143,31 +159,59 @@ Item
Rectangle
{
id: searchBox
visible: contactsView.searching
// visible: contactsView.searching
height: searchText.height
width: searchText.width
height: searchText.height + 10
width: parent.width * 0.9
anchors.right: parent.right
anchors.top: parent.top
Image
{
id: searchIcon
height: searchText.height - 4
width: searchText.height - 4
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/icons/edit-find.png"
}
anchors.leftMargin: 5
anchors.rightMargin: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "white"
TextField
{
id: searchText
anchors.left: searchIcon.right
anchors.verticalCenter: parent.verticalCenter
// placeholderText : "Search contacts..."
width: parent.width
anchors.leftMargin: 5
height: 0
background: Rectangle
{
border.width: 2
radius: 5
border.color: searchText.focus ? "cornflowerblue" : "lightgrey"
color: searchText.focus ? "white" : "ghostwhite"
}
onTextChanged:
contactsSortWorker.sendMessage(
{'action': 'searchContact', 'sexp': text})
}
states:
[
State
{
when: contactsView.searching;
PropertyChanges { target: searchText; height: implicitHeight }
PropertyChanges { target: searchText; placeholderText : "Search contacts..." }
PropertyChanges { target: searchBox; height: searchText.height + 10 }
},
State
{
when: !contactsView.searching;
PropertyChanges { target: searchText; height: 0 }
PropertyChanges { target: searchBox; height: 0 }
}
]
transitions: Transition
{
NumberAnimation { property: "height"; duration: 500; easing.type: Easing.InOutQuad}
}
}
Text
@ -187,4 +231,5 @@ Item
property bool defaultIdentityCreated: false
}
}

View File

@ -18,101 +18,252 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import "." //Needed for ChatCache singleton
import "./components"
Item
{
id: delegateRoot
height: 40
height: 57
width: parent.width
MouseArea
property var chatId: undefined
property var lastMessageData: ({})
property var locale: Qt.locale()
Rectangle
{
id: contactItem
anchors.fill: parent
onClicked:
color: contactItemArea.containsPress ? "lightgrey" : "transparent"
width: parent.width
height: parent.height
MouseArea
{
console.log("GxsIntentityDelegate onclicked:", model.name,
model.gxs_id)
contactsView.searching = false
if(model.own) contactsView.own_gxs_id = model.gxs_id
else
id: contactItemArea
anchors.fill: parent
onClicked:
{
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)
}
}
onPressAndHold: showDetails()
ColorHash
{
id: colorHash
hash: model.gxs_id
height: parent.height - 4
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 2
MouseArea
{
anchors.fill: parent
onClicked: delegateRoot.showDetails()
}
}
Text
{
id: nickText
color: model.own ? "blue" : "black"
text: model.name
anchors.left: colorHash.right
anchors.leftMargin: 5
anchors.verticalCenter: parent.verticalCenter
}
Row
{
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
height: parent.height - 10
spacing: 4
Rectangle
{
visible: model.unread_count > 0
color: "cornflowerblue"
antialiasing: true
border.color: "blue"
border.width: 1
height: parent.height - 4
radius: height/2
width: height
anchors.verticalCenter: parent.verticalCenter
Text
console.log("GxsIntentityDelegate onclicked:", model.name,
model.gxs_id)
contactsView.searching = false
if(model.own) contactsView.own_gxs_id = model.gxs_id
else
{
color: "white"
font.bold: true
text: model.unread_count
anchors.centerIn: parent
startDistantChat()
}
}
Image
onPressAndHold: showDetails()
hoverEnabled: true
}
Rectangle
{
id: backgroundRectangle
anchors.fill: parent.fill
anchors.right: parent.right
width: parent.width - colorHash.width - 15
height: parent.height
color:"transparent"
Rectangle
{
source: model.is_contact ?
"qrc:/icons/rating.png" :
"qrc:/icons/rating-unrated.png"
height: parent.height - 4
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
id: borderBottom
width: parent.width
height: 1
anchors.bottom: parent.bottom
anchors.right: parent.right
color: "lightgrey"
}
}
Rectangle
{
anchors.fill: parent
color: "transparent"
anchors.margins: 5
AvatarOrColorHash
{
id: colorHash
gxs_id: model.gxs_id
height: parent.height - 4
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 2
onlyCached: true
}
Column
{
id: chatInfoRow
height: parent.height
width: parent.width - isContactRow.width - colorHash.width
anchors.left: colorHash.right
anchors.leftMargin: 10
anchors.right: isContactRow.left
anchors.rightMargin: 5
Item
{
width: parent.width
height: parent.height /2
Text
{
id: nickText
color: model.own ? "blue" : "black"
text: model.name
font.bold: true
anchors.bottom: parent.bottom
}
Text
{
text: setTime()
anchors.right: parent.right
color: "darkslategrey"
anchors.bottom: parent.bottom
}
}
Item
{
id: lastMessageText
width: parent.width
height: (lastMessageData && lastMessageData.msg !== undefined)? parent.height /2 : 0
Text
{
id: lastMessageSender
font.italic: true
color: "royalblue"
text: ((lastMessageData && lastMessageData.incoming !== undefined) && !lastMessageData.incoming)? "You: " : ""
height: parent.height
}
Text
{
id: lastMessageMsg
anchors.left: lastMessageSender.right
text: (lastMessageData && lastMessageData.msg !== undefined)? lastMessageData.msg : ""
rightPadding: 5
elide: Text.ElideRight
color: "darkslategrey"
width: chatInfoRow.width - 30
height: parent.height
}
Rectangle
{
visible: model.unread_count > 0
color: "cornflowerblue"
antialiasing: true
height: parent.height - 6
radius: height/2
width: height
// anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
Text
{
color: "white"
font.bold: true
text: model.unread_count
anchors.centerIn: parent
}
}
}
}
Row
{
id: isContactRow
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
height: parent.height - 10
spacing: 4
Image
{
source: model.is_contact ?
"qrc:/icons/rating.svg" :
"qrc:/icons/rating-unrated.svg"
height: parent.height - 4
sourceSize.height: height
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
id: isContactIcon
}
}
}
}
Component.onCompleted:
{
if (!chatId){
chatId = getChatIdFromGXS()
}
if (chatId) {
var last = getChatLastMessage(chatId)
if (last) lastMessageData = last
}
}
Connections
{
target: ChatCache.lastMessageCache
onLastMessageChanged: {
if (!chatId) {
chatId = getChatIdFromGXS()
}
if (chatId && chatId === chatI){
console.log("New last message received!")
lastMessageData = newLastMessage
}
}
}
function getChatLastMessage (chatId)
{
return ChatCache.lastMessageCache.getChatLastMessage(chatId)
}
function getChatIdFromGXS ()
{
var id= ChatCache.lastMessageCache.getChatIdFromGxs(model.gxs_id)
return ChatCache.lastMessageCache.getChatIdFromGxs(model.gxs_id)
}
function setTime()
{
if (!lastMessageData || lastMessageData.recv_time === undefined) return ""
var timeFormat = "dd.MM.yyyy";
var recvDate = new Date(lastMessageData.recv_time*1000)
// Check if is today
if ( new Date (lastMessageData.recv_time*1000).setHours(0,0,0,0) == new Date ().setHours(0,0,0,0))
{
timeFormat = "hh:mm"
}
var timeString = Qt.formatDateTime(recvDate, timeFormat)
return timeString
}
function showDetails()
@ -123,4 +274,14 @@ Item
"qrc:/ContactDetails.qml",
{md: contactsListView.model.get(index)})
}
function startDistantChat ()
{
ChatCache.chatHelper.startDistantChat(contactsView.own_gxs_id, model.gxs_id, model.name,
function (chatId)
{
stackView.push("qrc:/ChatView.qml", {'chatId': chatId})
})
}
}

View File

@ -20,6 +20,8 @@ import QtQuick 2.7
import QtQuick.Controls 2.0
import org.retroshare.qml_components.LibresapiLocalClient 1.0
import "components/."
Item
{
id: locationView
@ -47,7 +49,7 @@ Item
target: loginView
visible: true
buttonText: qsTr("Save")
iconUrl: "qrc:/icons/edit-image-face-detect.png"
iconUrl: "qrc:/icons/edit-image-face-detect.svg"
suggestionText: qsTr("Create your profile")
onSubmit:
{
@ -62,6 +64,11 @@ Item
bottomButton.enabled = false
bottomButton.text = "Creating profile..."
}
onCancel:
{
locationView.state = "selectLocation"
}
}
},
State
@ -83,6 +90,10 @@ Item
locationView.attemptLogin = true
attemptTimer.start()
}
onCancel:
{
locationView.state = "selectLocation"
}
}
}
]
@ -127,28 +138,69 @@ Item
width: parent.width
anchors.top: parent.top
anchors.bottom: bottomButton.top
anchors.horizontalCenter: parent.horizontalCenter
model: locationsModel.model
delegate: Button
spacing: 3
delegate: Item
{
text: model.name
onClicked:
id: delegate
width: parent.width
height: 60
Rectangle
{
loginView.login = text
locationView.sslid = model.id
locationView.state = "login"
mainWindow.user_name = model.name
id: backgroundRectangle
anchors.fill: parent.fill
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width /2
height: parent.height
color:"transparent"
Rectangle
{
id: borderBottom
width: parent.width
height: 1
anchors.bottom: parent.bottom
anchors.right: parent.right
color: "lightgrey"
}
}
ButtonText
{
id: locationButton
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: model.name
borderRadius:0
iconUrl: "/icons/edit-image-face-detect.svg"
color: "white"
pressColor: "lightsteelblue"
buttonTextPixelSize: 20
onClicked:
{
loginView.login = text
locationView.sslid = model.id
locationView.state = "login"
mainWindow.user_name = model.name
}
}
}
visible: false
}
Button
ButtonText
{
id: bottomButton
text: "Create new location"
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
onClicked: locationView.state = "createLocation"
buttonTextPixelSize: 15
iconUrl: "/icons/add.svg"
borderRadius: 0
}
RsLoginPassView

View File

@ -19,12 +19,14 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0
import "components/."
Item
{
id: loginView
property string buttonText: qsTr("Unlock")
property string iconUrl: "qrc:/icons/emblem-locked.png"
property string cancelText: qsTr("Cancel")
property string iconUrl: "qrc:/icons/emblem-locked.svg"
property string login
property bool loginPreset: false
property bool advancedMode: false
@ -32,6 +34,7 @@ Item
property string password: advancedMode ? "" : hardcodedPassword
property string suggestionText
signal submit(string login, string password)
signal cancel()
Component.onCompleted: loginPreset = login.length > 0
@ -53,6 +56,8 @@ Item
{
source: loginView.iconUrl
Layout.alignment: Qt.AlignHCenter
height: 128
sourceSize.height: height
}
Text
@ -116,18 +121,32 @@ Item
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
spacing: 3
Button
ButtonIcon
{
text: qsTr("Advanced...")
visible: !loginView.loginPreset
onClicked: loginView.advancedMode = !loginView.advancedMode
imgUrl: "/icons/options.svg"
height: bottomButton.height - 5
width: height
anchors.verticalCenter: bottomButton.verticalCenter
}
Button
ButtonText
{
id: bottomButton
text: loginView.buttonText
onClicked: loginView.submit(nameField.text, passwordField.text)
iconUrl: "/icons/network.svg"
buttonTextPixelSize: 15
}
ButtonIcon
{
id: cancelButton
onClicked: loginView.cancel()
height: bottomButton.height - 5
width: height
imgUrl: "/icons/back.png"
anchors.verticalCenter: bottomButton.verticalCenter
}
}
}

View File

@ -18,6 +18,7 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import "components/."
Item
{
@ -25,19 +26,55 @@ Item
property string pgpName
property alias pgpId: pgpIdTxt.text
property bool isOnline
property string nodeCert
property var locations
function attemptConnectionCB (par)
{
console.log("attemptConnectionCB()", par.response)
}
Image
{
id: nodeStatusImage
source: isOnline?
"icons/state-ok.svg" :
"icons/state-offline.svg"
height: 128
sourceSize.height: height
fillMode: Image.PreserveAspectFit
anchors.top: parent.top
anchors.topMargin: 6
anchors.horizontalCenter: parent.horizontalCenter
}
Column
{
id: pgpColumn
anchors.top: parent.top
anchors.top: nodeStatusImage.bottom
width: parent.width
Text { text: nodeDetailsRoot.pgpName.replace(" (Generated by RetroShare) <>", "") }
Text { id: pgpIdTxt }
Text
{
text: nodeDetailsRoot.pgpName.replace(" (Generated by RetroShare) <>", "")
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 20
}
Text
{
id: pgpIdTxt
anchors.horizontalCenter: parent.horizontalCenter
color: "darkslategrey"
}
}
JSONListModel
@ -48,42 +85,96 @@ Item
ListView
{
width: parent.width
width: parent.width * .75
anchors.top: pgpColumn.bottom
anchors.topMargin: 5
anchors.bottom: buttonsRow.top
model: jsonModel.model
delegate: Column
anchors.horizontalCenter: parent.horizontalCenter
headerPositioning: ListView.OverlayHeader
clip: true
snapMode: ListView.SnapToItem
spacing:7
header:Rectangle
{
height: 60
color: "aliceblue"
width: parent.width
leftPadding: 4
spacing: 4
z: 2
height: headetText.contentHeight + 10
radius: 10
Row
Text
{
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
}
id: headetText
text: "Node locations ("+jsonModel.model.count+")"
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
font.italic: true
}
Text { text: model.peer_id }
}
delegate:
MouseArea
{
width: parent.width
height: innerCol.height
onClicked:
{
console.log("triggerLocationConnectionAttempt()")
rsApi.request("/peers/attempt_connection/",
JSON.stringify({peer_id: model.peer_id}) , attemptConnectionCB)
}
Column
{
id: innerCol
height: idRow.height + gxsInfo.height
width: parent.width
leftPadding: 4
spacing: 6
Row
{
id: idRow
height: 30
spacing: 4
Image
{
id: statusImage
source: model.is_online ?
"icons/network-connect.svg" :
"icons/network-disconnect.svg"
height: parent.height - 4
sourceSize.height: height
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
}
Text
{
id: locNameText
text: model.location
anchors.verticalCenter: parent.verticalCenter
}
}
TextAndIcon
{
id: gxsInfo
width: parent.width
innerText: model.peer_id
anchors.horizontalCenter: parent.horizontalCenter
iconUrl: "/icons/keyring.svg"
}
}
}
}
@ -95,9 +186,14 @@ Item
anchors.horizontalCenter: parent.horizontalCenter
spacing: 6
Button
ButtonText
{
text: qsTr("Revoke")
borderRadius: 0
buttonTextPixelSize: 14
iconUrl: "/icons/leave.svg"
onClicked:
rsApi.request(
"/peers/"+nodeDetailsRoot.pgpId+"/delete", "",
@ -105,11 +201,16 @@ Item
{ stackView.push("qrc:/TrustedNodesView.qml") })
}
Button
ButtonText
{
text: qsTr("Entrust")
visible: nodeDetailsRoot.nodeCert.length > 0
borderRadius: 0
buttonTextPixelSize: 14
iconUrl: "/icons/invite.svg"
onClicked:
{
var jsonData =

View File

@ -20,6 +20,7 @@ import QtQuick 2.7
import QtQuick.Controls 2.0
import "jsonpath.js" as JSONPath
import "." //Needed for TokensManager singleton
import "components/."
Item
{
@ -65,34 +66,29 @@ Item
anchors.top: parent.top
anchors.bottom: bottomButton.top
model: jsonModel.model
anchors.horizontalCenter: parent.horizontalCenter
spacing: 3
delegate: Item
{
height: 30
property bool isOnline: jsonModel.isOnline(model.pgp_id)
height: 54
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
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.left: parent.left
anchors.leftMargin: 3
anchors.verticalCenter: parent.verticalCenter
}
Text
ButtonText
{
id: locationButton
// anchors.horizontalCenter: parent.horizontalCenter
text: model.name
anchors.verticalCenter: parent.verticalCenter
anchors.left: statusImage.right
anchors.leftMargin: 10
}
MouseArea
{
anchors.fill: parent
borderRadius:0
iconUrl: isOnline?
"/icons/state-ok.svg" :
"/icons/state-offline.svg"
color: "transparent"
pressColor: "lightsteelblue"
buttonTextPixelSize: 18
iconHeight:parent.height - 4
onClicked:
{
stackView.push(
@ -100,6 +96,7 @@ Item
{
pgpName: model.name,
pgpId: model.pgp_id,
isOnline: isOnline,
locations: jsonModel.getLocations(
model.pgp_id)
}
@ -109,12 +106,17 @@ Item
}
}
Button
ButtonText
{
id: bottomButton
text: qsTr("Add Trusted Node")
text: qsTr("Add/Share Trusted Node")
anchors.bottom: parent.bottom
onClicked: stackView.push("qrc:/AddTrustedNode.qml")
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
// width: parent.width
borderRadius: 0
buttonTextPixelSize: 14
iconUrl: "/icons/add.svg"
}
}

View File

@ -11,6 +11,7 @@
android:label="RetroShare"
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
android:screenOrientation="unspecified"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@ -206,4 +207,8 @@
<!-- Added by G10h4ck: Needed permission for autostart at boot -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- Added by Angesoc: used to pick images from gallery or take it from camera -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
</manifest>

View File

@ -19,17 +19,25 @@
package org.retroshare.android.qml_app;
import android.app.ActivityManager;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.net.Uri;
import org.qtproject.qt5.android.bindings.QtActivity;
import org.retroshare.android.qml_app.jni.NativeCalls;
public class RetroShareQmlActivity extends QtActivity
{
static final int PICK_PHOTO = 1;
@Override
public void onCreate(Bundle savedInstanceState)
{
@ -57,12 +65,16 @@ public class RetroShareQmlActivity extends QtActivity
@Override
public void onNewIntent(Intent intent)
{
Log.i("RetroShareQmlActivity", "onNewIntent(Intent intent)");
Log.i("RetroShareQmlActivity", "on NewIntent(Intent intent)");
super.onNewIntent(intent);
String uri = intent.getDataString();
if (uri != null) NativeCalls.notifyIntentUri(uri);
if (uri != null)
{
NativeCalls.notifyIntentUri(uri);
Log.i("RetroShareQmlActivity", "onNewIntent(Intent intent) Uri: " + uri);
}
}
@UsedByNativeCode @SuppressWarnings("unused")
@ -84,4 +96,88 @@ public class RetroShareQmlActivity extends QtActivity
return true;
return false;
}
private Uri capturedImageURI;
public void openImagePicker()
{
Log.i("RetroShareQmlActivity", "openImagePicker()");
Intent pickIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickIntent.setType("image/*");
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, "Retroshare Avatar");
capturedImageURI = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
Intent takePicture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
takePicture.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageURI);
Intent chooserIntent = Intent.createChooser(pickIntent, "Select Image");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] {takePicture});
startActivityForResult( chooserIntent, PICK_PHOTO);
};
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
Log.i("RetroShareQmlActivity", "onActivityResult()" + String.valueOf(requestCode));
if (resultCode == RESULT_OK)
{
if (requestCode == PICK_PHOTO)
{
final boolean isCamera;
if (data == null)
{
isCamera = true;
}
else
{
final String action = data.getAction();
if (action == null)
{
isCamera = false;
}
else
{
isCamera = action.equals(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
}
}
Uri selectedImageUri;
if (isCamera)
{
selectedImageUri = capturedImageURI;
}
else
{
selectedImageUri = data == null ? null : data.getData();
}
String uri = getRealPathFromURI(selectedImageUri);
if (uri != null)
{
Log.i("RetroShareQmlActivity", "Image path from uri found!" + uri);
NativeCalls.notifyIntentUri("//file"+uri); // Add the authority for get it on qml code
}
}
}
}
public String getRealPathFromURI(Uri uri) {
String[] projection = { MediaStore.Images.Media.DATA };
@SuppressWarnings("deprecation")
Cursor cursor = managedQuery(uri, projection, null, null, null);
int column_index = cursor
.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
String result = cursor.getString(column_index);
return result;
}
}

View File

@ -0,0 +1,66 @@
#pragma once
#include <QObject>
#include <QDebug>
#include <QFile>
#include <QUrl>
#include <QImage>
#include <QImageReader>
#include <QBuffer>
#ifdef __ANDROID__
# include <QtAndroid>
# include <QtAndroidExtras/QAndroidJniObject>
#endif // __ANDROID__
struct AndroidImagePicker : QObject
{
Q_OBJECT
public slots:
static void openPicker()
{
qDebug() << "Starting image picker intent";
#ifdef __ANDROID__
QtAndroid::androidActivity().callMethod<void>(
"openImagePicker",
"()V" );
#endif // __ANDROID__
}
// Used to convert a given image path into a png base64 string
static QString imageToBase64 (QString const& path)
{
// Get local path from uri
QUrl url (path);
QString localPath = url.toLocalFile();
qDebug() << "imageToBase64() local path:" << localPath ;
// Read the image
QImageReader reader;
reader.setFileName(localPath);
QImage image = reader.read();
image = image.scaled(96,96,Qt::KeepAspectRatio,Qt::SmoothTransformation);
// Transform image into PNG format
QByteArray ba;
QBuffer buffer( &ba );
buffer.open( QIODevice::WriteOnly );
image.save( &buffer, "png" );
// Get Based 64 image string
QString encoded = QString(ba.toBase64());
qDebug() << "imageToBase64() encoded" ;
return encoded;
}
};

View File

@ -0,0 +1,159 @@
/*
* 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 "../" //Needed for Chat Cache
Item
{
id: compRoot
property string gxs_id
property bool onlyCached: false
signal clicked ()
height: 130
width: height
////////////// The following should be considered privates /////////////////////
property bool has_avatar: false
property bool default_image: false
property int avatarAttemptCnt: 0
property string noGxsImage: "/icons/retroshare.png"
function getDetails()
{
console.log("getDetails() ", compRoot.gxs_id )
++compRoot.avatarAttemptCnt
if (gxs_id)
{
default_image = false
var hasAvatarCached = hasAvatar (ChatCache.contactsCache.getIdentityAvatar(gxs_id))
if ( !hasAvatarCached )
{
rsApi.request(
"/identity/get_identity_details",
JSON.stringify({ gxs_id: compRoot.gxs_id }),
function(par)
{
var jData = JSON.parse(par.response).data
saveDetails(jData)
if(!compRoot.has_avatar &&
compRoot.avatarAttemptCnt < 3) getDetails()
})
}
else
{
setImage(ChatCache.contactsCache.getIdentityDetails(gxs_id))
}
}
else
{
has_avatar = true
default_image = true
contactAvatar.source = noGxsImage
}
}
function saveDetails(data)
{
ChatCache.contactsCache.setIdentityDetails(data)
setImage(data)
}
function setImage (data)
{
compRoot.has_avatar = hasAvatar (data.avatar)
if(compRoot.has_avatar)
{
contactAvatar.source =
"data:image/png;base64," + data.avatar
}
}
function hasAvatar (avatar)
{
return avatar.length > 0
}
function showDetails()
{
console.log("showDetails() ", gxs_id)
if (stackView.currentItem.objectName != "contactDetails")
{
stackView.push(
"qrc:/ContactDetails.qml",
{md: ChatCache.contactsCache.getContactFromGxsId(gxs_id)})
}
}
function refresh()
{
ChatCache.contactsCache.delIdentityAvatar(gxs_id)
compRoot.avatarAttemptCnt = 0
getDetails()
}
Component.onCompleted: startComponent ()
onVisibleChanged: startComponent ()
function startComponent ()
{
if (onlyCached && hasAvatar (ChatCache.contactsCache.getIdentityAvatar(gxs_id) ) )
{
console.log("load cached avatar")
setImage(ChatCache.contactsCache.getIdentityDetails(gxs_id))
}
else if (!onlyCached)
{
if(visible && (!has_avatar || default_image ) ) getDetails()
}
}
Image
{
id: contactAvatar
anchors.fill: parent
visible: compRoot.has_avatar
fillMode: Image.PreserveAspectFit
}
Faces
{
visible: !compRoot.has_avatar
hash: compRoot.gxs_id
anchors.fill: parent
iconSize: parent.height
}
MouseArea
{
anchors.fill: parent
onClicked:
{
compRoot.clicked()
showDetails()
}
}
}

View File

@ -0,0 +1,28 @@
import QtQuick 2.0
Item {
id: root
signal clicked
signal pressed
signal released
property var imgUrl: ""
property alias fillMode: image.fillMode
Image {
id: image
anchors.fill: parent
source: imgUrl
height: parent.height
width: parent.width
}
MouseArea {
anchors.fill: root
onClicked: { root.clicked() }
onPressed: { root.pressed() }
onReleased: { root.released() }
}
}

View File

@ -0,0 +1,130 @@
import QtQuick 2.0
import QtQuick.Controls 2.0
Item
{
id: button
property alias text: innerText.text;
property alias buttonTextPixelSize: innerText.font.pixelSize
property alias innerAnchors: innerElements.anchors;
property alias rectangleButton: rectangleButton;
property var iconUrl
property int iconHeight: 20
property color color: "lightsteelblue"
property color hoverColor: color
property color pressColor: color
property int fontSize
property int borderWidth
property int borderRadius: 3
property int innerMargin: 10
height: innerText.height + innerMargin + innerMargin
width: innerText.width + innerMargin + innerMargin + icon.width + icon.width
scale: state === "Pressed" ? 0.96 : 1.0
onEnabledChanged: state = ""
signal clicked
Rectangle
{
id: rectangleButton
anchors.fill: parent
radius: borderRadius
color: button.enabled ? button.color : "lavender"
border.width: borderWidth
border.color: "black"
ToolButton {
id: innerElements
Image
{
id: icon
source: (iconUrl)? iconUrl: ""
height: (iconUrl)? iconHeight: 0
width: (iconUrl)? iconHeight: 0
fillMode: Image.PreserveAspectFit
visible: (iconUrl)? true: false
anchors.left: innerElements.left
anchors.margins:(iconUrl)? innerMargin : 0
anchors.verticalCenter: parent.verticalCenter
sourceSize.height: height
}
Text
{
anchors.margins: innerMargin
id: innerText
font.pointSize: fontSize
anchors.left: icon.right
anchors.verticalCenter: parent.verticalCenter
color: button.enabled ? "black" : "grey"
}
}
}
states: [
State
{
name: "Hovering"
PropertyChanges
{
target: rectangleButton
color: hoverColor
}
},
State
{
name: "Pressed"
PropertyChanges
{
target: rectangleButton
color: pressColor
}
}
]
transitions: [
Transition
{
from: ""; to: "Hovering"
ColorAnimation { duration: 200 }
},
Transition
{
from: "*"; to: "Pressed"
ColorAnimation { duration: 10 }
}
]
MouseArea
{
hoverEnabled: true
anchors.fill: button
onEntered: { button.state='Hovering'}
onExited: { button.state=''}
onClicked: { button.clicked();}
onPressed: { button.state="Pressed" }
onReleased:
{
if (containsMouse)
button.state="Hovering";
else
button.state="";
}
}
Behavior on scale
{
NumberAnimation
{
duration: 100
easing.type: Easing.InOutQuad
}
}
}

View File

@ -28,8 +28,13 @@ Rectangle
Image
{
source: "qrc:/icons/edit-image-face-detect.png"
anchors.fill: parent
source: "qrc:/icons/edit-image-face-detect.svg"
anchors.centerIn: parent
height: parent.height
width: parent.width
sourceSize.height: height
sourceSize.width: width
fillMode: Image.PreserveAspectFit
}
Rectangle

View File

@ -0,0 +1,60 @@
import QtQuick 2.7
import QtQuick.Dialogs 1.2
import "../URI.js" as UriJs
Item
{
id: compRoot
property var resultFile
FileDialog
{
id: fileDialog
title: "Please choose a file"
folder: shortcuts.pictures
nameFilters: [ "Image files (*.png *.jpg)"]
visible: false
selectMultiple: false
onAccepted: {
console.log("You chose: " + fileDialog.fileUrl)
resultFile = fileDialog.fileUrl
}
onRejected: {
console.log("Canceled")
}
}
function open()
{
if (Qt.platform.os === "android")
{
console.log("ImagePicker Android platform detected")
mainWindow.addUriHandler("file", androidResult)
androidImagePicker.openPicker()
}
else
{
fileDialog.visible = true
}
}
function androidResult (uri)
{
console.log("QML Android image uri found" , uri)
resultFile = normalizeUriToFilePath (uri)
mainWindow.delUriHandler("media", androidResult)
}
function normalizeUriToFilePath (uriStr)
{
var uri = new UriJs.URI(uriStr)
var hPath = uri.path()
return "file:///"+hPath
}
}

View File

@ -0,0 +1,202 @@
import QtQuick 2.7
import Qt.labs.settings 1.0
import "../" // Needed by ChatCache (where stores generated faces)
Item
{
id: faces
property string hash
property var facesCache: ChatCache.facesCache
Image
{
id: imageAvatar
width: height
height: iconSize
visible: true
}
Canvas
{
id: canvasAvatar
width: height
height: canvasSizes
visible: false
renderStrategy: Canvas.Threaded;
renderTarget: Canvas.Image;
property var images
property var callback
onPaint:
{
var ctx = getContext("2d");
if (images)
{
for (y = 0 ; y< nPieces ; y++)
{
ctx.drawImage(images[y], 0, 0, iconSize, iconSize )
}
}
}
onPainted:
{
if (callback)
{
var data = toDataURL('image/png')
callback(data)
}
}
}
Component.onCompleted:
{
createFromHex(hash)
}
property var facesPath: "/icons/faces/"
property var iconSize: 32
property var canvasSizes: iconSize > 32 ? 64 : 32;
property var nPieces: 6
property var pieces: []
// Number of image files corresponding to heach part:
// [background, face, hair|head, mouth, clothes, eye ]
property var total:
({
female: [5, 4, 33, 17, 59, 53],
male: [5, 4, 36, 26, 65, 32]
})
function src (gender, piece, random)
{
var head = gender === 'female' ? 'head' : 'hair';
var pieces = ['background', 'face', head, 'mouth', 'clothes', 'eye'];
var num = random % total[gender][piece] + 1;
return facesPath+gender+canvasSizes+'/'+pieces[piece]+num+'.png';
}
function calcDataFromFingerprint(dataHex)
{
var females = total.female.reduce(function(previousValue, currentValue)
{
return previousValue * currentValue;
});
var males = total.male.reduce(function(previousValue, currentValue)
{
return previousValue * currentValue;
});
var ret = [];
var data = parseInt(dataHex, 16) % (females+males);
if (data - females < 0)
{
var i = 0;
ret = ['female'];
while (data > 0 || i < total.female.length)
{
ret.push(data % total.female[i]);
data = parseInt(data / total.female[i]);
i++;
}
}
else
{
data = data - females;
var i = 0;
ret = ['male'];
while (data > 0 || i < total.male.length)
{
ret.push(data % total.male[i]);
data = parseInt(data / total.male[i]);
i++;
}
}
return ret;
}
function generateImage(data, callback)
{
var gender = data[0];
var onloads = [];
for (var i=0; i<nPieces; i++)
{
var url = src(gender, i, data[i+1])
onloads.push(url)
canvasAvatar.loadImage(url)
}
canvasAvatar.images = onloads
canvasAvatar.callback = callback
canvasAvatar.requestPaint()
}
// Create the identicon
function createFromHex(dataHex)
{
var iconId = [dataHex, iconSize];
var update = function(data)
{
// This conditions are for solve a bug on an Lg S3.
// On this device the toDataURL() is incompleted.
// So for see the complete avatar at least at first execution we'll show the canvas,
// instead of the image component.
// See issue: https://gitlab.com/angesoc/RetroShare/issues/37
if (facesCache.iconCache[iconId])
{
imageAvatar.source = data
imageAvatar.visible = true
canvasAvatar.visible = false
canvasAvatar.height = 0
imageAvatar.height = iconSize
}
else
{
canvasAvatar.visible = true
imageAvatar.visible = false
canvasAvatar.height = iconSize
imageAvatar.height = 0
}
facesCache.iconCache[iconId] = data;
}
if (facesCache.iconCache.hasOwnProperty(iconId))
{
update(facesCache.iconCache[iconId])
}
else if(facesCache.callbackCache.hasOwnProperty(iconId))
{
facesCache.callbackCache[iconId].push(update)
}
else
{
var onImageGenerated = function(data)
{
facesCache.callbackCache[iconId].forEach(function(callback)
{
callback(data);
})
}
facesCache.callbackCache[iconId] = [update];
if (dataHex)
{
generateImage(calcDataFromFingerprint(dataHex), onImageGenerated);
}
}
}
}

View File

@ -0,0 +1,283 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import "../URI.js" as UriJs
import "../"
import "../components"
Drawer
{
property var styles: StyleSideBar
id: drawer
height: parent.height
width: Math.min(parent.width, parent.height) / 3 * styles.width
dragMargin: 10
Rectangle
{
width: parent.width
height: parent.height
ListView
{
id: listView
currentIndex: -1
anchors.fill:parent
clip: true
snapMode: ListView.SnapToItem
headerPositioning: ListView.OverlayHeader
header:Rectangle
{
id:header
property var styles: StyleSideBar.header
width: parent.width
height: colorHash.height + nickText.height + gxsText.height + 40
color: styles.color
AvatarOrColorHash
{
id: colorHash
gxs_id: (ChatCache.contactsCache.own)?ChatCache.contactsCache.own.gxs_id : ""
height: styles.avatarHeight
anchors.margins: styles.avatarMargins
anchors.horizontalCenter: header.horizontalCenter
anchors.top: header.top
onClicked: drawer.close()
}
Text
{
id: nickText
text: (ChatCache.contactsCache.own)?ChatCache.contactsCache.own.name : "Retroshare"
height: contentHeight
anchors.top: colorHash.bottom
anchors.left: header.left
anchors.right: header.right
anchors.leftMargin: 10
anchors.rightMargin: 10
horizontalAlignment:Text.AlignHCenter
wrapMode: Text.Wrap
color: styles.textColor
font.bold: true
font.pixelSize: styles.textNickSize
}
Text
{
id: gxsText
text: (ChatCache.contactsCache.own)?ChatCache.contactsCache.own.gxs_id : ""
// height: contentHeight
wrapMode: Text.WrapAnywhere
width: header.width
anchors.top: nickText.bottom
anchors.left: header.left
anchors.right: header.right
anchors.leftMargin: 10
anchors.rightMargin: 10
horizontalAlignment:Text.AlignHCenter
color: styles.textColor
font.pixelSize: styles.textGxsidSize
}
}
delegate: Item
{
property var styles: StyleSideBar.item
id: menuItem
width: parent.width
height: styles.height
Connections
{
target: mainWindow
onCoreReadyChanged:
{
if (model.showOnCoreReady)
{
setVisible(mainWindow.coreReady)
}
}
}
ButtonText
{
text: model.title
width: parent.width
height: parent.height
color: menuItem.styles.defaultColor
hoverColor: menuItem.styles.hoverColor
innerAnchors.left: rectangleButton.left
innerAnchors.verticalCenter: rectangleButton.verticalCenter
iconUrl: (model.icon)? model.icon : undefined
innerMargin: 20
buttonTextPixelSize: menuItem.styles.pixelSize
borderRadius: 0
}
MouseArea
{
property var lastItem
id: itemArea
width: parent.width
height: parent.height
onClicked:
{
if (listView.currentIndex != index || stackView.currentItem != lastItem)
{
listView.currentIndex = index
menuList.actions[model.title]();
lastItem = stackView.currentItem
// titleLabel.text = model.title
// stackView.replace(model.source)
}
drawer.close()
}
}
visible: (model.showOnCoreReady)? setVisible(mainWindow.coreReady) : true
Component.onCompleted:
{
if (model.showOnOsAndroid && !Q_OS_ANDROID)
{
menuItem.visible = false
menuItem.height = 0
}
}
function setVisible(b)
{
menuItem.visible = b
if (!b)
{
menuItem.height = 0
}
else
{
menuItem.height = styles.height
}
}
}
model: ListModel
{
id: menuList
property var actions :
{
"Contacts": function()
{
stackView.push("qrc:/Contacts.qml" )
},
"Trusted Nodes": function()
{
stackView.push("qrc:/TrustedNodesView.qml");
},
"Paste Link": function()
{
UriJs.URI.withinString(
ClipboardWrapper.getFromClipBoard(),
handleIntentUri);
},
"Share identity": function()
{
rsApi.request(
"/peers/self/certificate/", "",
function(par)
{
var radix = JSON.parse(par.response).data.cert_string
var name = mainWindow.user_name
var encodedName = UriJs.URI.encode(name)
var nodeUrl = (
"retroshare://certificate?" +
"name=" + encodedName +
"&radix=" + UriJs.URI.encode(radix) +
"&location=" + encodedName )
ClipboardWrapper.postToClipBoard(nodeUrl)
linkCopiedPopup.itemName = name
linkCopiedPopup.open()
platformGW.shareUrl(nodeUrl);
})
},
"Terminate Core": function()
{
rsApi.request("/control/shutdown");
}
}
ListElement
{
title: "Contacts"
showOnCoreReady: true
icon: "/icons/search.svg"
}
ListElement
{
title: "Trusted Nodes"
showOnCoreReady: true
icon: "/icons/netgraph.svg"
}
ListElement
{
title: "Paste Link"
showOnCoreReady: true
icon: "/icons/add.svg"
}
ListElement
{
title: "Share identity"
showOnCoreReady: true
icon: "/icons/share.svg"
}
ListElement
{
title: "Terminate Core"
showOnOsAndroid: false
icon: "/icons/exit.svg"
}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
Rectangle
{
property var styles: StyleSideBar.footer
width: parent.width
anchors.bottom: parent.bottom
height: Label.contentHeight
color: styles.color
Label
{
horizontalAlignment: Text.AlignRight
anchors.bottom: parent.bottom
anchors.right: parent.right
text: parent.styles.text
color: parent.styles.textColor
anchors.rightMargin: parent.styles.margins
anchors.bottomMargin: parent.styles.margins
}
}
}
}

View File

@ -0,0 +1,38 @@
import QtQuick 2.0
import QtQuick.Controls 2.0
Item
{
height: innerText.implicitHeight
property int iconHeight: 25
property alias iconUrl: icon.source
property alias innerText: innerText.text
Image
{
id: icon
height: iconHeight
width: height
fillMode: Image.PreserveAspectFit
anchors.left: parent.left
anchors.leftMargin: 4
anchors.verticalCenter: parent.verticalCenter
}
Text
{
id: innerText
wrapMode: Text.WrapAnywhere
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.left: icon.right
anchors.leftMargin: 5
}
}

View File

@ -0,0 +1,56 @@
import QtQuick 2.7
import QtQuick.Controls.Styles 1.2
Rectangle {
id: emojiButton
property var fontName
Text {
id: emojiText
color: "gray"
text: qsTr(eCatText)
font.pixelSize: emojiButton.width - 8
anchors.centerIn: parent
font.family: fontName
}
state: "RELEASED"
states: [
State {
name: "PRESSED"
PropertyChanges {
target: emojiText
font.pixelSize: emojiButton.width - 10
}
},
State {
name: "RELEASED"
PropertyChanges {
target: emojiText
font.pixelSize: emojiButton.width - 8
}
}
]
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
emojiText.color = "black"
}
onExited: {
emojiText.color = "gray"
}
onPressedChanged: {
emojiButton.state = emojiButton.state == "PRESSED" ? "RELEASED" : "PRESSED"
}
onClicked: {
Qt.emojiClickedHandler(emojiText.text)
}
}
}

View File

@ -0,0 +1,82 @@
import QtQuick 2.7
import QtQuick.Controls.Styles 1.2
Rectangle {
id: emojiCategoryButton
property string categoryName
property var fontName
function completedHandler() {
categoryName = eCatName
//initialize
if (parent.currSelEmojiButton === undefined) {
clickedHandler()
}
}
function pressedHandler() {
if (state != "SELECTED") {
state = state == "PRESSED" ? "RELEASED" : "PRESSED"
}
}
function clickedHandler() {
if (parent.currSelEmojiButton !== undefined) {
parent.currSelEmojiButton.state = "RELEASED"
}
parent.currSelEmojiButton = emojiCategoryButton
state = "SELECTED"
Qt.emojiCategoryChangedHandler(emojiCategoryButton.categoryName)
}
Text {
id: emojiText
color: "gray"
text: qsTr(eCatText)
font.pixelSize: emojiCategoryButton.width - 8
anchors.centerIn: parent
font.family: fontName
}
state: "RELEASED"
states: [
State {
name: "PRESSED"
PropertyChanges {
target: emojiText
font.pixelSize: emojiCategoryButton.width - 10
}
},
State {
name: "RELEASED"
PropertyChanges {
target: emojiText
font.pixelSize: emojiCategoryButton.width - 8
}
},
State {
name: "SELECTED"
PropertyChanges {
target: emojiCategoryButton
color: "#ADD6FF"
}
}
]
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: emojiText.color = "black"
onExited: emojiText.color = "gray"
onPressedChanged: pressedHandler()
onClicked: clickedHandler()
}
Component.onCompleted: completedHandler()
}

View File

@ -0,0 +1,130 @@
import QtQuick 2.7
import QtQuick.Controls 2.0
import "emoji.js" as EmojiJSON
Rectangle {
id: emojiPicker
property EmojiCategoryButton currSelEmojiButton
property variant emojiParsedJson
property int buttonWidth: 40
property TextArea textArea
property var rootFontName: theme.emojiFontName
//displays all Emoji of one categroy by modifying the ListModel of emojiGrid
function categoryChangedHandler (newCategoryName){
emojiByCategory.clear()
for (var i = 0; i < emojiParsedJson.emoji_by_category[newCategoryName].length; i++) {
var elem = emojiParsedJson.emoji_by_category[newCategoryName][i]
emojiByCategory.append({eCatName: newCategoryName, eCatText: elem})
}
}
//adds the clicked Emoji (and one ' ' if the previous character isn't an Emoji) to textArea
function emojiClickedHandler(selectedEmoji) {
var strAppnd = ""
var plainText = textArea.getText(0, textArea.length)
if (plainText.length > 0) {
var lastChar = plainText[plainText.length-1]
if ((lastChar !== ' ') && (lastChar.charCodeAt(0) < 255)) {
strAppnd = " "
}
}
strAppnd += selectedEmoji
textArea.insert(textArea.cursorPosition, strAppnd)
}
//parses JSON, publishes button handlers and inits textArea
function completedHandler() {
// emojiParsedJson = JSON.parse(EmojiJSON.emoji_json)
emojiParsedJson = EmojiJSON.emoji_json
for (var i = 0; i < emojiParsedJson.emoji_categories.length; i++) {
var elem = emojiParsedJson.emoji_categories[i]
emojiCategoryButtons.append({eCatName: elem.name, eCatText: elem.emoji_unified})
}
Qt.emojiCategoryChangedHandler = categoryChangedHandler
Qt.emojiClickedHandler = emojiClickedHandler
textArea.cursorPosition = textArea.length
textArea.Keys.pressed.connect(keyPressedHandler)
}
//checks if the previous character is an Emoji and adds a ' ' if that's the case
//this is necessary, because Emoji use a bigger font-size, and that font-size is kept using without a ' '
function keyPressedHandler(event) {
var testStr = textArea.getText(textArea.length-2, textArea.length)
var ptrn = new RegExp("[\uD800-\uDBFF][\uDC00-\uDFFF]")
if ((event.key !== Qt.Key_Backspace) && (ptrn.test(testStr))) {
textArea.text += " "
textArea.cursorPosition = textArea.length
}
}
//all emoji of one category
ListModel {
id: emojiByCategory
}
GridView {
id: emojiGrid
width: parent.width
anchors.fill: parent
anchors.bottomMargin: buttonWidth
cellWidth: buttonWidth; cellHeight: buttonWidth
model: emojiByCategory
delegate: EmojiButton {
width: buttonWidth
height: buttonWidth
color: emojiPicker.color
fontName: rootFontName
}
}
//seperator
Rectangle {
color: emojiPicker.color
anchors.bottom: parent.bottom
width: parent.width
height: buttonWidth
}
Rectangle {
color: "black"
anchors.bottom: parent.bottom
anchors.bottomMargin: buttonWidth
width: parent.width
height: 1
}
//emoji category selector
ListView {
width: parent.width
anchors.bottom: parent.bottom
anchors.bottomMargin: buttonWidth
orientation: ListView.Horizontal
model: emojiCategoryButtons
delegate: EmojiCategoryButton {
width: buttonWidth
height: buttonWidth
color: emojiPicker.color
fontName: rootFontName
}
}
ListModel {
id: emojiCategoryButtons
}
Component.onCompleted:
{
completedHandler()
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg4155"
version="1.1"
inkscape:version="0.91 r13725"
xml:space="preserve"
width="80"
height="80"
viewBox="0 0 80 80"
sodipodi:docname="add.svg"><metadata
id="metadata4161"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs4159" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="706"
id="namedview4157"
showgrid="false"
inkscape:zoom="5.11875"
inkscape:cx="28.462795"
inkscape:cy="38.239659"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g4163" /><g
id="g4163"
inkscape:groupmode="layer"
inkscape:label="ink_ext_XXXXXX"
transform="matrix(1.25,0,0,-1.25,0,80)"><path
inkscape:connector-curvature="0"
id="path4167"
style="fill:#039bd5;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 64,32 C 64,14.327 49.673,0 32,0 14.327,0 0,14.327 0,32 0,49.673 14.327,64 32,64 49.673,64 64,49.673 64,32" /><path
inkscape:connector-curvature="0"
style="fill:#ffffff"
d="m 52.357845,35.83752 -15.874967,0 c -0.560293,0 -0.933823,0.373529 -0.933823,0.933822 l 0,15.874965 c 0,1.120588 -0.747056,1.867645 -1.867641,1.867645 l -3.735288,0 c -1.120584,0 -1.867644,-0.747057 -1.867644,-1.867645 l 0,-15.874965 c 0,-0.560293 -0.373527,-0.933822 -0.933819,-0.933822 l -15.874968,0 c -1.120586,0 -1.8676431,-0.747057 -1.8676431,-1.867644 l 0,-3.735285 c 0,-1.120585 0.7470571,-1.867644 1.8676431,-1.867644 l 15.874968,0 c 0.560292,0 0.933819,-0.373527 0.933819,-0.933821 l 0,-15.874967 c 0,-1.120585 0.74706,-1.8676415 1.867644,-1.8676415 l 3.735288,0 c 1.120585,0 1.867641,0.7470565 1.867641,1.8676415 l 0,15.874967 c 0,0.560294 0.37353,0.933821 0.933823,0.933821 l 15.874967,0 c 1.120585,0 1.867641,0.747059 1.867641,1.867644 l 0,3.735285 c 0,1.120587 -0.747056,1.867644 -1.867641,1.867644 z"
id="path4" /></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 2 3 L 2 5 L 14 5 L 14 3 L 2 3 z M 2 7 L 2 9 L 14 9 L 14 7 L 2 7 z M 2 11 L 2 13 L 14 13 L 14 11 L 2 11 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 443 B

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg4311"
version="1.1"
inkscape:version="0.91 r13725"
xml:space="preserve"
width="80"
height="80"
viewBox="0 0 80 80"
sodipodi:docname="send-image.svg"><metadata
id="metadata4317"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs4315" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1009"
id="namedview4313"
showgrid="false"
inkscape:zoom="2.085965"
inkscape:cx="-284.73674"
inkscape:cy="65.541346"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g4319" /><g
id="g4319"
inkscape:groupmode="layer"
inkscape:label="ink_ext_XXXXXX"
transform="matrix(1.25,0,0,-1.25,0,80)"><path
inkscape:connector-curvature="0"
id="path4323"
style="fill:#039bd5;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 64,32 C 64,14.327 49.673,0 32,0 14.327,0 0,14.327 0,32 0,49.673 14.327,64 32,64 49.673,64 64,49.673 64,32" /><path
inkscape:connector-curvature="0"
id="path4325"
style="fill:#000000;fill-opacity:0.15686275;fill-rule:nonzero;stroke:none"
d="m 45.545501,13.221531 -27.091002,0 c -1.870464,0 -3.386375,1.516249 -3.386375,3.386375 l 0,27.091001 c 0,1.870464 1.515911,3.386375 3.386375,3.386375 l 27.091002,0 c 1.870464,0 3.386375,-1.515911 3.386375,-3.386375 l 0,-27.091001 c 0,-1.870126 -1.515911,-3.386375 -3.386375,-3.386375" /><path
inkscape:connector-curvature="0"
id="path4327"
style="fill:#7db6d8;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 18.454499,16.607906 c -0.933877,0 -1.693187,0.759987 -1.693187,1.693187 l 0,27.091001 c 0,0.933201 0.75931,1.693188 1.693187,1.693188 l 27.091002,0 c 0.933877,0 1.693187,-0.759987 1.693187,-1.693188 l 0,-27.091001 c 0,-0.9332 -0.75931,-1.693187 -1.693187,-1.693187 l -27.091002,0" /><path
inkscape:connector-curvature="0"
id="path4329"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 45.545501,48.778469 -27.091002,0 c -1.870464,0 -3.386375,-1.51591 -3.386375,-3.386375 l 0,-27.091001 c 0,-1.870125 1.515911,-3.386375 3.386375,-3.386375 l 27.091002,0 c 1.870464,0 3.386375,1.51625 3.386375,3.386375 l 0,27.091001 c 0,1.870465 -1.515911,3.386375 -3.386375,3.386375 z m 0,-3.386375 0,-27.091001 -27.091002,0 0,27.091001 27.091002,0" /><path
inkscape:connector-curvature="0"
id="path4331"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 18.454499,18.301093 27.091002,0 0,3.386375 -27.091002,0 0,-3.386375 z" /><path
inkscape:connector-curvature="0"
id="path4333"
style="fill:#495672;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 16.752423,21.687468 29.542736,0 0,2.472985 c 0,0 -1.444119,2.520564 -2.782499,4.099716 -0.995425,1.174987 -2.481875,1.507275 -3.778856,0.06417 l -2.330165,-2.237886 -5.078547,6.458241 c -1.891629,2.405173 -4.986607,2.405173 -6.877897,0 L 16.752423,21.687468" /><path
inkscape:connector-curvature="0"
id="path4335"
style="fill:#bcd9ff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 41.312532,38.619344 c 0,-1.402806 -1.137314,-2.539781 -2.539782,-2.539781 -1.402806,0 -2.539781,1.136975 -2.539781,2.539781 0,1.402806 1.136975,2.539781 2.539781,2.539781 1.402468,0 2.539782,-1.136975 2.539782,-2.539781" /><path
inkscape:connector-curvature="0"
id="path4337"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 45.545501,48.778469 -27.091002,0 c -1.870464,0 -3.386375,-1.51591 -3.386375,-3.386375 l 0,-27.091001 c 0,-1.870125 1.515911,-3.386375 3.386375,-3.386375 l 27.091002,0 c 1.870464,0 3.386375,1.51625 3.386375,3.386375 l 0,27.091001 c 0,1.870465 -1.515911,3.386375 -3.386375,3.386375 z m 0,-3.386375 0,-27.091001 -27.091002,0 0,27.091001 27.091002,0" /></g></svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="80"
style="enable-background:new 0 0 110 110;"
version="1.0"
viewBox="0 0 110 110"
width="80"
xml:space="preserve"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="attach.svg"><metadata
id="metadata20"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs18" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1009"
id="namedview16"
showgrid="false"
inkscape:zoom="3.0341309"
inkscape:cx="-75.947202"
inkscape:cy="16.755676"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><g
id="Artboard" /><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Layer 1"
style="display:inline"><circle
id="circle6"
style="fill:#039bd5;fill-opacity:1"
r="55"
cy="55"
cx="55" /><path
style="fill:#000000;fill-opacity:0.15686275"
sodipodi:nodetypes="ccccccccccsasccsccccccccccccccsasccccccccccc"
id="use4188"
d="m 32.658016,88.493222 c -3.615848,8.28e-4 -7.263315,-1.424329 -10.041357,-4.202659 -2.77944,-2.779018 -4.205861,-6.427875 -4.204884,-10.04303 -0.0049,-3.403438 1.278081,-6.83375 3.862101,-9.413455 l 0.593216,-0.593494 35.641173,-35.642841 c 3.391596,-3.396336 7.894417,-5.081409 12.383162,-5.075283 4.771078,-0.002 9.594249,1.87938 13.276261,5.560136 3.678385,3.680194 5.559013,8.500861 5.558044,13.271665 0.0042,4.490277 -1.679643,8.991979 -5.075281,12.385393 -10.716723,10.715527 -20.64003,20.648354 -29.983159,29.721144 -1.137853,1.104931 -3.354856,1.236196 -4.391767,0.200648 -1.067827,-1.066425 -1.137299,-3.218296 0.200684,-4.522981 9.420341,-9.185906 29.722264,-29.852779 29.722264,-29.852779 2.153348,-2.157945 3.224312,-4.981261 3.229331,-7.9316 -4.97e-4,-3.126264 -1.22794,-6.331216 -3.714323,-8.818435 -2.48722,-2.488056 -5.693845,-3.714325 -8.821914,-3.716554 -2.949092,0.0058 -5.771848,1.076122 -7.928541,3.230447 L 27.31921,68.693496 26.726551,69.286573 c -1.343545,1.346615 -2.011143,3.098819 -2.016991,4.960511 0.002,1.972136 0.773588,4.003619 2.360607,5.591332 1.585346,1.583812 3.615429,2.354624 5.587991,2.356842 1.861408,-0.0058 3.613479,-0.673018 4.961066,-2.016569 L 62.839621,54.959584 64.95174,52.847599 c 0.506025,-0.509784 0.757852,-1.157881 0.76273,-1.899994 0,-0.782361 -0.304758,-1.604703 -0.959542,-2.259626 -0.656167,-0.656451 -1.478649,-0.960092 -2.260316,-0.962322 -0.741838,0.0064 -1.388963,0.258657 -1.899442,0.765517 0,0 -17.740223,17.818894 -20.832589,21.018568 -1.223513,1.265969 -3.596261,1.141861 -4.639005,3e-6 -1.036591,-1.135117 -0.92155,-3.247964 0.417589,-4.592681 3.215314,-3.228707 20.59883,-20.877036 20.59883,-20.877036 1.744968,-1.750676 4.072705,-2.616475 6.354335,-2.610904 2.425928,-0.002 4.864259,0.956613 6.715226,2.80674 1.845669,1.848595 2.803537,4.284834 2.803537,6.711885 0.0049,2.28024 -0.859952,4.607559 -2.608679,6.353359 l -2.112816,2.111986 -25.22026,25.220118 c -2.576358,2.578305 -5.997345,3.860847 -9.393545,3.860151 l -0.01975,-1.41e-4 0,0 z"
inkscape:connector-curvature="0" /><path
inkscape:connector-curvature="0"
d="m 32.658016,86.241133 c -3.615848,8.27e-4 -7.263315,-1.424329 -10.041357,-4.202659 -2.77944,-2.779019 -4.205861,-6.427875 -4.204884,-10.043031 -0.0049,-3.403437 1.278081,-6.83375 3.862101,-9.413456 l 0.593216,-0.593494 35.641173,-35.642841 c 3.391596,-3.396336 7.894417,-5.081408 12.383162,-5.075281 4.771078,-0.002 9.594249,1.879379 13.276261,5.560134 3.678385,3.680195 5.559013,8.500863 5.558044,13.271665 0.0042,4.490278 -1.679643,8.991981 -5.075281,12.385394 -10.716723,10.715526 -20.64003,20.648355 -29.983159,29.721144 -1.137853,1.10493 -3.354856,1.236196 -4.391767,0.200647 -1.067827,-1.066424 -1.137299,-3.218295 0.200684,-4.522981 9.420341,-9.185904 29.722264,-29.852778 29.722264,-29.852778 2.153348,-2.157945 3.224312,-4.981262 3.229331,-7.9316 -4.97e-4,-3.126264 -1.22794,-6.331217 -3.714323,-8.818435 -2.48722,-2.488056 -5.693845,-3.714325 -8.821914,-3.716555 -2.949092,0.0058 -5.771848,1.076123 -7.928541,3.230448 L 27.31921,66.441405 26.726551,67.034482 c -1.343545,1.346615 -2.011143,3.098821 -2.016991,4.960512 0.002,1.972136 0.773588,4.003619 2.360607,5.591331 1.585346,1.583813 3.615429,2.354624 5.587991,2.356843 1.861408,-0.0058 3.613479,-0.673017 4.961066,-2.01657 L 62.839621,52.707493 64.95174,50.595508 c 0.506025,-0.509784 0.757852,-1.15788 0.76273,-1.899994 0,-0.78236 -0.304758,-1.604703 -0.959542,-2.259625 -0.656167,-0.656452 -1.478649,-0.960092 -2.260316,-0.962321 -0.741838,0.0064 -1.388963,0.258655 -1.899442,0.765515 0,0 -17.740223,17.818896 -20.832589,21.01857 -1.223513,1.265968 -3.596261,1.14186 -4.639005,3e-6 -1.036591,-1.135118 -0.92155,-3.247965 0.417589,-4.592682 3.215314,-3.228708 20.59883,-20.877037 20.59883,-20.877037 1.744968,-1.750675 4.072705,-2.616473 6.354335,-2.610903 2.425928,-0.002 4.864259,0.956612 6.715226,2.806741 1.845669,1.848594 2.803537,4.284833 2.803537,6.711884 0.0049,2.28024 -0.859952,4.607558 -2.608679,6.353358 l -2.112816,2.111987 -25.22026,25.220118 c -2.576358,2.578306 -5.997345,3.860847 -9.393545,3.860151 l -0.01975,-1.4e-4 0,0 z"
id="path3"
sodipodi:nodetypes="ccccccccccsasccsccccccccccccccsasccccccccccc"
style="fill:#ffffff;fill-opacity:1" /></g></svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
xml:space="preserve"
width="80"
height="80"
viewBox="0 0 80 80"
sodipodi:docname="chat-bubble.svg"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs6"><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath16"><path
d="M 0,64 64,64 64,0 0,0 0,64 Z"
id="path18"
inkscape:connector-curvature="0" /></clipPath><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath28"><path
d="M 12,50 52,50 52,8 12,8 12,50 Z"
id="path30"
inkscape:connector-curvature="0" /></clipPath></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1009"
id="namedview4"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="-34.290603"
inkscape:cy="29.334046"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g10" /><g
id="g10"
inkscape:groupmode="layer"
inkscape:label="Elegant_circle-icons"
transform="matrix(1.25,0,0,-1.25,0,80)"><g
id="g26" /><path
inkscape:connector-curvature="0"
id="path22"
style="fill:#039bd5;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 64,32 C 64,14.327 49.673,0 32,0 14.327,0 0,14.327 0,32 0,49.673 14.327,64 32,64 49.673,64 64,49.673 64,32" /><g
id="use4160"
transform="translate(0,-1.9728814)"
style="fill:#000000;fill-opacity:0.15686275"><g
id="g4164"
transform="translate(49,35.2)"
style="fill:#000000;fill-opacity:0.15686275"><path
style="fill:#000000;fill-opacity:0.15686275;fill-rule:nonzero;stroke:none"
d="M 40 17.466797 C 26.1925 17.466797 15 27.540547 15 39.966797 C 15 51.933049 25.379902 61.713849 38.478516 62.421875 C 39.323921 66.895192 40 69.966797 40 69.966797 C 40 69.966797 46.28026 66.159283 52.013672 59.699219 C 59.752513 55.874654 65 48.473572 65 39.966797 C 65 27.540547 53.8075 17.466797 40 17.466797 z "
transform="matrix(0.8,0,0,-0.8,-49,30.772881)"
id="path4166" /></g><g
id="g4168"
transform="translate(52,34)"
style="fill:#000000;fill-opacity:0.15686275" /></g><g
id="g4154"><g
transform="translate(49,35.2)"
id="g40"><path
inkscape:connector-curvature="0"
id="path42"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 0,-14.963 -17,-25.2 -17,-25.2 0,0 -9.389,42 0,42 C -7.611,16.8 0,9.278 0,0" /></g><g
transform="translate(52,34)"
id="g44"><path
inkscape:connector-curvature="0"
id="path46"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 0,-9.941 -8.954,-18 -20,-18 -11.046,0 -20,8.059 -20,18 0,9.941 8.954,18 20,18 C -8.954,18 0,9.941 0,0" /></g></g><g
id="g48"
transform="translate(34,34)"
style="fill:#036ea7;fill-opacity:1"><path
d="m 0,0 c 0,1.105 -0.895,2 -2,2 -1.105,0 -2,-0.895 -2,-2 0,-1.105 0.895,-2 2,-2 1.105,0 2,0.895 2,2"
style="fill:#036ea7;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path50"
inkscape:connector-curvature="0" /></g><g
id="g52"
transform="translate(42,34)"
style="fill:#036ea7;fill-opacity:1"><path
d="m 0,0 c 0,1.105 -0.895,2 -2,2 -1.105,0 -2,-0.895 -2,-2 0,-1.105 0.895,-2 2,-2 1.105,0 2,0.895 2,2"
style="fill:#036ea7;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path54"
inkscape:connector-curvature="0" /></g><g
id="g56"
transform="translate(26,34)"
style="fill:#036ea7;fill-opacity:1"><path
d="m 0,0 c 0,1.105 -0.895,2 -2,2 -1.105,0 -2,-0.895 -2,-2 0,-1.105 0.895,-2 2,-2 1.105,0 2,0.895 2,2"
style="fill:#036ea7;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path58"
inkscape:connector-curvature="0" /></g></g></svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 2 2 L 2 5 L 3 5 L 3 3 L 5 3 L 5 2 L 2 2 z M 6 2 L 6 3 L 10 3 L 10 2 L 6 2 z M 11 2 L 11 3 L 13 3 L 13 5 L 14 5 L 14 2 L 11 2 z M 6 5 A 1 1 0 0 0 5 6 A 1 1 0 0 0 6 7 A 1 1 0 0 0 7 6 A 1 1 0 0 0 6 5 z M 10 5 A 1 1 0 0 0 9 6 A 1 1 0 0 0 10 7 A 1 1 0 0 0 11 6 A 1 1 0 0 0 10 5 z M 2 6 L 2 10 L 3 10 L 3 6 L 2 6 z M 13 6 L 13 10 L 14 10 L 14 6 L 13 6 z M 4 9 A 4 3 0 0 0 8 12 A 4 3 0 0 0 12 9 L 11 9 A 3 2 0 0 1 8 11 A 3 2 0 0 1 5 9 L 4 9 z M 2 11 L 2 14 L 5 14 L 5 13 L 3 13 L 3 11 L 2 11 z M 13 11 L 13 13 L 11 13 L 11 14 L 14 14 L 14 11 L 13 11 z M 6 13 L 6 14 L 10 14 L 10 13 L 6 13 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-NeutralText {
color:#f67400;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
class="ColorScheme-NeutralText"
d="M 1 0 C 0.4459807 0 0 0.446 0 1 L 0 7 C 0 7.5541 0.4459807 8 1 8 L 7 8 C 7.554019 8 8 7.5541 8 7 L 8 1 C 8 0.446 7.554019 0 7 0 L 1 0 z "
/>
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="M 4 1 C 2.8954237 1 1.9999904 1.8954 2 3 L 2.0019531 4 L 1 4 L 1 7 L 7 7 L 7 4 L 6 4 L 6 3 C 6.00001 1.8954 5.104576 1 4 1 z M 4 2 C 4.552285 2 5 2.4477 5 3 L 5 4 L 3 4 L 3 3 C 3 2.4477 3.447715 2 4 2 z "
/>
</svg>

After

Width:  |  Height:  |  Size: 782 B

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg4155"
version="1.1"
inkscape:version="0.91 r13725"
xml:space="preserve"
width="80"
height="80"
viewBox="0 0 80 80"
sodipodi:docname="exit.svg"><metadata
id="metadata4161"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs4159" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="706"
id="namedview4157"
showgrid="true"
inkscape:zoom="5.11875"
inkscape:cx="39.840819"
inkscape:cy="37.23375"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g4163"
showguides="false"><inkscape:grid
type="xygrid"
id="grid4142" /></sodipodi:namedview><g
id="g4163"
inkscape:groupmode="layer"
inkscape:label="ink_ext_XXXXXX"
transform="matrix(1.25,0,0,-1.25,0,80)"><path
inkscape:connector-curvature="0"
id="path4167"
style="fill:#039bd5;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 64,32 C 64,14.327 49.673,0 32,0 14.327,0 0,14.327 0,32 0,49.673 14.327,64 32,64 49.673,64 64,49.673 64,32" /><path
inkscape:connector-curvature="0"
style="fill:none;stroke:#ffffff;stroke-width:4.02773762;stroke-linecap:round;stroke-miterlimit:10"
d="m 39.607341,43.825636 c 4.92391,-2.756986 8.252834,-8.023253 8.252834,-14.06788 0,-8.89828 -7.21267,-16.11095 -16.11095,-16.11095 -8.89828,0 -16.110951,7.21267 -16.110951,16.11095 0,5.927822 3.201045,11.107492 7.96888,13.904757"
stroke-miterlimit="10"
id="path13" /><line
style="fill:none;stroke:#ffffff;stroke-width:4.02773762;stroke-linecap:round;stroke-miterlimit:10"
stroke-miterlimit="10"
x1="31.749226"
x2="31.749226"
y1="29.757759"
y2="49.89645"
id="line15" /></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Some files were not shown because too many files have changed in this diff Show More