mirror of
https://github.com/RetroShare/RetroShare.git
synced 2025-05-28 18:42:20 -04:00
Merge branch 'GSoC2017-evaluation-final'
This commit is contained in:
commit
32f43b999e
1431 changed files with 6666 additions and 445 deletions
159
retroshare-qml-app/src/components/AvatarOrColorHash.qml
Normal file
159
retroshare-qml-app/src/components/AvatarOrColorHash.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
28
retroshare-qml-app/src/components/ButtonIcon.qml
Normal file
28
retroshare-qml-app/src/components/ButtonIcon.qml
Normal 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() }
|
||||
}
|
||||
}
|
130
retroshare-qml-app/src/components/ButtonText.qml
Normal file
130
retroshare-qml-app/src/components/ButtonText.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
72
retroshare-qml-app/src/components/ColorHash.qml
Normal file
72
retroshare-qml-app/src/components/ColorHash.qml
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
Rectangle
|
||||
{
|
||||
property string hash
|
||||
|
||||
width: height
|
||||
property int childHeight : height/2
|
||||
color: "white"
|
||||
|
||||
Image
|
||||
{
|
||||
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
|
||||
{
|
||||
color: '#' + hash.substring(1, 9)
|
||||
height: parent.childHeight
|
||||
width: height
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
color: '#' + hash.substring(9, 17)
|
||||
height: parent.childHeight
|
||||
width: height
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
color: '#' + hash.substring(17, 25)
|
||||
height: parent.childHeight
|
||||
width: height
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
color: '#' + hash.slice(-8)
|
||||
height: parent.childHeight
|
||||
width: height
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
60
retroshare-qml-app/src/components/CustomFileChooser.qml
Normal file
60
retroshare-qml-app/src/components/CustomFileChooser.qml
Normal 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
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
202
retroshare-qml-app/src/components/Faces.qml
Normal file
202
retroshare-qml-app/src/components/Faces.qml
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
283
retroshare-qml-app/src/components/SideBar.qml
Normal file
283
retroshare-qml-app/src/components/SideBar.qml
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
38
retroshare-qml-app/src/components/TextAndIcon.qml
Normal file
38
retroshare-qml-app/src/components/TextAndIcon.qml
Normal 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
|
||||
}
|
||||
|
||||
|
||||
}
|
56
retroshare-qml-app/src/components/emoji/EmojiButton.qml
Normal file
56
retroshare-qml-app/src/components/emoji/EmojiButton.qml
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
130
retroshare-qml-app/src/components/emoji/EmojiPicker.qml
Normal file
130
retroshare-qml-app/src/components/emoji/EmojiPicker.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
|
1068
retroshare-qml-app/src/components/emoji/emoji.js
Normal file
1068
retroshare-qml-app/src/components/emoji/emoji.js
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue